diff --git a/.editorconfig b/.editorconfig index b54df7ecd379..1b960db343d2 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/CODEOWNERS b/.github/CODEOWNERS index 34dcdf6482d4..bae511e8d963 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -9,3 +9,39 @@ /Dockerfile* @nethermindeth/core-devops @rubo /LICENSE* @rubo @LukaszRozmej @MarekM25 *.md @rubo @LukaszRozmej @MarekM25 + +/src/Nethermind/Ethereum.Test.Base/ @flcl42 +/src/Nethermind/Nethermind.Abi @LukaszRozmej +/src/Nethermind/Nethermind.Blockchain @LukaszRozmej @MarekM25 @flcl42 +/src/Nethermind/Nethermind.Consensus @LukaszRozmej @MarekM25 @flcl42 +/src/Nethermind/Nethermind.Crypto @LukaszRozmej @Marchhill +/src/Nethermind/Nethermind.Db.Rocks @LukaszRozmej @asdacap @damian-orzechowski +/src/Nethermind/Nethermind.Era1 @ak88 @Marchhill @smartprogrammer93 +/src/Nethermind/Nethermind.Evm.Precompiles @LukaszRozmej @benaadams @rubo @Marchhill +/src/Nethermind/Nethermind.Evm @LukaszRozmej @benaadams @Demuirgos +/src/Nethermind/Nethermind.ExternalSigner.Plugin @ak88 +/src/Nethermind/Nethermind.History @LukaszRozmej @Marchhill +/src/Nethermind/Nethermind.Hive @flcl42 @marcindsobczak +/src/Nethermind/Nethermind.Init @LukaszRozmej @asdacap +/src/Nethermind/Nethermind.JsonRpc.TraceStore @LukaszRozmej +/src/Nethermind/Nethermind.JsonRpc @LukaszRozmej @benaadams @smartprogrammer93 +/src/Nethermind/Nethermind.Merge.Plugin @LukaszRozmej @MarekM25 @flcl42 @benaadams @Marchhill +/src/Nethermind/Nethermind.Merkleization @ak88 @Marchhill @smartprogrammer93 @flcl42 +/src/Nethermind/Nethermind.Network.Discovery @LukaszRozmej @flcl42 @asdacap +/src/Nethermind/Nethermind.Network @LukaszRozmej @flcl42 @asdacap @marcindsobczak +/src/Nethermind/Nethermind.Optimism @emlautarom1 @deffrian +/src/Nethermind/Nethermind.Runner @LukaszRozmej @flcl42 +/src/Nethermind/Nethermind.Serialization.Json @LukaszRozmej @benaadams +/src/Nethermind/Nethermind.Serialization.Rlp @LukaszRozmej @Marchhill +/src/Nethermind/Nethermind.Serialization.Ssz @flcl42 @ak88 @Marchhill +/src/Nethermind/Nethermind.Serialization.SszGenerator @flcl42 @ak88 @Marchhill +/src/Nethermind/Nethermind.Shutter @Marchhill +/src/Nethermind/Nethermind.Sockets @LukaszRozmej @benaadams +/src/Nethermind/Nethermind.Specs @smartprogrammer93 +/src/Nethermind/Nethermind.State @LukaszRozmej @benaadams @asdacap @flcl42 @damian-orzechowski @tanishqjasoria +/src/Nethermind/Nethermind.Synchronization @LukaszRozmej @benaadams @asdacap @flcl42 @marcindsobczak +/src/Nethermind/Nethermind.Taiko @smartprogrammer93 @dipkakwani +/src/Nethermind/Nethermind.Trie @LukaszRozmej @benaadams @asdacap @flcl42 @damian-orzechowski @tanishqjasoria +/src/Nethermind/Nethermind.TxPool @LukaszRozmej @benaadams @flcl42 @marcindsobczak +/src/Nethermind/Nethermind.UI @benaadams +/src/Nethermind/Nethermind.Xdc @ak88 @Demuirgos @cicr99 diff --git a/.github/actions/publish-docker/action.yaml b/.github/actions/publish-docker/action.yaml new file mode 100644 index 000000000000..2613c6ccfdd3 --- /dev/null +++ b/.github/actions/publish-docker/action.yaml @@ -0,0 +1,106 @@ +name: Publish Docker Image +description: "Build and push Docker image to Docker Hub with multi-platform support" +inputs: + image-name: + description: "Image name (without registry prefix)" + required: true + default: "nethermind" + tag: + description: "Image tag (if empty, will use branch name)" + required: false + default: "" + dockerfile: + description: "Path to Dockerfile" + required: true + default: "Dockerfile" + build-config: + description: "Build configuration (release or debug)" + required: true + default: "release" + docker-hub-username: + description: "Docker Hub username" + required: true + docker-hub-password: + description: "Docker Hub password or token" + required: true + platforms: + description: "Comma-separated list of platforms (e.g., linux/amd64,linux/arm64)" + required: false + default: "linux/amd64,linux/arm64" + add-master-tag: + description: "Whether to add master tag with commit SHA when on master branch" + required: false + default: "true" + +outputs: + image-label: + description: "The generated image label in format registry/image-name:tag" + value: ${{ steps.build-image.outputs.image-label }} + +runs: + using: "composite" + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ inputs.docker-hub-username }} + password: ${{ inputs.docker-hub-password }} + + - name: Build and push image to Docker Hub + id: build-image + shell: bash + env: + IMAGE_NAME: ${{ inputs.image-name }} + TAG: ${{ inputs.tag }} + DOCKERFILE: ${{ inputs.dockerfile }} + BUILD_CONFIG: ${{ inputs.build-config }} + PLATFORMS: ${{ inputs.platforms }} + ADD_MASTER_TAG: ${{ inputs.add-master-tag }} + GITHUB_REF: ${{ github.ref }} + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_SHA: ${{ github.sha }} + run: | + # Extract branch name from ref + branch=$(echo "$GITHUB_REF" | sed -e "s/refs\/heads\///g") + + # Set image name with registry prefix + full_image_name="nethermindeth/${IMAGE_NAME}" + + # Determine tag (use input tag or branch name) + if [ -z "$TAG" ]; then + tag=$(echo "$branch" | sed 's/\//-/g') # replace '/' with '-' + else + tag=$(echo "$TAG" | sed 's/\//-/g') # replace '/' with '-' + fi + + # Set the image label output + image_label="${full_image_name}:${tag}" + echo "image-label=${image_label}" >> $GITHUB_OUTPUT + + echo "Building image: ${image_label}" + + # Build Docker buildx command + build_cmd="docker buildx build . --platform=${PLATFORMS} -f ${DOCKERFILE} -t ${image_label}" + + # Add master tag if on master branch and enabled + if [ "$branch" = "master" ] && [ "$GITHUB_EVENT_NAME" = "push" ] && [ "$ADD_MASTER_TAG" = "true" ]; then + master_tag="${full_image_name}:master-${GITHUB_SHA:0:7}" + build_cmd="${build_cmd} -t ${master_tag}" + echo "Also tagging as: $master_tag" + fi + + # Add build arguments + build_cmd="${build_cmd} --build-arg BUILD_CONFIG=${BUILD_CONFIG}" + build_cmd="${build_cmd} --build-arg CI=${CI}" + build_cmd="${build_cmd} --build-arg COMMIT_HASH=${GITHUB_SHA}" + build_cmd="${build_cmd} --build-arg SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" + build_cmd="${build_cmd} --push" + + echo "Executing: $build_cmd" + eval "$build_cmd" diff --git a/.github/actions/remove-runner/action.yaml b/.github/actions/remove-runner/action.yaml new file mode 100644 index 000000000000..e7580c10195d --- /dev/null +++ b/.github/actions/remove-runner/action.yaml @@ -0,0 +1,81 @@ +name: Remove GH Runner +description: "Remove a self-hosted GitHub Actions runner and destroy its machine" +inputs: + gh-token: + description: "The token of the GitHub App to use for workflow invocations" + required: true + gh-runner-token: + description: "The token to use for runner deregistration (optional, defaults to gh-token)" + required: false + default: "" + runner-name: + description: "Name of the runner to remove from GitHub Actions" + required: true + workflow-run-id: + description: "The workflow run ID from the machine creation (used to destroy the machine)" + required: true + +runs: + using: "composite" + + steps: + - name: Remove runner from GitHub Actions + if: ${{ inputs.runner-name != '' }} + continue-on-error: true + env: + GH_TOKEN: ${{ inputs.gh-runner-token != '' && inputs.gh-runner-token || inputs.gh-token }} + TARGET_RUNNER_NAME: ${{ inputs.runner-name }} + ORG_NAME: ${{ github.repository_owner }} + REPO_NAME: ${{ github.event.repository.name }} + shell: bash + run: | + echo "Removing runner '$TARGET_RUNNER_NAME' from GitHub Actions..." + + # Get the runner ID + RUNNER_ID=$(gh api \ + --method GET \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/${ORG_NAME}/${REPO_NAME}/actions/runners" \ + --jq ".runners[] | select(.name == \"$TARGET_RUNNER_NAME\") | .id") + + if [ -z "$RUNNER_ID" ]; then + echo "Warning: Runner '$TARGET_RUNNER_NAME' not found in repository. It may have already been removed." + else + echo "Found runner with ID: $RUNNER_ID" + + # Remove the runner + gh api \ + --method DELETE \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/${ORG_NAME}/${REPO_NAME}/actions/runners/${RUNNER_ID}" + + echo "Runner removed successfully from GitHub Actions" + fi + + - name: Trigger machine destruction workflow + if: ${{ inputs.workflow-run-id != '' }} + env: + GH_TOKEN: ${{ inputs.gh-token }} + shell: bash + run: | + echo "Triggering machine destruction workflow..." + echo "Workflow Run ID: ${{ inputs.workflow-run-id }}" + + gh workflow run manual-destroy-smoketests.yml \ + --repo NethermindEth/post-merge-smoke-tests \ + --ref main \ + --field workflow_run_id="${{ inputs.workflow-run-id }}" + + echo "Machine destruction workflow triggered successfully" + + - name: Display removal summary + shell: bash + run: | + echo "### Self-Hosted Runner Removed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Runner Name**: ${{ inputs.runner-name }}" >> $GITHUB_STEP_SUMMARY + echo "- **Workflow Run ID**: ${{ inputs.workflow-run-id }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "The runner has been removed from GitHub Actions and the machine destruction workflow has been triggered." >> $GITHUB_STEP_SUMMARY diff --git a/.github/actions/setup-runner/action.yaml b/.github/actions/setup-runner/action.yaml new file mode 100644 index 000000000000..7838a8aaa494 --- /dev/null +++ b/.github/actions/setup-runner/action.yaml @@ -0,0 +1,217 @@ +name: Setup GH Runner +description: "Setup a self-hosted GitHub Actions runner" +inputs: + gh-token: + description: "The token of the GitHub App to use for workflow invocations" + required: true + gh-runner-token: + description: "The token to use for runner registration (optional, defaults to gh-token)" + required: false + default: "" + ssh-private-key: + description: "SSH private key for accessing the runner machine" + required: true + gh-username: + description: "GitHub username that manages this runner" + required: true + runner-name: + description: "Runner name to be used as base label for the Linode instance" + required: true + runner-type: + description: "Runner Linode machine type (e.g., g6-standard-2)" + required: true + tags: + description: "Comma separated tags for the runner machine" + required: false + default: "" + allowed-ips: + description: "Comma separated allowed IPs for the runner firewall" + required: false + default: "" + ssh-keys: + description: "Comma separated public SSH keys for the runner machine" + required: false + default: "" + timeout: + description: "Timeout for the runner machine auto removal in hours" + required: false + default: "24" + setup-wait-time: + description: "Time to wait (in seconds) for runner setup to complete before returning" + required: false + default: "60" + +outputs: + runner-name: + description: "Name of the created runner (generated by external workflow)" + value: ${{ steps.wait-workflow.outputs.runner_name }} + machine-ip: + description: "IP address of the created machine" + value: ${{ steps.extract-details.outputs.machine_ip }} + run-id: + description: "Run ID of the machine creation workflow" + value: ${{ steps.wait-workflow.outputs.run_id }} + +runs: + using: "composite" + + steps: + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.x" + + - name: Install dependencies + shell: bash + run: | + pip install jinja2 + + - name: Build base tag and runner label + env: + USER_NAME: ${{ inputs.gh-username || github.actor }} + shell: bash + run: | + # Generate BASE_TAG using the first letter of USER_NAME and a random 4-digit number + BASE_TAG="${USER_NAME:0:1}$(shuf -i 1000-9999 -n 1)" + echo "BASE_TAG=$BASE_TAG" >> $GITHUB_ENV + echo "Generated BASE_TAG: $BASE_TAG" + + # Create unique runner label to avoid collisions + GH_RUNNER_LABEL="${{ inputs.runner-name }}-${USER_NAME}-${BASE_TAG}" + echo "GH_RUNNER_LABEL=$GH_RUNNER_LABEL" >> $GITHUB_ENV + echo "Generated GH_RUNNER_LABEL: $GH_RUNNER_LABEL" + + - name: Generate workflow inputs with setup script + env: + BASE_TAG: ${{ env.BASE_TAG }} + GITHUB_USERNAME: ${{ inputs.gh-username }} + CUSTOM_RUNNER_NAME: ${{ inputs.runner-name }} + CUSTOM_RUNNER_TYPE: ${{ inputs.runner-type }} + TAGS: ${{ inputs.tags }} + ALLOWED_IPS: ${{ inputs.allowed-ips }} + SSH_KEYS: ${{ inputs.ssh-keys }} + TIMEOUT: ${{ inputs.timeout }} + OUTPUT_FILE: workflow-inputs.json + shell: bash + run: | + echo "Generating workflow inputs with runner setup script..." + python3 ${{ github.action_path }}/generate_workflow_inputs.py + + - name: Trigger machine creation workflow + env: + GH_TOKEN: ${{ inputs.gh-token }} + shell: bash + run: | + echo "Triggering machine creation workflow..." + gh workflow run run-custom-node.yml \ + --repo NethermindEth/post-merge-smoke-tests \ + --ref main \ + --json < workflow-inputs.json + + echo "Workflow triggered successfully" + + - name: Wait for machine creation workflow + id: wait-workflow + env: + GITHUB_TOKEN: ${{ inputs.gh-token }} + WORKFLOW_ID: "run-custom-node.yml" + MAX_WAIT_MINUTES: "5" + INTERVAL: "5" + TIMEOUT: "20" + ORG_NAME: "NethermindEth" + REPO_NAME: "post-merge-smoke-tests" + NAME_FILTER: ${{ env.BASE_TAG }} + REF: "main" + shell: bash + run: | + chmod +x ${{ github.workspace }}/scripts/wait-for-workflow.sh + ${{ github.workspace }}/scripts/wait-for-workflow.sh | tee script-output.txt + run_id=$(grep -oP 'Run ID: \K\d+' script-output.txt) + echo "Run ID extracted is: $run_id" + echo "run_id=$run_id" >> $GITHUB_OUTPUT + + # Set runner name output early (even if machine setup fails later) + # This ensures cleanup can always identify and remove the runner + echo "runner_name=${{ env.GH_RUNNER_LABEL }}" >> $GITHUB_OUTPUT + + - name: Download machine specs artifact + env: + GH_TOKEN: ${{ inputs.gh-token }} + shell: bash + run: | + echo "Downloading machine specs artifact..." + mkdir -p ./downloaded-artifacts + + gh run download "${{ steps.wait-workflow.outputs.run_id }}" \ + --repo NethermindEth/post-merge-smoke-tests \ + --dir ./downloaded-artifacts + + echo "Artifacts downloaded successfully" + + - name: Extract machine details + id: extract-details + shell: bash + run: | + # The artifact contains a directory structure with machine-details/machine-specs.txt + # After gh run download, it extracts to ./downloaded-artifacts//machine-details/machine-specs.txt + + # Find the machine-specs.txt file + SPECS_FILE=$(find ./downloaded-artifacts -type f -name "machine-specs.txt" | head -n 1) + + if [ -z "$SPECS_FILE" ]; then + echo "Error: Could not find machine-specs.txt in downloaded artifacts" + echo "Contents of downloaded-artifacts:" + find ./downloaded-artifacts -type f + exit 1 + fi + + echo "Found machine specs file: $SPECS_FILE" + + # Display details in summary + echo "
" >> $GITHUB_STEP_SUMMARY + echo "Self-hosted runner machine details" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + cat "$SPECS_FILE" >> $GITHUB_STEP_SUMMARY + echo "
" >> $GITHUB_STEP_SUMMARY + + # Extract details from the artifact + cat "$SPECS_FILE" | tee machine-output.txt + + # Extract machine IP (format: " IP: 123.456.789.012") + MACHINE_IP=$(grep -oP '^\s+IP:\s+\K[0-9.]+' machine-output.txt | head -n 1 || echo "unknown") + echo "Machine IP: $MACHINE_IP" + echo "machine_ip=$MACHINE_IP" >> $GITHUB_OUTPUT + + - name: Wait for runner setup to complete + shell: bash + run: | + WAIT_TIME="${{ inputs.setup-wait-time }}" + echo "Waiting ${WAIT_TIME} seconds for runner setup to complete..." + sleep "${WAIT_TIME}" + echo "Runner setup wait time completed" + + - name: Configure runner via SSH + env: + MACHINE_IP: ${{ steps.extract-details.outputs.machine_ip }} + GH_TOKEN: ${{ inputs.gh-runner-token != '' && inputs.gh-runner-token || inputs.gh-token }} + GH_RUNNER_LABEL: ${{ env.GH_RUNNER_LABEL }} + ORG_NAME: ${{ github.repository_owner }} + REPO_NAME: ${{ github.event.repository.name }} + SSH_PRIVATE_KEY: ${{ inputs.ssh-private-key }} + shell: bash + run: | + chmod +x ${{ github.action_path }}/runner-configure.sh + ${{ github.action_path }}/runner-configure.sh + + - name: Display runner information + shell: bash + run: | + echo "### Self-Hosted Runner Created" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Runner Name**: ${{ steps.wait-workflow.outputs.runner_name }}" >> $GITHUB_STEP_SUMMARY + echo "- **Base Tag**: ${{ env.BASE_TAG }}" >> $GITHUB_STEP_SUMMARY + echo "- **Machine IP**: ${{ steps.extract-details.outputs.machine_ip }}" >> $GITHUB_STEP_SUMMARY + echo "- **Timeout**: ${{ inputs.timeout }} hours" >> $GITHUB_STEP_SUMMARY + echo "- **Run ID**: ${{ steps.wait-workflow.outputs.run_id }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "The runner is now available for use in workflows." >> $GITHUB_STEP_SUMMARY diff --git a/.github/actions/setup-runner/generate_workflow_inputs.py b/.github/actions/setup-runner/generate_workflow_inputs.py new file mode 100644 index 000000000000..be788dcd96d4 --- /dev/null +++ b/.github/actions/setup-runner/generate_workflow_inputs.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +""" +Generate workflow inputs JSON for the runner creation workflow. +This script: +1. Renders the runner setup script from a Jinja2 template +2. Compresses and base64-encodes the setup script +3. Creates the workflow inputs JSON with all required parameters +""" + +import base64 +import gzip +import json +import os +import sys +from pathlib import Path + +try: + from jinja2 import ( # ty:ignore[unresolved-import] + Environment, + FileSystemLoader, + select_autoescape, + ) +except ImportError: + print( + "Error: jinja2 is required. Install with: pip install jinja2", file=sys.stderr + ) + sys.exit(1) + + +def get_env_var(name, required=True, default=None): + """Get an environment variable with optional requirement check.""" + value = os.environ.get(name, default) + if required and not value: + print( + f"Error: Environment variable {name} is required but not set", + file=sys.stderr, + ) + sys.exit(1) + return value + + +def render_setup_script(template_path, context): + """Render the setup script from Jinja2 template.""" + template_dir = template_path.parent + template_file = template_path.name + + env = Environment( + loader=FileSystemLoader(template_dir), + autoescape=select_autoescape(), + trim_blocks=True, + lstrip_blocks=True, + ) + + template = env.get_template(template_file) + rendered_script = template.render(context) + + return rendered_script + + +def encode_script(script_content): + """Compress with gzip and encode with base64.""" + compressed = gzip.compress(script_content.encode("utf-8")) + encoded = base64.b64encode(compressed).decode("ascii") + return encoded + + +def generate_workflow_inputs(): + """Generate the complete workflow inputs JSON.""" + + # Get environment variables + # Note: Avoid RUNNER_* names as they are reserved by GitHub Actions + base_tag = get_env_var("BASE_TAG") + github_username = get_env_var("GITHUB_USERNAME") + custom_runner_name = get_env_var("CUSTOM_RUNNER_NAME") + custom_runner_type = get_env_var("CUSTOM_RUNNER_TYPE") + tags = get_env_var("TAGS", required=False, default="") + allowed_ips = get_env_var("ALLOWED_IPS", required=False, default="") + ssh_keys = get_env_var("SSH_KEYS", required=False, default="") + timeout = get_env_var("TIMEOUT", required=False, default="24") + + # Find template file + script_dir = Path(__file__).parent + template_path = script_dir / "runner-setup.sh.j2" + + if not template_path.exists(): + print(f"Error: Template file not found: {template_path}", file=sys.stderr) + sys.exit(1) + + # Render the setup script (template has no variables, but we use Jinja2 for consistency) + print(f"Rendering setup script from template: {template_path}", file=sys.stderr) + setup_script = render_setup_script(template_path, {}) + + # Encode the setup script + print("Encoding setup script...", file=sys.stderr) + encoded_script = encode_script(setup_script) + + # Create workflow inputs JSON + workflow_inputs = { + "base_tag": base_tag, + "github_username": github_username, + "custom_node_name": custom_runner_name, + "custom_node_type": custom_runner_type, + "setup_script": encoded_script, + "tags": tags, + "allowed_ips": allowed_ips, + "ssh_keys": ssh_keys, + "timeout": timeout, + } + + return workflow_inputs + + +def main(): + """Main entry point.""" + try: + # Generate workflow inputs + workflow_inputs = generate_workflow_inputs() + + # Write to file + output_file = get_env_var( + "OUTPUT_FILE", required=False, default="workflow-inputs.json" + ) + with open(output_file, "w") as f: + json.dump(workflow_inputs, f, indent=2) + + print(f"Workflow inputs written to: {output_file}", file=sys.stderr) + print( + f"Setup script size: {len(workflow_inputs['setup_script'])} bytes (encoded)", + file=sys.stderr, + ) + + # Also write to GITHUB_OUTPUT if available + github_output = os.environ.get("GITHUB_OUTPUT") + if github_output: + with open(github_output, "a") as f: + f.write(f"workflow_inputs_file={output_file}\n") + + return 0 + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + import traceback + + traceback.print_exc() + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/actions/setup-runner/runner-configure.sh b/.github/actions/setup-runner/runner-configure.sh new file mode 100644 index 000000000000..e59c31070462 --- /dev/null +++ b/.github/actions/setup-runner/runner-configure.sh @@ -0,0 +1,140 @@ +#!/bin/bash +set -e + +# Configure GitHub Actions Runner via SSH +# This script runs locally and uses SSH to configure the runner on the remote machine +# The GitHub token is passed via stdin to avoid it being visible in process listings or logs + +echo "=========================================" +echo "Configuring Runner via SSH" +echo "=========================================" + +# Validate required environment variables +if [ -z "$MACHINE_IP" ]; then + echo "Error: MACHINE_IP environment variable is required" + exit 1 +fi + +if [ -z "$GH_TOKEN" ]; then + echo "Error: GH_TOKEN environment variable is required" + exit 1 +fi + +if [ -z "$GH_RUNNER_LABEL" ]; then + echo "Error: GH_RUNNER_LABEL environment variable is required" + exit 1 +fi + +if [ -z "$ORG_NAME" ]; then + echo "Error: ORG_NAME environment variable is required" + exit 1 +fi + +if [ -z "$REPO_NAME" ]; then + echo "Error: REPO_NAME environment variable is required" + exit 1 +fi + +if [ -z "$SSH_PRIVATE_KEY" ]; then + echo "Error: SSH_PRIVATE_KEY environment variable is required" + exit 1 +fi + +# Setup SSH key in current directory +echo "Setting up SSH key..." +echo "$SSH_PRIVATE_KEY" > ./runner_key +chmod 600 ./runner_key + +# SSH options +SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ./runner_key" + +# Get registration token (keeping it off the remote machine) +echo "Obtaining registration token..." +REGISTRATION_TOKEN=$(curl -s -X POST \ + -H "Authorization: token ${GH_TOKEN}" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${ORG_NAME}/${REPO_NAME}/actions/runners/registration-token" | jq -r '.token') + +if [ -z "$REGISTRATION_TOKEN" ] || [ "$REGISTRATION_TOKEN" = "null" ]; then + echo "Error: Failed to obtain registration token" + rm -f ./runner_key + exit 1 +fi + +# Mask the registration token to prevent it from appearing in logs +echo "::add-mask::${REGISTRATION_TOKEN}" + +echo "Registration token obtained successfully" + +# Configure runner via SSH +echo "Configuring GitHub Actions runner on remote machine at ${MACHINE_IP}..." + +# Execute runner configuration remotely +ssh ${SSH_OPTS} root@${MACHINE_IP} \ + RUNNER_TOKEN="${REGISTRATION_TOKEN}" \ + ORG_NAME="${ORG_NAME}" \ + REPO_NAME="${REPO_NAME}" \ + GH_RUNNER_LABEL="${GH_RUNNER_LABEL}" \ + bash << 'ENDSSH' + set -e + + # Set up logging to file in home directory + LOG_FILE="${HOME}/runner-configure-$(date +%Y%m%d-%H%M%S).log" + + # Redirect all output (stdout and stderr) to both console and log file + exec > >(tee -a "${LOG_FILE}") 2>&1 + + echo "=========================================" + echo "Runner Configuration via SSH" + echo "Started at: $(date)" + echo "Log file: ${LOG_FILE}" + echo "=========================================" + + cd /root/actions-runner + export RUNNER_ALLOW_RUNASROOT="1" + + echo "Configuring runner..." + # Registration token is short-lived and single-use, only for runner registration + # By the time the machine is accessible, this process will have completed + ./config.sh --url "https://github.com/${ORG_NAME}/${REPO_NAME}" \ + --token "${RUNNER_TOKEN}" \ + --name "${GH_RUNNER_LABEL}" \ + --labels "${GH_RUNNER_LABEL}" \ + --unattended \ + --replace + + echo "Runner configured successfully" + + # Install and start the runner service + echo "Installing runner service..." + ./svc.sh install + + echo "Starting runner service..." + ./svc.sh start + + # Verify service is running + sleep 2 + ./svc.sh status + + echo "" + echo "=========================================" + echo "Runner Configuration Complete" + echo "Log file saved to: ${LOG_FILE}" + echo "=========================================" +ENDSSH + +EXIT_CODE=$? + +# Cleanup SSH key +rm -f ./runner_key + +if [ $EXIT_CODE -ne 0 ]; then + echo "Error: SSH runner configuration failed with exit code ${EXIT_CODE}" + exit $EXIT_CODE +fi + +echo "" +echo "=========================================" +echo "Runner Configuration Complete" +echo "=========================================" +echo "" diff --git a/.github/actions/setup-runner/runner-setup.sh.j2 b/.github/actions/setup-runner/runner-setup.sh.j2 new file mode 100644 index 000000000000..dcf1ab2c9dcc --- /dev/null +++ b/.github/actions/setup-runner/runner-setup.sh.j2 @@ -0,0 +1,75 @@ +#!/bin/bash +set -e + +# GitHub Actions Runner Setup Script (Part 1 - Machine Setup) +# This script is generated from a Jinja2 template and sets up a self-hosted GitHub Actions runner +# This part runs on the remote machine and prepares it for runner configuration + +# Determine script directory (where Stackscript is typically created) +SCRIPT_DIR="${HOME}" +LOG_FILE="${SCRIPT_DIR}/runner-setup-$(date +%Y%m%d-%H%M%S).log" + +# Redirect all output (stdout and stderr) to both console and log file +exec > >(tee -a "${LOG_FILE}") 2>&1 + +echo "=========================================" +echo "GitHub Actions Runner Setup (Machine Preparation)" +echo "Started at: $(date)" +echo "Log file: ${LOG_FILE}" +echo "=========================================" + +# Display environment for debugging +echo "Environment information:" +echo " HOME: ${HOME}" +echo " USER: ${USER}" +echo " PWD: $(pwd)" +echo " Hostname: $(hostname)" +echo " Script directory: ${SCRIPT_DIR}" +echo " Log file: ${LOG_FILE}" +echo "" + +# Check if running as root +if [ "$(id -u)" -ne 0 ]; then + echo "Error: This script must be run as root" + exit 1 +fi + +# Install dependencies +echo "Installing dependencies..." + +# Preparing Git-LFS +curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash + +apt-get update +apt-get install -y curl jq git-lfs + +# Create runner directory +GH_RUNNER_DIR="/root/actions-runner" +echo "Creating runner directory: ${GH_RUNNER_DIR}" +mkdir -p "${GH_RUNNER_DIR}" +cd "${GH_RUNNER_DIR}" + +# Download the latest runner package +echo "Downloading GitHub Actions runner..." +RUNNER_VERSION=$(curl -s https://api.github.com/repos/actions/runner/releases/latest | jq -r '.tag_name' | sed 's/v//') +echo "Latest runner version: ${RUNNER_VERSION}" + +curl -o actions-runner-linux-x64.tar.gz -L "https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz" + +# Verify download +if [ ! -f "actions-runner-linux-x64.tar.gz" ]; then + echo "Error: Failed to download runner package" + exit 1 +fi + +# Extract the installer +echo "Extracting runner package..." +tar xzf ./actions-runner-linux-x64.tar.gz + +echo "" +echo "=========================================" +echo "Machine Preparation Complete" +echo "Ready for runner registration via SSH" +echo "Log file saved to: ${LOG_FILE}" +echo "=========================================" +echo "" diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index e1724408447e..000000000000 --- 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 2039db8800e9..a3e8263dacf8 100644 --- a/.github/workflows/build-nethermind-packages.yml +++ b/.github/workflows/build-nethermind-packages.yml @@ -11,52 +11,63 @@ jobs: PACKAGE_DIR: pkg PACKAGE_RETENTION: 7 PUB_DIR: pub - SCRIPTS_PATH: ${{ github.workspace }}/scripts/build steps: - name: Check out repository - uses: actions/checkout@v5 - - name: Set up .NET - uses: actions/setup-dotnet@v5 + uses: actions/checkout@v6 + + - name: Detect version + id: version + working-directory: src/Nethermind + run: | + sudo apt-get update && sudo apt-get install xmlstarlet -y --no-install-recommends + version_prefix=$(xmlstarlet sel -t -v "//Project/PropertyGroup/VersionPrefix" Directory.Build.props) + version_suffix=$(xmlstarlet sel -t -v "//Project/PropertyGroup/VersionSuffix" -n Directory.Build.props) + version=$([[ -n "$version_suffix" ]] && echo "$version_prefix-$version_suffix" || echo "$version_prefix") + echo "Detected version $version" + echo "PACKAGE_PREFIX=nethermind-$version-${GITHUB_SHA:0:8}" >> $GITHUB_ENV + - name: Build Nethermind.Runner - id: build run: | - build_timestamp=$(date '+%s') - echo "build-timestamp=$build_timestamp" >> $GITHUB_OUTPUT - echo "commit-hash=${GITHUB_SHA:0:8}" >> $GITHUB_OUTPUT - $SCRIPTS_PATH/build.sh $GITHUB_SHA $build_timestamp + 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=./$PUB_DIR,target=/output nethermind-build + - name: Archive packages - env: - PACKAGE_PREFIX: nethermind-preview-${{ steps.build.outputs.commit-hash }} - run: | - echo "PACKAGE_PREFIX=$PACKAGE_PREFIX" >> $GITHUB_ENV - $SCRIPTS_PATH/archive.sh - - name: Upload Nethermind Linux x64 package - uses: actions/upload-artifact@v4 - with: - name: ${{ env.PACKAGE_PREFIX }}-linux-x64-package - path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }}/*linux-x64* - retention-days: ${{ env.PACKAGE_RETENTION }} + run: scripts/build/archive.sh + - name: Upload Nethermind Linux arm64 package uses: actions/upload-artifact@v4 with: name: ${{ env.PACKAGE_PREFIX }}-linux-arm64-package path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }}/*linux-arm64* retention-days: ${{ env.PACKAGE_RETENTION }} - - name: Upload Nethermind Windows x64 package + + - name: Upload Nethermind Linux x64 package uses: actions/upload-artifact@v4 with: - name: ${{ env.PACKAGE_PREFIX }}-windows-x64-package - path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }}/*windows-x64* + name: ${{ env.PACKAGE_PREFIX }}-linux-x64-package + path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }}/*linux-x64* + retention-days: ${{ env.PACKAGE_RETENTION }} + + - name: Upload Nethermind macOS arm64 package + uses: actions/upload-artifact@v4 + with: + name: ${{ env.PACKAGE_PREFIX }}-macos-arm64-package + path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }}/*macos-arm64* retention-days: ${{ env.PACKAGE_RETENTION }} + - name: Upload Nethermind macOS x64 package uses: actions/upload-artifact@v4 with: name: ${{ env.PACKAGE_PREFIX }}-macos-x64-package path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }}/*macos-x64* retention-days: ${{ env.PACKAGE_RETENTION }} - - name: Upload Nethermind macOS arm64 package + + - name: Upload Nethermind Windows x64 package uses: actions/upload-artifact@v4 with: - name: ${{ env.PACKAGE_PREFIX }}-macos-arm64-package - path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }}/*macos-arm64* + name: ${{ env.PACKAGE_PREFIX }}-windows-x64-package + path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }}/*windows-x64* retention-days: ${{ env.PACKAGE_RETENTION }} diff --git a/.github/workflows/build-solutions.yml b/.github/workflows/build-solutions.yml index b1fa89a114aa..5434f1dffc89 100644 --- a/.github/workflows/build-solutions.yml +++ b/.github/workflows/build-solutions.yml @@ -17,15 +17,24 @@ jobs: solution: [Nethermind, EthereumTests, Benchmarks] steps: - name: Free up disk space - uses: jlumbroso/free-disk-space@main + uses: jlumbroso/free-disk-space@v1.3.1 with: large-packages: false tool-cache: false + - name: Check out repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: ${{ matrix.solution == 'EthereumTests' }} + - name: Set up .NET uses: actions/setup-dotnet@v5 + with: + cache: true + cache-dependency-path: src/Nethermind/Nethermind.Runner/packages.lock.json + + - name: Install dependencies + run: dotnet restore src/Nethermind/${{ matrix.solution }}.slnx --locked-mode + - name: Build ${{ matrix.solution }}.slnx - run: dotnet build src/Nethermind/${{ matrix.solution }}.slnx -c ${{ matrix.config }} + run: dotnet build src/Nethermind/${{ matrix.solution }}.slnx -c ${{ matrix.config }} --no-restore diff --git a/.github/workflows/build-tools.yml b/.github/workflows/build-tools.yml index 5228630fbd55..4369e74a07b4 100644 --- a/.github/workflows/build-tools.yml +++ b/.github/workflows/build-tools.yml @@ -21,15 +21,19 @@ jobs: - Evm/Evm.slnx - HiveCompare/HiveCompare.slnx - HiveConsensusWorkflowGenerator/HiveConsensusWorkflowGenerator.slnx + - JitAsm/JitAsm.slnx - Kute/Kute.slnx # - SchemaGenerator/SchemaGenerator.slnx - SendBlobs/SendBlobs.slnx + - StatelessExecution/StatelessExecution.slnx - TxParser/TxParser.slnx steps: - name: Check out repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 + - name: Set up .NET uses: actions/setup-dotnet@v5 + - name: Build ${{ matrix.project }} working-directory: tools run: dotnet build ./${{ matrix.project }} -c ${{ matrix.config }} diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml index 4675b71a140e..79f85caa87b6 100644 --- a/.github/workflows/bump-version.yml +++ b/.github/workflows/bump-version.yml @@ -17,7 +17,9 @@ jobs: steps: - name: Check out repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 + with: + ref: master - name: Bump up the version id: version @@ -56,4 +58,5 @@ jobs: gh pr create -B master -H $head_branch \ -t "Bump up the version to ${{ steps.version.outputs.new_version }}-unstable" \ -b "Automated version bump-up from ${{ steps.version.outputs.current_version }} to ${{ steps.version.outputs.new_version }}-unstable" \ - -l "version bump" + -l "version bump" \ + -r "kamilchodola,stdevMac,LukaszRozmej" diff --git a/.github/workflows/ci-surge.yml b/.github/workflows/ci-surge.yml new file mode 100644 index 000000000000..87ff8b8c4688 --- /dev/null +++ b/.github/workflows/ci-surge.yml @@ -0,0 +1,167 @@ +name: "Surge Integration Tests" + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + paths: + - "src/Nethermind/Nethermind.Taiko/**" + - ".github/workflows/ci-surge.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: 45 + env: + SURGE_TAIKO_MONO_DIR: surge-taiko-mono + PACAYA_FORK_TAIKO_MONO_DIR: pacaya-fork-taiko-mono + SHASTA_FORK_TAIKO_MONO_DIR: shasta-fork-taiko-mono + + steps: + - name: Checkout Nethermind + uses: actions/checkout@v6 + + - name: Checkout surge-taiko-mono + uses: actions/checkout@v6 + with: + repository: NethermindEth/surge-taiko-mono + path: ${{ env.SURGE_TAIKO_MONO_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.SURGE_TAIKO_MONO_DIR }}/go.mod + cache: true + + - 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.SURGE_TAIKO_MONO_DIR }}/pnpm-lock.yaml + + - name: Install dependencies + working-directory: ${{ env.SURGE_TAIKO_MONO_DIR }} + shell: bash + run: pnpm install + + - name: Checkout Pacaya fork + uses: actions/checkout@v6 + with: + repository: NethermindEth/surge-taiko-mono + path: >- + ${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, + env.PACAYA_FORK_TAIKO_MONO_DIR) }} + ref: jmadibekov/pacaya-dummy-verifiers-fix + + - name: Checkout Shasta fork + uses: actions/checkout@v6 + with: + repository: NethermindEth/surge-taiko-mono + path: >- + ${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, + env.SHASTA_FORK_TAIKO_MONO_DIR) }} + ref: surge-alethia-protocol-v3.0.0 + + - name: Install pnpm dependencies for pacaya fork taiko-mono + working-directory: >- + ${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_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.SURGE_TAIKO_MONO_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.SURGE_TAIKO_MONO_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 integration tests + working-directory: >- + ${{ env.SURGE_TAIKO_MONO_DIR }}/packages/taiko-client + env: + L2_NODE: l2_nmc + run: >- + SHASTA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, env.SHASTA_FORK_TAIKO_MONO_DIR) }} + PACAYA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, env.PACAYA_FORK_TAIKO_MONO_DIR) }} + make test + + - name: Codecov.io + uses: codecov/codecov-action@v5 + with: + files: ${{ env.SURGE_TAIKO_MONO_DIR }}/packages/taiko-client/coverage.out + flags: taiko-client + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/ci-taiko.yml b/.github/workflows/ci-taiko.yml new file mode 100644 index 000000000000..2d92a38090aa --- /dev/null +++ b/.github/workflows/ci-taiko.yml @@ -0,0 +1,167 @@ +name: "Taiko Integration 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: 45 + env: + TAIKO_MONO_DIR: taiko-mono + PACAYA_FORK_TAIKO_MONO_DIR: pacaya-fork-taiko-mono + SHASTA_FORK_TAIKO_MONO_DIR: shasta-fork-taiko-mono + + steps: + - name: Checkout Nethermind + uses: actions/checkout@v6 + + - name: Checkout taiko-mono + uses: actions/checkout@v6 + with: + repository: taikoxyz/taiko-mono + path: ${{ env.TAIKO_MONO_DIR }} + ref: main + + - 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_DIR }}/go.mod + cache: true + + - 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_DIR }}/pnpm-lock.yaml + + - name: Install dependencies + working-directory: ${{ env.TAIKO_MONO_DIR }} + shell: bash + run: pnpm install + + - name: Checkout Pacaya fork + uses: actions/checkout@v6 + with: + repository: taikoxyz/taiko-mono + path: >- + ${{ format('{0}/{1}', env.TAIKO_MONO_DIR, + env.PACAYA_FORK_TAIKO_MONO_DIR) }} + ref: taiko-alethia-protocol-v2.3.0-devnet-shasta-test + + - name: Checkout Shasta fork + uses: actions/checkout@v6 + with: + repository: taikoxyz/taiko-mono + path: >- + ${{ format('{0}/{1}', env.TAIKO_MONO_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_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_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_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 integration tests + working-directory: >- + ${{ env.TAIKO_MONO_DIR }}/packages/taiko-client + env: + L2_NODE: l2_nmc + run: >- + SHASTA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.TAIKO_MONO_DIR, env.SHASTA_FORK_TAIKO_MONO_DIR) }} + PACAYA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.TAIKO_MONO_DIR, env.PACAYA_FORK_TAIKO_MONO_DIR) }} + make test + + - name: Codecov.io + uses: codecov/codecov-action@v5 + with: + files: ${{ env.TAIKO_MONO_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 07376a433fc3..578e0543042c 100644 --- a/.github/workflows/code-formatting.yml +++ b/.github/workflows/code-formatting.yml @@ -12,8 +12,10 @@ jobs: contents: read steps: - name: Check out repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up .NET 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 a93b7682afdc..82079d5bff2c 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@v5 + 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/dependency-review.yml b/.github/workflows/dependency-review.yml index f145aa646760..31431001d9d0 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Dependency review uses: actions/dependency-review-action@v4 with: diff --git a/.github/workflows/hive-consensus-tests.yml b/.github/workflows/hive-consensus-tests.yml index ed864dda9e9f..65de1725577d 100644 --- a/.github/workflows/hive-consensus-tests.yml +++ b/.github/workflows/hive-consensus-tests.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ github.ref }} @@ -117,7 +117,7 @@ jobs: needs: [create_docker_image] steps: - name: Check out Nethermind repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: path: nethermind submodules: "recursive" @@ -146,7 +146,7 @@ jobs: echo "PARALLELISM=${{ github.event.inputs.parallelism || '8' }}" >> $GITHUB_ENV - name: Check out Nethermind repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: path: nethermind @@ -161,7 +161,7 @@ jobs: go-version: '>=1.17.0' - name: Check out Hive repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: repository: ethereum/hive ref: master diff --git a/.github/workflows/hive-tests.yml b/.github/workflows/hive-tests.yml index 95bc4ff60d2b..d1bb92f9ec34 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: @@ -29,6 +28,15 @@ on: default: '3' type: choice options: ['0', '1', '2', '3', '4', '5'] + parallelism: + description: Parallelism + required: true + default: 4 + type: number + buildarg: + description: Docker build arguments + required: false + type: string hive-repo: description: Hive repo required: true @@ -39,19 +47,18 @@ on: required: false default: master type: string - buildarg: - description: Simulator build argument - required: false - type: string jobs: test: name: Build and run tests runs-on: ubuntu-latest 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: @@ -59,15 +66,19 @@ jobs: run: | echo "LIMIT=${{ github.event.inputs.limit || '' }}" >> $GITHUB_ENV echo "LOG_LEVEL=${{ github.event.inputs.log-level || '3' }}" >> $GITHUB_ENV + echo "PARALLELISM=${{ github.event.inputs.parallelism || '4' }}" >> $GITHUB_ENV echo "HIVE_REPO=${{ github.event.inputs.hive-repo || 'ethereum/hive' }}" >> $GITHUB_ENV echo "HIVE_BRANCH=${{ github.event.inputs.hive-branch || 'master' }}" >> $GITHUB_ENV echo "BUILDARG=${{ github.event.inputs.buildarg || '' }}" >> $GITHUB_ENV + - name: Check out Nethermind repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: path: nethermind + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Build Docker image uses: docker/build-push-action@v6 env: @@ -77,34 +88,44 @@ jobs: file: nethermind/Dockerfile tags: nethermind:test-${{ github.sha }} outputs: type=docker,dest=/tmp/image.tar + - name: Check out Hive repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: repository: ${{ env.HIVE_REPO }} ref: ${{ env.HIVE_BRANCH }} path: hive + - name: Patch Hive Dockerfile run: sed -i 's#FROM $baseimage:$tag#FROM nethermind:test-${{ github.sha }}#g' hive/clients/nethermind/Dockerfile + - name: Build Hive working-directory: hive run: go build . + - name: Load Docker image run: docker load --input /tmp/image.tar + - name: Run Hive continue-on-error: true working-directory: hive run: | - HIVE_ARGS="--client nethermind --sim ${{ matrix.test-suite }} --sim.loglevel $LOG_LEVEL" - [[ -n "$LIMIT" ]] && HIVE_ARGS="$HIVE_ARGS --sim.limit $LIMIT" - [[ -n "$BUILDARG" ]] && HIVE_ARGS="$HIVE_ARGS --sim.buildarg \"$BUILDARG\"" + HIVE_ARGS="--client nethermind + --sim ${{ matrix.test-suite }} + --sim.loglevel $LOG_LEVEL + --sim.parallelism $PARALLELISM" + [[ -n "$LIMIT" ]] && HIVE_ARGS+=" --sim.limit $LIMIT" + [[ -n "$BUILDARG" ]] && HIVE_ARGS+=" --sim.buildarg \"$BUILDARG\"" ./hive $HIVE_ARGS + - name: Upload results uses: actions/upload-artifact@v4 with: name: results-${{ strategy.job-index }}-${{ github.run_number }}-${{ github.run_attempt }} path: hive/workspace retention-days: 7 + - name: Print results run: | - rm hive/workspace/logs/hive.json + rm -f hive/workspace/logs/hive.json nethermind/scripts/hive-results.sh "hive/workspace/logs/*.json" diff --git a/.github/workflows/nethermind-tests.yml b/.github/workflows/nethermind-tests.yml index 23df491d526c..6a8d009b0a14 100644 --- a/.github/workflows/nethermind-tests.yml +++ b/.github/workflows/nethermind-tests.yml @@ -23,7 +23,7 @@ env: jobs: tests: - name: Run ${{ matrix.project }} + name: Run ${{ matrix.project }}${{ matrix.chunk && format(' ({0})', matrix.chunk) || '' }} runs-on: ubuntu-latest continue-on-error: true outputs: @@ -33,21 +33,19 @@ jobs: project: - Ethereum.Abi.Test - Ethereum.Basic.Test - - Ethereum.Blockchain.Block.Legacy.Test - Ethereum.Blockchain.Block.Test - - Ethereum.Blockchain.Legacy.Test - Ethereum.Blockchain.Pyspec.Test - - Ethereum.Blockchain.Test - Ethereum.Difficulty.Test - Ethereum.HexPrefix.Test - Ethereum.KeyAddress.Test - Ethereum.KeyStore.Test + - Ethereum.Legacy.Blockchain.Block.Test + - Ethereum.Legacy.Transition.Test + - Ethereum.Legacy.VM.Test - Ethereum.PoW.Test - Ethereum.Rlp.Test - Ethereum.Transaction.Test - - Ethereum.Transition.Test - Ethereum.Trie.Test - - Ethereum.VM.Test - Nethermind.Abi.Test - Nethermind.Api.Test - Nethermind.AuRa.Test @@ -57,6 +55,7 @@ jobs: - Nethermind.Consensus.Test - Nethermind.Core.Test - Nethermind.Db.Test + - Nethermind.Era1.Test - Nethermind.Ethash.Test - Nethermind.EthStats.Test - Nethermind.Evm.Test @@ -78,7 +77,6 @@ jobs: - Nethermind.Network.Enr.Test - Nethermind.Network.Test - Nethermind.Optimism.Test - # - Nethermind.Overseer.Test - Nethermind.Runner.Test - Nethermind.Serialization.Ssz.Test - Nethermind.Shutter.Test @@ -92,48 +90,67 @@ jobs: - Nethermind.TxPool.Test - Nethermind.Wallet.Test - Nethermind.Xdc.Test + chunk: [''] + include: + - project: Ethereum.Legacy.Blockchain.Test + chunk: 1of4 + - project: Ethereum.Legacy.Blockchain.Test + chunk: 2of4 + - project: Ethereum.Legacy.Blockchain.Test + chunk: 3of4 + - project: Ethereum.Legacy.Blockchain.Test + chunk: 4of4 steps: - - name: Check out repository - uses: actions/checkout@v5 - with: - submodules: ${{ startsWith(matrix.project, 'Ethereum.') && 'recursive' || 'false' }} - - name: Set up .NET - uses: actions/setup-dotnet@v5 - - name: ${{ matrix.project }} - id: test - run: | - dotnet run --project src/Nethermind/${{ matrix.project }} -c release \ - ${{ env.COLLECT_COVERAGE == 'true' && '--coverage --coverage-output-format cobertura --coverage-settings $GITHUB_WORKSPACE/src/Nethermind/codecoverage.config' || '' }} - - name: Save test outcome - if: success() || failure() - run: echo "${{ steps.test.outcome == 'success' }}," >> test.outcome - - name: Upload test outcome - if: success() || failure() - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.project }}-outcome - path: test.outcome - retention-days: 1 - - name: Upload coverage report - if: env.COLLECT_COVERAGE == 'true' - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.project }}-coverage - path: src/Nethermind/artifacts/bin/${{ matrix.project }}/release/TestResults/*.cobertura.xml - retention-days: 1 + - name: Check out repository + uses: actions/checkout@v6 + with: + submodules: ${{ startsWith(matrix.project, 'Ethereum.') && 'recursive' || 'false' }} + + - name: Set up .NET + uses: actions/setup-dotnet@v5 + + - name: ${{ matrix.project }} + id: test + working-directory: src/Nethermind/${{ matrix.project }} + env: + TEST_CHUNK: ${{ matrix.chunk }} + run: | + dotnet test --project ${{ matrix.project }}.csproj -c release \ + ${{ env.COLLECT_COVERAGE == 'true' && '--coverage --coverage-output-format cobertura --coverage-settings $GITHUB_WORKSPACE/src/Nethermind/codecoverage.config' || '' }} + + - name: Save test outcome + if: success() || failure() + run: echo "${{ steps.test.outcome == 'success' }}," >> test.outcome + + - name: Upload test outcome + if: success() || failure() + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.project }}${{ matrix.chunk && format('-{0}', matrix.chunk) || '' }}-outcome + path: test.outcome + retention-days: 1 + + - name: Upload coverage report + if: env.COLLECT_COVERAGE == 'true' + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.project }}${{ matrix.chunk && format('-{0}', matrix.chunk) || '' }}-coverage + path: src/Nethermind/artifacts/bin/${{ matrix.project }}/release/TestResults/*.cobertura.xml + retention-days: 1 tests-summary: name: Tests summary needs: tests runs-on: ubuntu-latest steps: - - name: Download test outcomes - uses: actions/download-artifact@v4 - - name: Ensure all tests passed - run: | - data=$(cat **/test.outcome) && data=${data%?} - passed=$(echo "[$data]" | jq -r 'all') - [ $passed = 'true' ] && exit 0 || exit 1 + - name: Download test outcomes + uses: actions/download-artifact@v4 + + - name: Ensure all tests passed + run: | + data=$(cat **/test.outcome) && data=${data%?} + passed=$(echo "[$data]" | jq -r 'all') + [[ "$passed" == "true" ]] && exit 0 || exit 1 codecov-upload: name: Upload Codecov reports @@ -146,6 +163,7 @@ jobs: with: path: .coverage pattern: '*-coverage' + - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 with: diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml new file mode 100644 index 000000000000..9ded94bebe46 --- /dev/null +++ b/.github/workflows/pr-labeler.yml @@ -0,0 +1,201 @@ +name: PR labeler + +on: + pull_request_target: + types: [opened, edited, ready_for_review, synchronize] + +jobs: + label: + name: Label PR by type + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Apply labels from PR template checkboxes + uses: actions/github-script@v7 + with: + script: | + // --- Checkbox rules --- + const checkboxToLabel = { + 'Bugfix (a non-breaking change that fixes an issue)': 'bug fix + reliability', + 'New feature (a non-breaking change that adds functionality)': 'new feature', + 'Breaking change (a change that causes existing functionality not to work as expected)': 'BREAKING', + 'Optimization': 'performance is good', + 'Refactoring': 'refactoring', + 'Documentation update': 'docs', + 'Build-related changes': 'build changes', + }; + + // --- Title prefix rules (conventional commits) --- + const prefixToLabel = { + 'fix': 'bug fix + reliability', + 'feat': 'new feature', + 'perf': 'performance is good', + 'refactor': 'refactoring', + 'chore': 'minor', + 'docs': 'docs', + 'test': 'test', + 'ci': 'build changes', + 'build': 'build changes', + }; + + // --- Path rules --- + const pathToLabel = [ + { pattern: 'src/Nethermind/Nethermind.Optimism', label: 'optimism' }, + { pattern: 'src/Nethermind/Nethermind.Taiko', label: 'taiko' }, + { pattern: 'src/Nethermind/Nethermind.Xdc', label: 'XDC' }, + { pattern: 'src/Nethermind/Nethermind.Network', label: 'network' }, + { pattern: 'src/Nethermind/Nethermind.Evm', label: 'evm' }, + { pattern: 'src/Nethermind/Nethermind.Trie', label: 'trie' }, + { pattern: 'src/Nethermind/Nethermind.State', label: 'state+storage' }, + { pattern: 'src/Nethermind/Nethermind.Synchronization', label: 'sync' }, + { pattern: 'src/Nethermind/Nethermind.JsonRpc', label: 'rpc' }, + { pattern: 'src/Nethermind/Nethermind.Db.Rocks', label: 'rocksdb' }, + { pattern: 'src/Nethermind/Nethermind.Db', label: 'database' }, + { pattern: 'src/Nethermind/Nethermind.Init/Modules/DbModule.cs', label: 'database' }, + { pattern: 'src/Nethermind/Nethermind.Runner/configs', label: 'configuration' }, + { pattern: 'src/Nethermind/Nethermind.Config', label: 'configuration' }, + { pattern: 'README.md', label: 'docs' }, + { pattern: 'AGENTS.md', label: 'agentic 🤖' }, + { pattern: '.github/', label: 'devops' }, + { pattern: 'Directory.Packages.props', label: 'dependencies' }, + { pattern: 'tools/', label: 'tools' }, + ]; + + // test label: applied when ALL changed files are in a .Test project + const testOnlyLabel = 'test'; + + const checkboxLabels = new Set(Object.values(checkboxToLabel)); + const prefixLabels = new Set(Object.values(prefixToLabel)); + const pathLabels = new Set(pathToLabel.map(r => r.label)); + const managedLabels = new Set([...checkboxLabels, ...prefixLabels, ...pathLabels, testOnlyLabel, 'cleanup', 'snap sync']); + + const body = context.payload.pull_request.body || ''; + const title = context.payload.pull_request.title || ''; + + const desiredLabels = new Set(); + + // Evaluate checkboxes + for (const [text, label] of Object.entries(checkboxToLabel)) { + const escaped = text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + if (new RegExp(`-\\s*\\[\\s*[xX]\\s*\\]\\s*${escaped}`).test(body)) { + desiredLabels.add(label); + } + } + + // Evaluate "Other" checkbox with keyword matching + const otherMatch = body.match(/-\s*\[\s*[xX]\s*\]\s*Other:\s*(.+)/); + if (otherMatch) { + const otherText = otherMatch[1].toLowerCase(); + if (/\btest/.test(otherText)) desiredLabels.add('test'); + if (/\btool/.test(otherText)) desiredLabels.add('tools'); + if (/\bagent/.test(otherText)) desiredLabels.add('agentic 🤖'); + if (/\bdoc/.test(otherText)) desiredLabels.add('docs'); + } + + // Evaluate title prefix: supports "fix:", "fix(scope):", "(fix)", "[fix]" + const prefixMatch = title.match(/^[(\[]?(\w+)[)\]]?[\s(:/]/) + if (prefixMatch) { + const prefix = prefixMatch[1].toLowerCase(); + if (prefixToLabel[prefix]) { + desiredLabels.add(prefixToLabel[prefix]); + } + } + + // Evaluate title for EIP mentions (eip-1234, EIP 1234, eip1234, etc.) + if (/eip[-\s]?\d+/i.test(title)) { + desiredLabels.add('eip'); + } + + // Evaluate title for optimization keyword + if (/\boptimiz/i.test(title)) { + desiredLabels.add('performance is good'); + } + + // Evaluate changed file paths + const files = await github.paginate(github.rest.pulls.listFiles, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + per_page: 100, + }); + + for (const { pattern, label } of pathToLabel) { + if (files.some(f => f.filename.startsWith(pattern))) { + desiredLabels.add(label); + } + } + + // SnapSync in path + if (files.some(f => /SnapSync/i.test(f.filename))) { + desiredLabels.add('snap sync'); + } + + // Dockerfile changes + if (files.some(f => /(?:^|\/)[Dd]ockerfile/.test(f.filename))) { + desiredLabels.add('devops'); + } + + // Chain config files with integration keywords + const chainKeywordToLabel = { + 'taiko': 'taiko', + 'optimism': 'optimism', + 'op-': 'optimism', + 'xdc': 'XDC', + }; + for (const f of files) { + if (/^src\/Nethermind\/Chains\//.test(f.filename)) { + const name = f.filename.toLowerCase(); + for (const [keyword, label] of Object.entries(chainKeywordToLabel)) { + if (name.includes(keyword)) { + desiredLabels.add(label); + } + } + } + } + + // Apply test label when all changed files are in Test projects + if (files.length > 0 && files.every(f => /\.Test[s]?\//.test(f.filename))) { + desiredLabels.add(testOnlyLabel); + } + + // Apply cleanup label when PR only removes code + if (files.length > 0 && files.every(f => f.additions === 0 && f.deletions > 0)) { + desiredLabels.add('cleanup'); + } + + // Diff against current labels + const currentLabels = new Set( + context.payload.pull_request.labels.map(l => l.name) + ); + + const toAdd = [...desiredLabels].filter(l => !currentLabels.has(l)); + const toRemove = [...currentLabels].filter(l => managedLabels.has(l) && !desiredLabels.has(l)); + + if (toAdd.length > 0) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: toAdd, + }); + core.info(`Added labels: ${toAdd.join(', ')}`); + } + + for (const label of toRemove) { + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + name: label, + }); + core.info(`Removed label: ${label}`); + } catch (e) { + if (e.status !== 404) throw e; + } + } + + if (toAdd.length === 0 && toRemove.length === 0) { + core.info('No label changes needed'); + } diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 8aefc5717928..abc6eeea77b6 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -2,7 +2,7 @@ name: Publish Docker image on: push: - branches: [master, release/*, paprika] + branches: [master, release/*, performance] paths: [src/Nethermind/**] workflow_dispatch: @@ -30,32 +30,21 @@ jobs: name: Publish to Docker Hub runs-on: ubuntu-latest steps: - - name: Check out repository - uses: actions/checkout@v5 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Log in to Docker Hub - uses: docker/login-action@v3 + - name: Free up disk space + uses: jlumbroso/free-disk-space@v1.3.1 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - name: Build and push image to Docker Hub (staging) - run: | - branch=$(echo "${{ github.ref }}" | sed -e "s/refs\/heads\///g") - tag=$(echo "${{ github.event.inputs.tag || '$branch' }}" | sed 's/\//-/g') # replace '/' with '-' - image_name=nethermindeth/${{ github.event.inputs.image-name || 'nethermind' }} - build_timestamp=$(date '+%s') - - echo "Building image with tag $tag" + large-packages: false + tool-cache: false + + - name: Check out repository + uses: actions/checkout@v6 - docker buildx build --platform=linux/amd64,linux/arm64 \ - -f ${{ github.event.inputs.dockerfile || 'Dockerfile' }} \ - -t "$image_name:$tag" \ - ${{ endsWith(github.ref, '/master') && github.event_name == 'push' && '-t $image_name:master-${GITHUB_SHA:0:7}' || '' }} \ - --build-arg BUILD_CONFIG=${{ github.event.inputs.build-config || 'release' }} \ - --build-arg BUILD_TIMESTAMP=$build_timestamp \ - --build-arg CI=$CI \ - --build-arg COMMIT_HASH=$GITHUB_SHA \ - . --push + - name: Publish Docker image + uses: ./.github/actions/publish-docker + with: + image-name: ${{ github.event.inputs.image-name || 'nethermind' }} + tag: ${{ github.event.inputs.tag || '' }} + dockerfile: ${{ github.event.inputs.dockerfile || 'Dockerfile' }} + build-config: ${{ github.event.inputs.build-config || 'release' }} + docker-hub-username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker-hub-password: ${{ secrets.DOCKER_HUB_PASSWORD }} diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml deleted file mode 100644 index 9915d898779f..000000000000 --- 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@v5 - 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 aa2b625f6c6e..30637b0e1d3b 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -10,8 +10,8 @@ jobs: runs-on: ubuntu-latest if: ${{ !github.event.release.prerelease }} steps: - - name: Check out Nethermind repository - uses: actions/checkout@v5 + - name: Check out repository + uses: actions/checkout@v6 with: ref: ${{ github.event.release.tag_name }} - name: Set up GPG @@ -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,16 +103,16 @@ 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@v5 + uses: actions/checkout@v6 with: repository: NethermindEth/homebrew-nethermind token: ${{ steps.gh-app.outputs.token }} @@ -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 43e89013996f..1bb9a671b73d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,10 +2,6 @@ name: Release on: workflow_dispatch: - inputs: - tag: - description: Version tag - required: true permissions: deployments: write @@ -16,70 +12,100 @@ env: PACKAGE_DIR: pkg PACKAGE_RETENTION: 7 PUB_DIR: pub - SCRIPTS_PATH: ${{ github.workspace }}/scripts/build jobs: build: name: Build Nethermind packages runs-on: ubuntu-latest outputs: - build-timestamp: ${{ steps.build.outputs.build-timestamp }} - package-prefix: ${{ steps.archive.outputs.package-prefix }} - prerelease: ${{ steps.build.outputs.prerelease }} + version: ${{ steps.version.outputs.version }} + package-prefix: ${{ steps.version.outputs.package-prefix }} + prerelease: ${{ steps.version.outputs.prerelease }} steps: - name: Check out repository - uses: actions/checkout@v5 - - name: Set up .NET - uses: actions/setup-dotnet@v5 + uses: actions/checkout@v6 + + - name: Detect version + id: version + working-directory: src/Nethermind + run: | + sudo apt-get update && sudo apt-get install xmlstarlet -y --no-install-recommends + version_prefix=$(xmlstarlet sel -t -v "//Project/PropertyGroup/VersionPrefix" Directory.Build.props) + version_suffix=$(xmlstarlet sel -t -v "//Project/PropertyGroup/VersionSuffix" -n Directory.Build.props) + version=$([[ -n "$version_suffix" ]] && echo "$version_prefix-$version_suffix" || echo "$version_prefix") + package_prefix=nethermind-$version-${GITHUB_SHA:0:8} + echo "Detected version $version" + echo "version=$version" >> $GITHUB_OUTPUT + echo "prerelease=$([[ -n "$version_suffix" ]] && echo 'true' || echo 'false')" >> $GITHUB_OUTPUT + echo "package-prefix=$package_prefix" >> $GITHUB_OUTPUT + echo "PACKAGE_PREFIX=$package_prefix" >> $GITHUB_ENV + - name: Build Nethermind.Runner id: build run: | - build_timestamp=$(date '+%s') - echo "build-timestamp=$build_timestamp" >> $GITHUB_OUTPUT - echo "commit-hash=${GITHUB_SHA:0:8}" >> $GITHUB_OUTPUT - echo "prerelease=${{ contains(github.event.inputs.tag, '-') }}" >> $GITHUB_OUTPUT - $SCRIPTS_PATH/build.sh $GITHUB_SHA $build_timestamp + 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=./$PUB_DIR,target=/output nethermind-build + - name: Archive packages - id: archive + run: scripts/build/archive.sh + + - name: Sign packages env: - PACKAGE_PREFIX: nethermind-${{ github.event.inputs.tag }}-${{ steps.build.outputs.commit-hash }} + GPG_PASSPHRASE: ${{ secrets.PPA_GPG_PASSPHRASE }} + GPG_SECRET_KEY: ${{ secrets.PPA_GPG_SECRET_KEY }} + working-directory: ${{ env.PACKAGE_DIR }} run: | - echo "package-prefix=$PACKAGE_PREFIX" >> $GITHUB_OUTPUT - $SCRIPTS_PATH/archive.sh - - name: Upload Nethermind Linux x64 package + 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 with: - name: ${{ steps.archive.outputs.package-prefix }}-linux-x64-package - path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }}/*linux-x64* + name: ${{ env.PACKAGE_PREFIX }}-linux-arm64-package + path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }}/*linux-arm64* retention-days: ${{ env.PACKAGE_RETENTION }} - - name: Upload Nethermind Linux arm64 package + + - name: Upload Nethermind Linux x64 package uses: actions/upload-artifact@v4 with: - name: ${{ steps.archive.outputs.package-prefix }}-linux-arm64-package - path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }}/*linux-arm64* + name: ${{ env.PACKAGE_PREFIX }}-linux-x64-package + path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }}/*linux-x64* retention-days: ${{ env.PACKAGE_RETENTION }} - - name: Upload Nethermind Windows x64 package + + - name: Upload Nethermind macOS arm64 package uses: actions/upload-artifact@v4 with: - name: ${{ steps.archive.outputs.package-prefix }}-windows-x64-package - path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }}/*windows-x64* + name: ${{ env.PACKAGE_PREFIX }}-macos-arm64-package + path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }}/*macos-arm64* retention-days: ${{ env.PACKAGE_RETENTION }} + - name: Upload Nethermind macOS x64 package uses: actions/upload-artifact@v4 with: - name: ${{ steps.archive.outputs.package-prefix }}-macos-x64-package + name: ${{ env.PACKAGE_PREFIX }}-macos-x64-package path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }}/*macos-x64* retention-days: ${{ env.PACKAGE_RETENTION }} - - name: Upload Nethermind macOS arm64 package + + - name: Upload Nethermind Windows x64 package uses: actions/upload-artifact@v4 with: - name: ${{ steps.archive.outputs.package-prefix }}-macos-arm64-package - path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }}/*macos-arm64* + name: ${{ env.PACKAGE_PREFIX }}-windows-x64-package + path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }}/*windows-x64* retention-days: ${{ env.PACKAGE_RETENTION }} + - name: Upload Nethermind reference assemblies uses: actions/upload-artifact@v4 with: - name: ${{ steps.archive.outputs.package-prefix }}-ref-assemblies-package + name: ${{ env.PACKAGE_PREFIX }}-ref-assemblies-package path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }}/*ref-assemblies* retention-days: ${{ env.PACKAGE_RETENTION }} @@ -89,7 +115,7 @@ jobs: needs: build environment: name: Releases - url: https://github.com/NethermindEth/nethermind/releases/tag/${{ github.event.inputs.tag }} + url: https://github.com/NethermindEth/nethermind/releases/tag/${{ needs.build.outputs.version }} steps: - name: Wait for approval run: echo "Waiting for approval..." @@ -100,54 +126,57 @@ jobs: needs: [approval, build] steps: - name: Check out Nethermind repository - uses: actions/checkout@v5 - - name: Authenticate App + uses: actions/checkout@v6 + + - 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: Download artifacts uses: actions/download-artifact@v4 with: path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }} + - name: Publish env: - GIT_TAG: ${{ github.event.inputs.tag }} + 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@v5 - - name: Download artifacts - uses: actions/download-artifact@v4 - with: - path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }} - - 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 - - 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 + if (( !publish )); then + echo "Drafting release $GIT_TAG" + relnotes=$(cat <<'EOF' + # Release notes + + ### [CONTENT PLACEHOLDER] + + #### Build signatures + + 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 @@ -155,34 +184,38 @@ jobs: needs: [approval, build] steps: - name: Check out Nethermind repository - uses: actions/checkout@v5 - - name: Authenticate App + uses: actions/checkout@v6 + + - 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: Set up QEMU uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Log in to Docker Hub uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_PASSWORD }} + - name: Build and push image to Docker Hub run: | image=nethermind/nethermind for suffix in "" ".chiseled"; do - if [[ $suffix == ".chiseled" ]]; then tag_suffix="-chiseled"; else tag_suffix=""; fi - - docker buildx build --platform=linux/amd64,linux/arm64 -f Dockerfile$suffix \ + 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:${{ github.event.inputs.tag }}$tag_suffix" \ - --build-arg BUILD_TIMESTAMP=${{ needs.build.outputs.build-timestamp }} \ + -t "$image:${{ needs.build.outputs.version }}$tag_suffix" \ --build-arg CI=$CI \ --build-arg COMMIT_HASH=$GITHUB_SHA \ - . --push + --build-arg SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) \ + --push done diff --git a/.github/workflows/rpc-comparison.yml b/.github/workflows/rpc-comparison.yml index 158ac757ca72..4c2ce642852c 100644 --- a/.github/workflows/rpc-comparison.yml +++ b/.github/workflows/rpc-comparison.yml @@ -183,135 +183,252 @@ jobs: if: always() && needs.aggregate_rpcs.result == 'success' timeout-minutes: 1440 steps: - - uses: actions/checkout@v5 - + - uses: actions/checkout@v6 + - name: Wait for the nodes to sync timeout-minutes: 1440 env: ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} - run: | - # Assuming rpc_urls_str is a comma-separated string of URLs + shell: bash + run: | + set -o pipefail + + # ------------------------------------------------------------ + # Parse aggregated RPCs from previous job output + # Format: "label::http://ip:port,label::http://ip:port" + # ------------------------------------------------------------ rpc_urls_str="${{ needs.aggregate_rpcs.outputs.rpc_urls }}" + if [[ -z "$rpc_urls_str" ]]; then + echo "No RPC URLs provided in needs.aggregate_rpcs.outputs.rpc_urls" + exit 1 + fi + IFS=',' read -r -a rpc_urls_array <<< "$rpc_urls_str" - - # Loop through the array and strip the branch prefix + processed_rpc_urls=() for url_entry in "${rpc_urls_array[@]}"; do - processed_url="${url_entry#*::}" # Remove everything up to and including "::" + processed_url="${url_entry#*::}" # strip 'label::' processed_rpc_urls+=("$processed_url") - done - - check_eth_syncing() { - rpc_url=$1 - # Loop until the "eth_syncing" response is "false" + done + + if [[ ${#processed_rpc_urls[@]} -eq 0 ]]; then + echo "Parsed RPC list is empty after processing." + exit 1 + fi + + # ------------------------------------------------------------ + # Helper: guarded curl POST with timeouts/retries and JSON header + # ------------------------------------------------------------ + rpc_post() { + local url="$1" + local payload="$2" + # -sS: silent but show errors; -m 10: overall timeout; --connect-timeout 5 + # --retry 5 with --retry-all-errors/--retry-connrefused to handle brief restarts + curl -sS \ + -m 10 --connect-timeout 5 \ + --retry 5 --retry-all-errors --retry-connrefused \ + -H 'Content-Type: application/json' \ + -X POST --data "$payload" "$url" + } + + # ------------------------------------------------------------ + # Function: wait until debug_getSyncStage shows SnapSync or StateNodes + # (Option A guard on curl + extra hardening) + # ------------------------------------------------------------ + check_sync_stage() { + local rpc_url="$1" + echo "Waiting for SnapSync/StateNodes at $rpc_url ..." while true; do - response=$(curl -s -X POST --data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' "$rpc_url") - # Check if the response contains "false" - if echo "$response" | jq -e '.result == false'; then - echo "Node at $rpc_url synced." + response="$(rpc_post "$rpc_url" '{"jsonrpc":"2.0","method":"debug_getSyncStage","params":[],"id":0}')" \ + || { echo "RPC unreachable at $rpc_url; retrying in 60s"; sleep 60; continue; } + + # Ensure we got JSON we can parse + if ! echo "$response" | jq -e . >/dev/null 2>&1; then + echo "Invalid JSON from $rpc_url (debug_getSyncStage): $response" + echo "Retrying in 60s..." + sleep 60 + continue + fi + + current_stage="$(echo "$response" | jq -r '.result.currentStage // empty')" + if [[ -z "$current_stage" || "$current_stage" == "null" ]]; then + echo "Current stage not available yet from $rpc_url; retrying in 60s..." + sleep 60 + continue + fi + + if echo "$current_stage" | grep -Eq 'SnapSync|StateNodes'; then + echo "SnapSync/StateNodes stage reached at $rpc_url (currentStage: $current_stage)." break else - echo "Still waiting for node to be synced at RPC: $rpc_url." + echo "Current stage at $rpc_url: $current_stage (not SnapSync/StateNodes). Retrying in 60s..." sleep 60 fi done } - check_sync_stage() { - rpc_url=$1 + # ------------------------------------------------------------ + # Function: wait until eth_syncing == false (fully synced) + # (Option A guard on curl + extra hardening) + # ------------------------------------------------------------ + check_eth_syncing() { + local rpc_url="$1" + echo "Waiting for node to report eth_syncing=false at $rpc_url ..." while true; do - response=$(curl -s -X POST --data '{"jsonrpc":"2.0","method":"debug_getSyncStage","params":[],"id":0}' "$rpc_url") - - # Check if the currentStage contains "SnapSync" or "StateNodes" - if echo "$response" | jq -e '.result.currentStage | test("SnapSync|StateNodes")'; then - echo "SnapSync or StateNodes stage reached at RPC: $rpc_url. Node is syncing." + response="$(rpc_post "$rpc_url" '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}')" \ + || { echo "RPC unreachable at $rpc_url; retrying in 60s"; sleep 60; continue; } + + if ! echo "$response" | jq -e . >/dev/null 2>&1; then + echo "Invalid JSON from $rpc_url (eth_syncing): $response" + echo "Retrying in 60s..." + sleep 60 + continue + fi + + if echo "$response" | jq -e '.result == false' >/dev/null 2>&1; then + echo "Node at $rpc_url reports synced (eth_syncing=false)." break else - current_stage=$(echo "$response" | jq -r '.result.currentStage') - echo "Current stage at RPC: $rpc_url is not SnapSync or StateNodes. Current stage: $current_stage" + # When syncing, .result is an object; show a short summary if available + syncing_summary="$(echo "$response" | jq -c '.result | {startingBlock, currentBlock, highestBlock} // {}' 2>/dev/null)" + echo "Still syncing at $rpc_url. Details: ${syncing_summary:-unknown}. Retrying in 60s..." sleep 60 fi done } + # ------------------------------------------------------------ + # Function: detect a brief downtime/restart and then confirm JSON-RPC is back + # (already guarded; add headers/timeouts) + # ------------------------------------------------------------ check_jsonrpc_responding() { - rpc_url=$1 - echo "Checking if JsonRPC is responding at $rpc_url." - - # Flag to ensure at least one failure has been logged - has_failed_before=false - + local rpc_url="$1" + echo "Checking if JsonRPC restarts and responds at $rpc_url." + + local has_failed_before=false + while true; do - response=$(curl -s -X POST --data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' -m 10 "$rpc_url" || echo "curl_failed") - - if [[ "$response" == "curl_failed" ]]; then - echo "JsonRPC not responding at $rpc_url, node might be stopped for migration." + response="$(curl -sS \ + -m 10 --connect-timeout 5 \ + --retry 3 --retry-all-errors --retry-connrefused \ + -H 'Content-Type: application/json' \ + -X POST --data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' \ + "$rpc_url" 2>/dev/null)" || response="curl_failed" + + if [[ "$response" == "curl_failed" || -z "$response" ]]; then + echo "JsonRPC not responding at $rpc_url (expected during migration/restart)." has_failed_before=true else - if [ "$has_failed_before" = true ]; then - echo "JsonRPC responded at $rpc_url, node might have restarted." + if [[ "$has_failed_before" == true ]]; then + echo "JsonRPC responded at $rpc_url after a failure; node likely restarted." break else - # Log the successful response but do not break; wait for a possible failure - echo "JsonRPC responded at $rpc_url, but waiting for at least one failure before confirming." + echo "JsonRPC currently responding at $rpc_url; waiting to observe at least one failure before confirming restart." fi - fi - sleep 60 # Check every 60 seconds + fi + sleep 60 done } + # ------------------------------------------------------------ + # Function: compare node head vs Etherscan (optional) + # (extra guards for null/empty and missing API key) + # ------------------------------------------------------------ check_chain_head() { - rpc_url=$1 - etherscan_api_key="$ETHERSCAN_API_KEY" - etherscan_api_url="https://api.etherscan.io/api" - - while true; do - # Fetch current chain head from node - node_head_hex=$(curl -s -X POST --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' "$rpc_url" | jq -r '.result') - node_head_decimal=$(echo $((16#${node_head_hex#0x}))) - echo "Current chain head at $rpc_url: $node_head_decimal" - - # Fetch current chain head from Etherscan API - etherscan_response=$(curl -s "$etherscan_api_url?module=proxy&action=eth_blockNumber&apikey=$etherscan_api_key") - etherscan_head_hex=$(echo "$etherscan_response" | jq -r '.result') - etherscan_head_decimal=$(echo $((16#${etherscan_head_hex#0x}))) - echo "Current chain head at Etherscan: $etherscan_head_decimal" - - # Compare heads - if [ "$node_head_decimal" -ge "$etherscan_head_decimal" ]; then - echo "Node at $rpc_url has caught up with the chain." - break - else - echo "Node at $rpc_url has not caught up with the chain. Node head: $node_head_decimal, Etherscan head: $etherscan_head_decimal" - echo "Waiting for 30 seconds before rechecking..." - sleep 30 - fi - done + local rpc_url="$1" + local etherscan_api_key="${ETHERSCAN_API_KEY:-}" + local etherscan_api_url="https://api.etherscan.io/api" + + if [[ -z "$etherscan_api_key" ]]; then + echo "ETHERSCAN_API_KEY not provided; skipping head comparison." + return 0 + fi + + echo "Comparing chain head of $rpc_url vs Etherscan ..." + + while true; do + # Node head + node_resp="$(rpc_post "$rpc_url" '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}')" \ + || { echo "RPC unreachable at $rpc_url; retrying in 30s"; sleep 30; continue; } + + node_head_hex="$(echo "$node_resp" | jq -r '.result // empty' 2>/dev/null)" + if [[ -z "$node_head_hex" || "$node_head_hex" == "null" ]]; then + echo "No blockNumber yet from $rpc_url; retrying in 30s..." + sleep 30 + continue + fi + # hex -> dec (strip 0x) + node_head_decimal=$((16#${node_head_hex#0x})) + echo "Current chain head at $rpc_url: $node_head_decimal" + + # Etherscan head + etherscan_response="$(curl -sS -m 10 --connect-timeout 5 \ + --retry 5 --retry-all-errors --retry-connrefused \ + "$etherscan_api_url?module=proxy&action=eth_blockNumber&apikey=$etherscan_api_key")" \ + || { echo "Etherscan unreachable; retrying in 30s"; sleep 30; continue; } + + if ! echo "$etherscan_response" | jq -e . >/dev/null 2>&1; then + echo "Invalid JSON from Etherscan: $etherscan_response" + echo "Retrying in 30s..." + sleep 30 + continue + fi + + etherscan_head_hex="$(echo "$etherscan_response" | jq -r '.result // empty')" + if [[ -z "$etherscan_head_hex" || "$etherscan_head_hex" == "null" ]]; then + echo "Etherscan did not return a blockNumber; retrying in 30s..." + sleep 30 + continue + fi + etherscan_head_decimal=$((16#${etherscan_head_hex#0x})) + echo "Current chain head at Etherscan: $etherscan_head_decimal" + + # Compare heads + if (( node_head_decimal >= etherscan_head_decimal )); then + echo "Node at $rpc_url has caught up with the chain." + break + else + echo "Node at $rpc_url has not caught up yet. Node: $node_head_decimal, Etherscan: $etherscan_head_decimal" + echo "Waiting for 30 seconds before rechecking..." + sleep 30 + fi + done } - # Just an RPC warmup - to make sure no faulty info reached a check + # ----------------------------- + # Warmup (avoid transient boot noise) + # ----------------------------- + echo "Initial warmup sleep (60s)..." sleep 60 - # Check if nodes progressed to SnapSync + # ----------------------------- + # Stage check: SnapSync/StateNodes + # ----------------------------- for url in "${processed_rpc_urls[@]}"; do check_sync_stage "$url" done - # Check if nodes are synced + # ----------------------------- + # Sync completion (eth_syncing == false) + # ----------------------------- for url in "${processed_rpc_urls[@]}"; do check_eth_syncing "$url" - done + done - if [ "${{ inputs.convert_to_paprika }}" == "true" ]; then - # Waiting for Paprika Import + # ----------------------------- + # Optional: Paprika import wait and head comparison + # ----------------------------- + if [[ "${{ inputs.convert_to_paprika }}" == "true" ]]; then url="${processed_rpc_urls[0]}" check_jsonrpc_responding "$url" check_chain_head "$url" fi - # Extra wait - nodes need to process a few new blocks - nice to have at least 128 of them after StateHealing - # Adding (128 - 32) * 12 seconds (-32 because we always keep 32 blocks to be processed after healing) - echo "Waiting for (128 - 32) blocks to be synced" + # ----------------------------- + # Extra wait for ~96 blocks after StateHealing + # (128 - 32) * 12s = 1152s + # ----------------------------- + echo "Waiting for (128 - 32) Ethereum blocks ≈ 1152 seconds..." sleep 1152 compare: @@ -320,7 +437,7 @@ jobs: needs: [wait_for_node_to_sync, aggregate_rpcs] if: always() && needs.aggregate_rpcs.result == 'success' && needs.wait_for_node_to_sync.result == 'success' steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install flood run: pip install --force-reinstall --no-deps git+https://github.com/kamilchodola/flood.git diff --git a/.github/workflows/run-a-single-node-from-branch.yml b/.github/workflows/run-a-single-node-from-branch.yml index 755afe1cb827..6caf5cca22a1 100644 --- a/.github/workflows/run-a-single-node-from-branch.yml +++ b/.github/workflows/run-a-single-node-from-branch.yml @@ -13,7 +13,6 @@ on: - gnosis - sepolia - chiado - - holesky - hoodi - op-mainnet - op-sepolia @@ -23,10 +22,10 @@ on: - world-mainnet - joc-mainnet - joc-testnet - - linea-mainnet - - linea-sepolia + # - linea-mainnet + # - linea-sepolia - taiko-alethia - - taiko-hekla + - taiko-hoodi - energyweb - volta cl_client: @@ -161,7 +160,7 @@ jobs: base_tag: ${{ steps.set-base-tag.outputs.base_tag }} steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ github.ref }} @@ -259,7 +258,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ github.ref }} diff --git a/.github/workflows/run-expb-reproducible-benchmarks.yml b/.github/workflows/run-expb-reproducible-benchmarks.yml new file mode 100644 index 000000000000..6282e8705c15 --- /dev/null +++ b/.github/workflows/run-expb-reproducible-benchmarks.yml @@ -0,0 +1,775 @@ +name: Run EXPB Reproducible Benchmarks + +on: + workflow_dispatch: + inputs: + expb_repo: + description: execution-payloads-benchmarks repository in owner/repo format + required: false + default: NethermindEth/execution-payloads-benchmarks + expb_branch: + description: execution-payloads-benchmarks branch or tag + required: false + default: main + state_layout: + description: State layout mode + required: true + type: choice + options: + - halfpath + - flat + default: halfpath + payload_set: + description: Payload set mode + required: true + type: choice + options: + - realblocks + - superblocks + default: superblocks + delay_seconds: + description: Value used to replace <> placeholder (integer) + required: false + default: "0" + additional_extra_flags: + description: >- + Optional extra Nethermind flags. Example (single): --Merge.SweepMemory=NoGC. + Example (multiple): --Sync.FastSync=false, --Pruning.CacheMb=12000 + (or provide one flag per line). + required: false + default: "" + rebuild_docker: + description: Rebuild Nethermind Docker image before running benchmarks + required: false + type: boolean + default: true + pull_request: + types: [labeled] + push: + branches: + - master + +permissions: + contents: read + actions: write + issues: write + pull-requests: write + +jobs: + resolve: + if: github.event_name != 'pull_request' || (github.event.action == 'labeled' && github.event.label.name == 'reproducible-benchmark' && github.event.pull_request.head.repo.full_name == github.repository) + runs-on: ubuntu-latest + outputs: + should_run: ${{ steps.resolve.outputs.should_run }} + branch: ${{ steps.resolve.outputs.branch }} + clean_branch: ${{ steps.resolve.outputs.clean_branch }} + should_trigger_publish_docker: ${{ steps.resolve.outputs.should_trigger_publish_docker }} + rebuild_docker: ${{ steps.resolve.outputs.rebuild_docker }} + config_file: ${{ steps.resolve.outputs.config_file }} + image_label: ${{ steps.resolve.outputs.image_label }} + expb_repo: ${{ steps.resolve.outputs.expb_repo }} + expb_branch: ${{ steps.resolve.outputs.expb_branch }} + expb_data_dir: ${{ steps.resolve.outputs.expb_data_dir }} + delay_seconds: ${{ steps.resolve.outputs.delay_seconds }} + additional_extra_flags: ${{ steps.resolve.outputs.additional_extra_flags }} + cleanup_grace_seconds: ${{ steps.resolve.outputs.cleanup_grace_seconds }} + scenario_name: ${{ steps.resolve.outputs.scenario_name }} + steps: + - name: Resolve branch and configuration + id: resolve + shell: bash + env: + EVENT_NAME: ${{ github.event_name }} + PR_LABEL: ${{ github.event.label.name }} + PR_HEAD_BRANCH: ${{ github.event.pull_request.head.ref }} + PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} + CURRENT_REPO: ${{ github.repository }} + PUSH_BRANCH: ${{ github.ref_name }} + DISPATCH_STATE_LAYOUT: ${{ inputs.state_layout }} + DISPATCH_PAYLOAD_SET: ${{ inputs.payload_set }} + DISPATCH_EXPB_REPO: ${{ inputs.expb_repo }} + DISPATCH_EXPB_BRANCH: ${{ inputs.expb_branch }} + DISPATCH_DELAY_SECONDS: ${{ inputs.delay_seconds }} + DISPATCH_ADDITIONAL_EXTRA_FLAGS: ${{ inputs.additional_extra_flags }} + DISPATCH_REBUILD_DOCKER: ${{ inputs.rebuild_docker }} + run: | + set -euo pipefail + + should_run="true" + if [[ "${EVENT_NAME}" == "workflow_dispatch" ]]; then + branch="${PUSH_BRANCH}" + state_layout="${DISPATCH_STATE_LAYOUT}" + payload_set="${DISPATCH_PAYLOAD_SET}" + expb_repo="${DISPATCH_EXPB_REPO}" + expb_branch="${DISPATCH_EXPB_BRANCH}" + expb_data_dir="/mnt/sda/expb-data" + delay_seconds="${DISPATCH_DELAY_SECONDS:-0}" + additional_extra_flags="${DISPATCH_ADDITIONAL_EXTRA_FLAGS:-}" + cleanup_grace_seconds="90" + rebuild_docker="${DISPATCH_REBUILD_DOCKER:-true}" + elif [[ "${EVENT_NAME}" == "pull_request" ]]; then + if [[ "${PR_LABEL}" != "reproducible-benchmark" ]]; then + should_run="false" + fi + if [[ -n "${PR_HEAD_REPO:-}" && "${PR_HEAD_REPO}" != "${CURRENT_REPO}" ]]; then + should_run="false" + fi + branch="${PR_HEAD_BRANCH}" + state_layout="halfpath" + payload_set="superblocks" + expb_repo="NethermindEth/execution-payloads-benchmarks" + expb_branch="main" + expb_data_dir="/mnt/sda/expb-data" + delay_seconds="0" + additional_extra_flags="" + cleanup_grace_seconds="90" + rebuild_docker="true" + else + branch="${PUSH_BRANCH}" + state_layout="halfpath" + payload_set="superblocks" + expb_repo="NethermindEth/execution-payloads-benchmarks" + expb_branch="main" + expb_data_dir="/mnt/sda/expb-data" + delay_seconds="0" + additional_extra_flags="" + cleanup_grace_seconds="90" + rebuild_docker="true" + fi + + branch="${branch#refs/heads/}" + if [[ -z "${branch}" ]]; then + echo "Failed to resolve branch for event '${EVENT_NAME}'." + exit 1 + fi + + if ! [[ "${delay_seconds}" =~ ^-?[0-9]+$ ]]; then + echo "delay_seconds must be an integer, got '${delay_seconds}'." + exit 1 + fi + + if ! [[ "${cleanup_grace_seconds}" =~ ^[0-9]+$ ]]; then + echo "cleanup_grace_seconds must be a non-negative integer, got '${cleanup_grace_seconds}'." + exit 1 + fi + + if [[ "${rebuild_docker}" != "true" && "${rebuild_docker}" != "false" ]]; then + echo "rebuild_docker must be true or false, got '${rebuild_docker}'." + exit 1 + fi + + clean_branch="$(echo "${branch}" | sed 's/[^a-zA-Z0-9._-]/-/g')" + + should_trigger_publish_docker="true" + if [[ "${branch}" == "master" || "${branch}" == "paprika" || "${branch}" == release/* ]]; then + should_trigger_publish_docker="false" + elif [[ "${EVENT_NAME}" == "workflow_dispatch" && "${rebuild_docker}" != "true" ]]; then + should_trigger_publish_docker="false" + fi + + if [[ "${state_layout}" == "flat" && "${payload_set}" == "superblocks" ]]; then + config_file="github-action-compressed-mainnet-flat.yaml" + elif [[ "${state_layout}" == "flat" && "${payload_set}" == "realblocks" ]]; then + config_file="github-action-mainnet-flat.yaml" + elif [[ "${state_layout}" == "halfpath" && "${payload_set}" == "superblocks" ]]; then + config_file="github-action-compressed-mainnet.yaml" + else + config_file="github-action-mainnet.yaml" + fi + + scenario_name="nethermind-${state_layout}-${payload_set}-${clean_branch}-delay${delay_seconds}s" + + { + echo "should_run=${should_run}" + echo "branch=${branch}" + echo "clean_branch=${clean_branch}" + echo "should_trigger_publish_docker=${should_trigger_publish_docker}" + echo "rebuild_docker=${rebuild_docker}" + echo "config_file=${config_file}" + echo "image_label=nethermindeth/nethermind:${clean_branch}" + echo "expb_repo=${expb_repo}" + echo "expb_branch=${expb_branch}" + echo "expb_data_dir=${expb_data_dir}" + echo "delay_seconds=${delay_seconds}" + echo "cleanup_grace_seconds=${cleanup_grace_seconds}" + echo "scenario_name=${scenario_name}" + echo "additional_extra_flags<> "${GITHUB_OUTPUT}" + + prepare-docker: + needs: [resolve] + if: needs.resolve.outputs.should_run == 'true' + runs-on: ubuntu-latest + steps: + - name: Docker image source + if: needs.resolve.outputs.should_trigger_publish_docker != 'true' + run: | + echo "Skipping publish-docker.yml for auto-built branch '${{ needs.resolve.outputs.branch }}'." + echo "Using image '${{ needs.resolve.outputs.image_label }}'." + + - name: Checkout repository + if: needs.resolve.outputs.should_trigger_publish_docker == 'true' + uses: actions/checkout@v6 + + - name: Trigger publish-docker.yml + if: needs.resolve.outputs.should_trigger_publish_docker == 'true' + uses: benc-uk/workflow-dispatch@v1 + with: + workflow: publish-docker.yml + ref: ${{ needs.resolve.outputs.branch }} + token: ${{ github.token }} + inputs: '{ + "image-name": "nethermind", + "tag": "${{ needs.resolve.outputs.clean_branch }}", + "dockerfile": "Dockerfile", + "build-config": "release" + }' + + - name: Wait for publish-docker.yml to complete + if: needs.resolve.outputs.should_trigger_publish_docker == 'true' + env: + GITHUB_TOKEN: ${{ github.token }} + WORKFLOW_ID: publish-docker.yml + MAX_WAIT_MINUTES: "10" + INTERVAL: "10" + TIMEOUT: "120" + ORG_NAME: ${{ github.repository_owner }} + REPO_NAME: ${{ github.event.repository.name }} + NAME_FILTER: Publish Docker image + REF: ${{ needs.resolve.outputs.branch }} + run: | + chmod +x scripts/wait-for-workflow.sh + ./scripts/wait-for-workflow.sh + + benchmark: + needs: [resolve, prepare-docker] + if: needs.resolve.outputs.should_run == 'true' + runs-on: [self-hosted, reproducible-benchmarks] + timeout-minutes: 720 + env: + EXPB_DATA_DIR: ${{ needs.resolve.outputs.expb_data_dir }} + CONFIG_FILE: ${{ needs.resolve.outputs.config_file }} + NETHERMIND_IMAGE: ${{ needs.resolve.outputs.image_label }} + CLEANUP_GRACE_SECONDS: ${{ needs.resolve.outputs.cleanup_grace_seconds }} + steps: + - name: Print resolved inputs + run: | + echo "Event: ${{ github.event_name }}" + echo "Nethermind branch: ${{ needs.resolve.outputs.branch }}" + echo "Docker image: ${NETHERMIND_IMAGE}" + echo "Config file: ${EXPB_DATA_DIR}/${CONFIG_FILE}" + echo "Delay placeholder value: ${{ needs.resolve.outputs.delay_seconds }}" + echo "Rebuild docker requested: ${{ needs.resolve.outputs.rebuild_docker }}" + echo "Publish docker workflow triggered: ${{ needs.resolve.outputs.should_trigger_publish_docker }}" + echo "Cleanup grace period (s): ${CLEANUP_GRACE_SECONDS}" + echo "Scenario name: ${{ needs.resolve.outputs.scenario_name }}" + + - name: Restore cached master metrics + id: restore-master-metrics + if: github.event_name == 'pull_request' + uses: actions/cache/restore@v4 + with: + path: ${{ runner.temp }}/expb-master-metrics-cache + key: expb-master-metrics-v1-${{ github.event.pull_request.base.sha }} + restore-keys: | + expb-master-metrics-v1- + + - name: Ensure EXPB config file exists + shell: bash + run: | + set -euo pipefail + if [[ ! -f "${EXPB_DATA_DIR}/${CONFIG_FILE}" ]]; then + echo "Config file '${EXPB_DATA_DIR}/${CONFIG_FILE}' does not exist." + echo "Available github-action config files in '${EXPB_DATA_DIR}':" + ls -1 "${EXPB_DATA_DIR}"/github-action*mainnet*.yaml || true + exit 1 + fi + + - name: Render benchmark config + id: render-config + shell: bash + env: + SOURCE_CONFIG_FILE: ${{ env.EXPB_DATA_DIR }}/${{ env.CONFIG_FILE }} + RENDERED_CONFIG_FILE: ${{ runner.temp }}/rendered-expb-config.yaml + DOCKER_TAG: ${{ needs.resolve.outputs.clean_branch }} + DELAY_SECONDS: ${{ needs.resolve.outputs.delay_seconds }} + SCENARIO_NAME: ${{ needs.resolve.outputs.scenario_name }} + ADDITIONAL_EXTRA_FLAGS: ${{ needs.resolve.outputs.additional_extra_flags }} + run: | + set -euo pipefail + flags_file="$(mktemp)" + printf '%s' "${ADDITIONAL_EXTRA_FLAGS}" \ + | tr '\r' '\n' \ + | tr ',' '\n' \ + | sed 's/^[[:space:]]*//; s/[[:space:]]*$//' \ + | sed '/^$/d' \ + | awk ' + function emit_pending() { + if (pending != "") { + print pending + pending = "" + } + } + { + line = $0 + + # Convert "--Key Value" to "--Key=Value" to avoid bare value tokens. + if (match(line, /^--[^[:space:]=]+[[:space:]]+/)) { + key = substr(line, 1, RLENGTH) + sub(/[[:space:]]+$/, "", key) + value = substr(line, RLENGTH + 1) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", value) + emit_pending() + print key "=" value + next + } + + if (line ~ /^--/) { + emit_pending() + pending = line + next + } + + if (pending != "") { + print pending "=" line + pending = "" + } else { + print line + } + } + END { + emit_pending() + } + ' \ + > "${flags_file}" + + sed \ + -e "s#<>#${DOCKER_TAG}#g" \ + -e "s#<>#${DELAY_SECONDS}#g" \ + -e "s#^\([[:space:]]*\)nethermind:#\1${SCENARIO_NAME}:#g" \ + "${SOURCE_CONFIG_FILE}" \ + | awk -v flags_file="${flags_file}" ' + BEGIN { + while ((getline flag < flags_file) > 0) { + flags[++flags_count] = flag + } + close(flags_file) + } + { + print + if (flags_count > 0 && $0 ~ /^[[:space:]]*extra_flags:[[:space:]]*$/) { + match($0, /^[[:space:]]*/) + indent = substr($0, RSTART, RLENGTH) + for (i = 1; i <= flags_count; i++) { + print indent " - " flags[i] + } + } + } + ' > "${RENDERED_CONFIG_FILE}" + + echo "rendered_config_file=${RENDERED_CONFIG_FILE}" >> "${GITHUB_OUTPUT}" + + - name: Install or upgrade expb + shell: bash + env: + EXPB_REPO: ${{ needs.resolve.outputs.expb_repo }} + EXPB_BRANCH: ${{ needs.resolve.outputs.expb_branch }} + run: | + set -euo pipefail + + if ! command -v uv >/dev/null 2>&1; then + echo "uv is required on runner but was not found in PATH." + exit 1 + fi + + if [[ "${EXPB_REPO}" == "NethermindEth/execution-payloads-benchmarks" && "${EXPB_BRANCH}" == "main" ]]; then + expb_source="git+https://github.com/NethermindEth/execution-payloads-benchmarks" + elif [[ -n "${EXPB_BRANCH}" ]]; then + expb_source="git+https://github.com/${EXPB_REPO}@${EXPB_BRANCH}" + else + expb_source="git+https://github.com/${EXPB_REPO}" + fi + + echo "Installing expb from ${expb_source}" + uv tool install --force --from "${expb_source}" expb + echo "$(uv tool dir --bin)" >> "${GITHUB_PATH}" + + - name: Run expb scenarios + id: run-expb + continue-on-error: true + shell: bash + working-directory: ${{ env.EXPB_DATA_DIR }} + env: + RAW_RUN_LOG: ${{ runner.temp }}/expb-run.log + run: | + set -euo pipefail + expb_pid="" + : > "${RAW_RUN_LOG}" + + on_terminate() { + echo "Termination signal received. Waiting up to ${CLEANUP_GRACE_SECONDS}s for expb cleanup." + if [[ -n "${expb_pid}" ]] && kill -0 "${expb_pid}" 2>/dev/null; then + kill -TERM "${expb_pid}" 2>/dev/null || true + + remaining="${CLEANUP_GRACE_SECONDS}" + while [[ "${remaining}" -gt 0 ]]; do + if ! kill -0 "${expb_pid}" 2>/dev/null; then + echo "expb exited during cleanup grace period." + break + fi + sleep 1 + remaining=$((remaining - 1)) + done + + if kill -0 "${expb_pid}" 2>/dev/null; then + echo "Cleanup grace period elapsed. Forcing expb shutdown." + kill -KILL "${expb_pid}" 2>/dev/null || true + fi + fi + + if [[ -f "${RAW_RUN_LOG}" ]]; then + cat "${RAW_RUN_LOG}" + fi + exit 143 + } + + trap on_terminate TERM INT + + expb execute-scenarios \ + --config-file "${{ steps.render-config.outputs.rendered_config_file }}" \ + --per-payload-metrics \ + --per-payload-metrics-logs \ + --print-logs \ + > "${RAW_RUN_LOG}" 2>&1 & + expb_pid=$! + + set +e + wait "${expb_pid}" + expb_exit_code=$? + set -e + + cat "${RAW_RUN_LOG}" + exit "${expb_exit_code}" + + - name: Analyze benchmark output + id: analyze + if: always() + shell: bash + env: + RAW_RUN_LOG: ${{ runner.temp }}/expb-run.log + run: | + set -euo pipefail + + clean_log="${RUNNER_TEMP}/expb-run.clean.log" + exception_lines="${RUNNER_TEMP}/expb-exceptions.log" + invalid_block_lines="${RUNNER_TEMP}/expb-invalid-blocks.log" + processing_ms="${RUNNER_TEMP}/expb-processing-ms.txt" + processing_ms_sorted="${RUNNER_TEMP}/expb-processing-ms-sorted.txt" + metrics_file="${RUNNER_TEMP}/expb-metrics.env" + + if [[ ! -f "${RAW_RUN_LOG}" ]]; then + echo "Run output log '${RAW_RUN_LOG}' was not produced." + exit 1 + fi + + sed -E 's/\x1B\[[0-9;?]*[ -/]*[@-~]//g' "${RAW_RUN_LOG}" > "${clean_log}" + + grep -in "Exception" "${clean_log}" > "${exception_lines}" || true + exception_found="false" + if [[ -s "${exception_lines}" ]]; then + exception_found="true" + echo "Found Exception lines in benchmark output:" + head -n 40 "${exception_lines}" + fi + + grep -Ein "invalid[[:space:]_-]*block" "${clean_log}" > "${invalid_block_lines}" || true + invalid_block_found="false" + if [[ -s "${invalid_block_lines}" ]]; then + invalid_block_found="true" + echo "Found invalid block lines in benchmark output:" + head -n 40 "${invalid_block_lines}" + fi + + awk -F'|' ' + /^[[:space:]]*\|[[:space:]]*[0-9]+[[:space:]]*\|[[:space:]]*[0-9]+[[:space:]]*\|[[:space:]]*[0-9]+(\.[0-9]+)?[[:space:]]*\|[[:space:]]*$/ { + value = $4 + gsub(/^[[:space:]]+|[[:space:]]+$/, "", value) + if (value ~ /^[0-9]+(\.[0-9]+)?$/) { + print value + } + } + ' "${clean_log}" > "${processing_ms}" + + count=$(wc -l < "${processing_ms}") + count="${count//[[:space:]]/}" + if [[ "${count}" -eq 0 ]]; then + echo "Could not extract per-payload processing_ms rows from benchmark output." + exit 1 + fi + + sort -n "${processing_ms}" > "${processing_ms_sorted}" + + min=$(head -n 1 "${processing_ms_sorted}") + max=$(tail -n 1 "${processing_ms_sorted}") + avg=$(awk '{sum += $1} END {printf "%.6f", sum / NR}' "${processing_ms_sorted}") + if (( count % 2 == 1 )); then + median_index=$(( (count + 1) / 2 )) + median=$(sed -n "${median_index}p" "${processing_ms_sorted}") + else + lower_index=$(( count / 2 )) + upper_index=$(( lower_index + 1 )) + lower_value=$(sed -n "${lower_index}p" "${processing_ms_sorted}") + upper_value=$(sed -n "${upper_index}p" "${processing_ms_sorted}") + median=$(awk -v a="${lower_value}" -v b="${upper_value}" 'BEGIN {printf "%.6f", (a + b) / 2}') + fi + + p90_index=$(( (90 * count + 99) / 100 )) + p95_index=$(( (95 * count + 99) / 100 )) + p99_index=$(( (99 * count + 99) / 100 )) + + p90=$(sed -n "${p90_index}p" "${processing_ms_sorted}") + p95=$(sed -n "${p95_index}p" "${processing_ms_sorted}") + p99=$(sed -n "${p99_index}p" "${processing_ms_sorted}") + + { + echo "COUNT=${count}" + echo "AVG=${avg}" + echo "MEDIAN=${median}" + echo "P90=${p90}" + echo "P95=${p95}" + echo "P99=${p99}" + echo "MIN=${min}" + echo "MAX=${max}" + } > "${metrics_file}" + + { + echo "exception_found=${exception_found}" + echo "exception_lines_file=${exception_lines}" + echo "invalid_block_found=${invalid_block_found}" + echo "invalid_block_lines_file=${invalid_block_lines}" + echo "metrics_file=${metrics_file}" + echo "count=${count}" + echo "avg=${avg}" + echo "median=${median}" + echo "p90=${p90}" + echo "p95=${p95}" + echo "p99=${p99}" + echo "min=${min}" + echo "max=${max}" + } >> "${GITHUB_OUTPUT}" + + - name: Build PR comparison comment + id: pr-comment + if: always() && github.event_name == 'pull_request' + shell: bash + env: + CURRENT_METRICS_FILE: ${{ steps.analyze.outputs.metrics_file }} + MASTER_METRICS_FILE: ${{ runner.temp }}/expb-master-metrics-cache/master-metrics.env + SCENARIO_NAME: ${{ needs.resolve.outputs.scenario_name }} + EXCEPTION_FOUND: ${{ steps.analyze.outputs.exception_found }} + INVALID_BLOCK_FOUND: ${{ steps.analyze.outputs.invalid_block_found }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + set -euo pipefail + + marker="" + comment_file="${RUNNER_TEMP}/expb-pr-comment.md" + : > "${comment_file}" + + load_metric() { + local file="$1" + local key="$2" + grep -E "^${key}=" "${file}" | head -n 1 | cut -d'=' -f2- || true + } + + load_metric_with_fallback() { + local file="$1" + local primary_key="$2" + local fallback_key="$3" + local value="" + + value="$(load_metric "${file}" "${primary_key}")" + if [[ -z "${value}" ]]; then + value="$(load_metric "${file}" "${fallback_key}")" + fi + printf '%s' "${value}" + } + + percentage_delta() { + local current="$1" + local baseline="$2" + awk -v c="${current}" -v b="${baseline}" 'BEGIN { if (b == 0) { print "n/a"; } else { printf "%+.2f%%", ((c - b) / b) * 100; } }' + } + + append_line() { + printf '%s\n' "$1" >> "${comment_file}" + } + + append_blank() { + printf '\n' >> "${comment_file}" + } + + if [[ ! -f "${CURRENT_METRICS_FILE}" ]]; then + append_line "${marker}" + append_line "### EXPB Benchmark Comparison" + append_blank + append_line "Run: [View workflow run](${RUN_URL})" + append_blank + append_line "No current run metrics file was produced, so comparison against master could not be generated." + elif [[ ! -f "${MASTER_METRICS_FILE}" ]]; then + append_line "${marker}" + append_line "### EXPB Benchmark Comparison" + append_blank + append_line "Run: [View workflow run](${RUN_URL})" + append_blank + append_line "No cached master baseline metrics were found yet. A baseline will be created from the next successful \`master\` push run." + append_blank + append_line "Current scenario: \`${SCENARIO_NAME}\`" + else + current_avg="$(load_metric "${CURRENT_METRICS_FILE}" "AVG")" + current_median="$(load_metric_with_fallback "${CURRENT_METRICS_FILE}" "MEDIAN" "MEAN")" + current_p90="$(load_metric "${CURRENT_METRICS_FILE}" "P90")" + current_p95="$(load_metric "${CURRENT_METRICS_FILE}" "P95")" + current_p99="$(load_metric "${CURRENT_METRICS_FILE}" "P99")" + current_min="$(load_metric "${CURRENT_METRICS_FILE}" "MIN")" + current_max="$(load_metric "${CURRENT_METRICS_FILE}" "MAX")" + + master_avg="$(load_metric "${MASTER_METRICS_FILE}" "AVG")" + master_median="$(load_metric_with_fallback "${MASTER_METRICS_FILE}" "MEDIAN" "MEAN")" + master_p90="$(load_metric "${MASTER_METRICS_FILE}" "P90")" + master_p95="$(load_metric "${MASTER_METRICS_FILE}" "P95")" + master_p99="$(load_metric "${MASTER_METRICS_FILE}" "P99")" + master_min="$(load_metric "${MASTER_METRICS_FILE}" "MIN")" + master_max="$(load_metric "${MASTER_METRICS_FILE}" "MAX")" + + append_line "${marker}" + append_line "### EXPB Benchmark Comparison" + append_blank + append_line "Run: [View workflow run](${RUN_URL})" + append_blank + append_line "Scenario: \`${SCENARIO_NAME}\`" + append_blank + append_line "| Metric | PR | Master (cached) | Delta PR vs Master |" + append_line "|---|---:|---:|---:|" + append_line "| AVG (ms) | ${current_avg} | ${master_avg} | $(percentage_delta "${current_avg}" "${master_avg}") |" + append_line "| MEDIAN (ms) | ${current_median} | ${master_median} | $(percentage_delta "${current_median}" "${master_median}") |" + append_line "| P90 (ms) | ${current_p90} | ${master_p90} | $(percentage_delta "${current_p90}" "${master_p90}") |" + append_line "| P95 (ms) | ${current_p95} | ${master_p95} | $(percentage_delta "${current_p95}" "${master_p95}") |" + append_line "| P99 (ms) | ${current_p99} | ${master_p99} | $(percentage_delta "${current_p99}" "${master_p99}") |" + append_line "| MIN (ms) | ${current_min} | ${master_min} | $(percentage_delta "${current_min}" "${master_min}") |" + append_line "| MAX (ms) | ${current_max} | ${master_max} | $(percentage_delta "${current_max}" "${master_max}") |" + fi + + if [[ "${EXCEPTION_FOUND}" == "true" ]]; then + append_blank + append_line ":warning: This run contained \`Exception\` lines and the workflow is expected to fail." + fi + + if [[ "${INVALID_BLOCK_FOUND}" == "true" ]]; then + append_blank + append_line ":warning: This run contained \`Invalid block\` log lines and the workflow is expected to fail." + fi + + { + echo "body<> "${GITHUB_OUTPUT}" + + - name: Publish PR comparison comment + if: always() && github.event_name == 'pull_request' + uses: actions/github-script@v7 + env: + COMMENT_BODY: ${{ steps.pr-comment.outputs.body }} + with: + script: | + const marker = ''; + const body = process.env.COMMENT_BODY; + const { owner, repo } = context.repo; + const issue_number = context.payload.pull_request.number; + + const comments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number, + per_page: 100, + }); + + const existing = comments.find((comment) => + comment.body && comment.body.includes(marker), + ); + + if (existing) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body, + }); + } + + - name: Prepare master metrics cache + if: github.event_name == 'push' && github.ref_name == 'master' && steps.analyze.outputs.exception_found != 'true' && steps.analyze.outputs.invalid_block_found != 'true' + shell: bash + run: | + set -euo pipefail + mkdir -p "${RUNNER_TEMP}/expb-master-metrics-cache" + cp "${{ steps.analyze.outputs.metrics_file }}" "${RUNNER_TEMP}/expb-master-metrics-cache/master-metrics.env" + + - name: Save master metrics cache + if: github.event_name == 'push' && github.ref_name == 'master' && steps.analyze.outputs.exception_found != 'true' && steps.analyze.outputs.invalid_block_found != 'true' + uses: actions/cache/save@v4 + with: + path: ${{ runner.temp }}/expb-master-metrics-cache + key: expb-master-metrics-v1-${{ github.run_id }} + + - name: Enforce run quality gates + if: always() + shell: bash + run: | + set -euo pipefail + + if [[ "${{ steps.run-expb.outcome }}" != "success" ]]; then + echo "expb execute-scenarios did not finish successfully." + exit 1 + fi + + if [[ "${{ steps.analyze.outputs.exception_found }}" == "true" ]]; then + echo "Exceptions were detected in run output. Failing workflow." + if [[ -f "${{ steps.analyze.outputs.exception_lines_file }}" ]]; then + head -n 40 "${{ steps.analyze.outputs.exception_lines_file }}" + fi + exit 1 + fi + + if [[ "${{ steps.analyze.outputs.invalid_block_found }}" == "true" ]]; then + echo "Invalid block lines were detected in run output. Failing workflow." + if [[ -f "${{ steps.analyze.outputs.invalid_block_lines_file }}" ]]; then + head -n 40 "${{ steps.analyze.outputs.invalid_block_lines_file }}" + fi + exit 1 + fi + + - name: Cleanup rendered config + if: always() + shell: bash + run: | + set -euo pipefail + + # Always remove the deterministic rendered config path + rm -f "${RUNNER_TEMP}/rendered-expb-config.yaml" || true + + # Also remove the path reported by render-config (if any and different) + rendered_config_file="${{ steps.render-config.outputs.rendered_config_file }}" + if [[ -n "${rendered_config_file}" && "${rendered_config_file}" != "${RUNNER_TEMP}/rendered-expb-config.yaml" ]]; then + rm -f "${rendered_config_file}" || true + fi diff --git a/.github/workflows/run-gas-benchmarks.yml b/.github/workflows/run-gas-benchmarks.yml new file mode 100644 index 000000000000..b265005cef6d --- /dev/null +++ b/.github/workflows/run-gas-benchmarks.yml @@ -0,0 +1,258 @@ +name: Execute Gas benchmarks + +on: + pull_request: + branches: [master] + types: [labeled] + paths: [src/Nethermind/**] + workflow_dispatch: + inputs: + runs: + description: Number of benchmark runs + required: false + default: "1" + testPath: + description: Path to test files + required: false + default: "eest_tests" + genesisFile: + description: Genesis file to use + required: false + default: "zkevmgenesis.json" + warmupFile: + description: Warmup file path + required: false + default: "warmup/warmup-1000bl-16wi-24tx.txt" + clients: + description: Comma-separated list of clients to test + required: false + default: "nethermind" + filter: + description: Test filter pattern + required: false + default: "" + +env: + DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION: "1" + TERM: xterm + +jobs: + resolve-inputs: + runs-on: ubuntu-slim + outputs: + should-run: ${{ steps.resolve.outputs.should-run }} + runs: ${{ steps.resolve.outputs.runs }} + testPath: ${{ steps.resolve.outputs.testPath }} + genesisFile: ${{ steps.resolve.outputs.genesisFile }} + warmupFile: ${{ steps.resolve.outputs.warmupFile }} + clients: ${{ steps.resolve.outputs.clients }} + filter: ${{ steps.resolve.outputs.filter }} + steps: + - name: Resolve workflow inputs + id: resolve + shell: bash + env: + EVENT_NAME: ${{ github.event_name }} + LABEL_NAME: ${{ github.event.label.name }} + DISPATCH_RUNS: ${{ inputs.runs }} + DISPATCH_TEST_PATH: ${{ inputs.testPath }} + DISPATCH_GENESIS_FILE: ${{ inputs.genesisFile }} + DISPATCH_WARMUP_FILE: ${{ inputs.warmupFile }} + DISPATCH_CLIENTS: ${{ inputs.clients }} + DISPATCH_FILTER: ${{ inputs.filter }} + run: | + set -euo pipefail + + should_run="true" + runs="1" + testPath="eest_tests" + genesisFile="zkevmgenesis.json" + warmupFile="warmup/warmup-1000bl-16wi-24tx.txt" + clients="nethermind" + filter="" + + if [[ "${EVENT_NAME}" == "workflow_dispatch" ]]; then + runs="${DISPATCH_RUNS:-1}" + testPath="${DISPATCH_TEST_PATH:-eest_tests}" + genesisFile="${DISPATCH_GENESIS_FILE:-zkevmgenesis.json}" + warmupFile="${DISPATCH_WARMUP_FILE:-warmup/warmup-1000bl-16wi-24tx.txt}" + clients="${DISPATCH_CLIENTS:-nethermind}" + filter="${DISPATCH_FILTER:-}" + elif [[ "${EVENT_NAME}" == "pull_request" ]]; then + label="${LABEL_NAME:-}" + + # Only accept labels in the format: gasbench[-] + if [[ "${label}" == "gasbench" ]]; then + filter="" + elif [[ "${label}" == gasbench-* ]]; then + suffix="${label#gasbench-}" + if [[ -z "${suffix}" ]]; then + filter="" + elif [[ "${suffix}" =~ ^[A-Za-z0-9_.-]+(,[A-Za-z0-9_.-]+)*$ ]]; then + filter="${suffix}" + else + should_run="false" + fi + else + should_run="false" + fi + fi + + { + echo "should-run=${should_run}" + echo "runs=${runs}" + echo "testPath=${testPath}" + echo "genesisFile=${genesisFile}" + echo "warmupFile=${warmupFile}" + echo "clients=${clients}" + echo "filter=${filter}" + } >> "${GITHUB_OUTPUT}" + + publish-docker: + needs: [resolve-inputs] + if: needs.resolve-inputs.outputs.should-run == 'true' + runs-on: ubuntu-latest + outputs: + image-label: ${{ steps.publish-docker.outputs.image-label }} + steps: + - name: Free up disk space + uses: jlumbroso/free-disk-space@v1.3.1 + with: + large-packages: false + tool-cache: false + + - name: Check out repository + uses: actions/checkout@v6 + + - name: Publish Docker image + id: publish-docker + uses: ./.github/actions/publish-docker + with: + image-name: "nethermind" + tag: "" + dockerfile: "Dockerfile" + build-config: "release" + docker-hub-username: ${{ secrets.DOCKER_HUB_USERNAME }} + docker-hub-password: ${{ secrets.DOCKER_HUB_PASSWORD }} + + setup: + needs: [resolve-inputs] + if: needs.resolve-inputs.outputs.should-run == 'true' + runs-on: ubuntu-slim + outputs: + runner-name: ${{ steps.setup-runner.outputs.runner-name }} + machine-ip: ${{ steps.setup-runner.outputs.machine-ip }} + run-id: ${{ steps.setup-runner.outputs.run-id }} + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Authenticate App + id: gh-app + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + repositories: "nethermind,post-merge-smoke-tests" + + - name: Authenticate Admin App + id: gh-admin-app + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.ADMIN_APP_ID }} + private-key: ${{ secrets.ADMIN_APP_PRIVATE_KEY }} + repositories: "nethermind" + + - name: Setup self-hosted runner + id: setup-runner + uses: ./.github/actions/setup-runner + with: + gh-token: ${{ steps.gh-app.outputs.token }} + gh-runner-token: ${{ steps.gh-admin-app.outputs.token }} + gh-username: ${{ github.actor }} + runner-name: "gas-benchmark" + runner-type: g6-standard-2 + timeout: "5" + setup-wait-time: "120" + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + gas-benchmark: + needs: + - resolve-inputs + - setup + - publish-docker + if: needs.resolve-inputs.outputs.should-run == 'true' + runs-on: ${{ needs.setup.outputs.runner-name }} + timeout-minutes: 360 + env: + HOME: /root + GAS_BENCHMARK_RUNS: "${{ needs.resolve-inputs.outputs.runs }}" + GAS_BENCHMARK_TEST_PATH: "${{ needs.resolve-inputs.outputs.testPath }}" + GAS_BENCHMARK_GENESIS_FILE: "${{ needs.resolve-inputs.outputs.genesisFile }}" + GAS_BENCHMARK_WARMUP_FILE: "${{ needs.resolve-inputs.outputs.warmupFile }}" + GAS_BENCHMARK_CLIENTS: "${{ needs.resolve-inputs.outputs.clients }}" + GAS_BENCHMARK_FILTER: "${{ needs.resolve-inputs.outputs.filter }}" + steps: + - name: Checkout Gas Benchmarks + uses: actions/checkout@v6 + with: + repository: NethermindEth/gas-benchmarks + ref: main + lfs: true + + - name: Set up Docker + uses: docker/setup-docker-action@v4 + + - name: Run gas-benchmarks composite action + uses: ./.github/actions/gas-benchmark-action + with: + # Benchmark configuration (all optional, with sensible defaults) + testPath: ${{ env.GAS_BENCHMARK_TEST_PATH }} + genesisFile: ${{ env.GAS_BENCHMARK_GENESIS_FILE }} + warmupFile: ${{ env.GAS_BENCHMARK_WARMUP_FILE }} + clients: ${{ env.GAS_BENCHMARK_CLIENTS }} + runs: ${{ env.GAS_BENCHMARK_RUNS }} + opcodeWarmupCount: "1" + filter: ${{ env.GAS_BENCHMARK_FILTER }} + images: '{"nethermind":"${{ needs.publish-docker.outputs.image-label }}","geth":"default","reth":"default","erigon":"default","besu":"default","nimbus":"default","ethrex":"default"}' + + # PostgreSQL target (no DB is provisioned by this workflow) + postgresHost: ${{ secrets.GAS_BENCHMARKS_DB_HOST }} + postgresPort: ${{ secrets.GAS_BENCHMARKS_DB_PORT }} + postgresDbName: ${{ secrets.GAS_BENCHMARKS_DB_NAME }} + postgresTable: ${{ secrets.GAS_BENCHMARKS_DB_TABLE }} + postgresUser: ${{ secrets.GAS_BENCHMARKS_DB_USER }} + postgresPassword: ${{ secrets.GAS_BENCHMARKS_DB_PASSWORD }} + + cleanup: + needs: [resolve-inputs, setup, gas-benchmark] + if: always() && needs.resolve-inputs.outputs.should-run == 'true' + runs-on: ubuntu-slim + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Authenticate App + id: gh-app + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + repositories: "nethermind,post-merge-smoke-tests" + + - name: Authenticate Admin App + id: gh-admin-app + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.ADMIN_APP_ID }} + private-key: ${{ secrets.ADMIN_APP_PRIVATE_KEY }} + repositories: "nethermind" + + - name: Remove self-hosted runner + uses: ./.github/actions/remove-runner + with: + gh-token: ${{ steps.gh-app.outputs.token }} + gh-runner-token: ${{ steps.gh-admin-app.outputs.token }} + runner-name: ${{ needs.setup.outputs.runner-name }} + workflow-run-id: ${{ needs.setup.outputs.run-id }} diff --git a/.github/workflows/sync-supported-chains.yml b/.github/workflows/sync-supported-chains.yml index 18cff36c31d9..7517b5416bf2 100644 --- a/.github/workflows/sync-supported-chains.yml +++ b/.github/workflows/sync-supported-chains.yml @@ -36,7 +36,7 @@ jobs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: path: nethermind ref: ${{ github.event.workflow_run.head_branch || github.ref_name || 'master' }} @@ -109,13 +109,13 @@ jobs: repositories: "nethermind,post-merge-smoke-tests" - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: clean: true ref: ${{ github.event.workflow_run.head_branch || github.ref_name || 'master' }} - name: Checkout tests repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: repository: NethermindEth/post-merge-smoke-tests path: tests @@ -178,6 +178,7 @@ jobs: --el-extra-flag "Seq.ServerUrl=https://seq.nethermind.io" \ --el-extra-flag "Seq.ApiKey=${{ secrets.SEQ_API_KEY }}" \ --el-extra-flag Seq.MinLevel=Info \ + --el-extra-flag "Init.LogRules=Synchronization.Peers.SyncPeersReport:Debug" \ --el-extra-flag "Metrics.PushGatewayUrl=${{ secrets.GRAFANA_CONNECTION_STRING }}" ) OP_METRICS_FLAGS=( @@ -186,6 +187,7 @@ jobs: --el-op-extra-flag "Seq.ServerUrl=https://seq.nethermind.io" \ --el-op-extra-flag "Seq.ApiKey=${{ secrets.SEQ_API_KEY }}" \ --el-op-extra-flag Seq.MinLevel=Info \ + --el-op-extra-flag "Init.LogRules=Synchronization.Peers.SyncPeersReport:Debug" \ --el-op-extra-flag "Metrics.PushGatewayUrl=${{ secrets.GRAFANA_CONNECTION_STRING }}" ) L2_METRICS_FLAGS=( @@ -194,6 +196,7 @@ jobs: --el-l2-extra-flag "Seq.ServerUrl=https://seq.nethermind.io" \ --el-l2-extra-flag "Seq.ApiKey=${{ secrets.SEQ_API_KEY }}" \ --el-l2-extra-flag Seq.MinLevel=Info \ + --el-l2-extra-flag "Init.LogRules=Synchronization.Peers.SyncPeersReport:Debug" \ --el-l2-extra-flag "Metrics.PushGatewayUrl=${{ secrets.GRAFANA_CONNECTION_STRING }}" ) @@ -246,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" == *hekla* ]]; then - CONSENSUS_URL="${{ secrets.HOLESKY_CONSENSUS_URL }}" - EXECUTION_URL="${{ secrets.HOLESKY_EXECUTION_URL }}" - stripped_network="holesky" - taiko_client_version="latest" + elif [[ "$network" == *hoodi* ]]; then + CONSENSUS_URL="${{ secrets.HOODI_CONSENSUS_URL }}" + EXECUTION_URL="${{ secrets.HOODI_EXECUTION_URL }}" + stripped_network="hoodi" else echo "Unknown network" exit 1 diff --git a/.github/workflows/test-assertoor.yml b/.github/workflows/test-assertoor.yml index a43485268a92..be6e212c05c0 100644 --- a/.github/workflows/test-assertoor.yml +++ b/.github/workflows/test-assertoor.yml @@ -21,12 +21,12 @@ jobs: private-key: ${{ secrets.APP_PRIVATE_KEY }} - name: Checkout this repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: path: main-repo - name: Checkout assertoor-test repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: repository: kamilchodola/assertoor-test path: assertoor-test diff --git a/.github/workflows/update-dockerfiles.yml b/.github/workflows/update-dockerfiles.yml new file mode 100644 index 000000000000..c7031e42362c --- /dev/null +++ b/.github/workflows/update-dockerfiles.yml @@ -0,0 +1,86 @@ +name: Update Dockerfiles + +on: + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + update-dockerfiles: + name: Update Dockerfiles + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v6 + + - name: Detect .NET version + run: | + dotnet_version=$(jq -r '.sdk.version' global.json | cut -d. -f1,2) + echo "DOTNET_VERSION=$dotnet_version" >> $GITHUB_ENV + + - name: Update Dockerfiles + id: update + run: | + # Get the latest .NET release information + manifest=$(curl -s https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/$DOTNET_VERSION/releases.json) + runtime_version=$(echo "$manifest" | jq -r '."latest-runtime"') + sdk_version=$(echo "$manifest" | jq -r '."latest-sdk"') + + # Get the digests for the specified tags + runtime_chiseled_tag=${runtime_version}-noble-chiseled + runtime_chiseled_digest=$(skopeo inspect docker://mcr.microsoft.com/dotnet/aspnet:${runtime_chiseled_tag} --no-tags | jq -r '.Digest') + + runtime_tag=${runtime_version}-noble + runtime_digest=$(skopeo inspect docker://mcr.microsoft.com/dotnet/aspnet:${runtime_tag} --no-tags | jq -r '.Digest') + + sdk_tag=${sdk_version}-noble + sdk_digest=$(skopeo inspect docker://mcr.microsoft.com/dotnet/sdk:${sdk_tag} --no-tags | jq -r '.Digest') + + # Update Dockerfiles + for file in "Dockerfile" "Dockerfile.chiseled" "scripts/build/Dockerfile"; do + sed -i "s|\(mcr\.microsoft\.com/dotnet/sdk:\)[^[:space:]]*|\1${sdk_tag}@${sdk_digest}|" "$file" + done + + runtime_pattern="\(mcr\.microsoft\.com/dotnet/aspnet:\)[^[:space:]]*" + + sed -i "s|${runtime_pattern}|\1${runtime_tag}@${runtime_digest}|" Dockerfile + sed -i "s|${runtime_pattern}|\1${runtime_chiseled_tag}@${runtime_chiseled_digest}|" Dockerfile.chiseled + + # Get the number of modified files + file_count=$(git status --porcelain | wc -l) + echo "file-count=$file_count" >> $GITHUB_OUTPUT + + - name: Create GitHub app token + if: steps.update.outputs.file-count != '0' + id: gh-app + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - name: Create a pull request + if: steps.update.outputs.file-count != '0' + env: + GH_TOKEN: ${{ steps.gh-app.outputs.token }} + run: | + head_branch=chore/update-dockerfiles-$GITHUB_RUN_NUMBER-$GITHUB_RUN_ATTEMPT + body=$(cat <>` + - replacing `<>` + - renaming scenario key `nethermind:` to a detailed scenario name + - appending user-provided extra flags under `extra_flags:` +- Installs `expb` via `uv tool install --force --from ... expb`. +- Runs `expb execute-scenarios` with per-payload metrics and logs. +- Handles termination gracefully with cleanup grace period. +- On successful `master` push runs, caches per-payload timing aggregates extracted from the `processing_ms` table. +- On labeled PR runs, restores latest cached `master` metrics and posts a PR comment with PR vs master comparison. + +### What to inspect in run output + +- Inspect the `Run expb scenarios` step output first. +- Treat any Nethermind `Exception` as a high-priority issue. +- Explicitly scan logs for invalid block signals, including `Invalid Block` and `Invalid Blocks`. +- Review the end-of-run summary section with per-block timings and totals. +- Use summary timing values to derive aggregate metrics (average/mean at minimum; median/p95 when available). +- If a run fails or is terminated, check whether cleanup grace-period handling completed cleanly. + +### Log structure reference + +- Reference run used for structure validation: + - Run: `https://github.com/NethermindEth/nethermind/actions/runs/22185801008` + - Job: `https://github.com/NethermindEth/nethermind/actions/runs/22185801008/job/64159725161` +- Fetch logs with: + ```bash + gh run view 22185801008 --job 64159725161 --log + ``` +- GitHub job log lines are tab-separated in this shape: + - `\t\t\t` + - Example step names in this workflow: `Print resolved inputs`, `Render benchmark config`, `Install or upgrade expb`, `Run expb scenarios`. +- `Run expb scenarios` contains mixed streams: + - EXPB structured events like: `timestamp=... level=info event="..."`. + - K6 progress and metric blocks (`http_req_duration`, `iteration_duration`, percentiles like `p(95)`). + - Raw Nethermind runtime logs (received blocks, processed block timings, shutdown sequence). + - Per-payload metrics table near the end, marked by: + - `+---------+------------+-----------------+` + - `| payload | gas_used | processing_ms |` + - rows with payload id, gas used, processing time. +- ANSI color codes are present; when searching/parsing, strip ANSI escape sequences first. +- Some non-ASCII time-unit glyphs can appear mangled in plain terminal output, so prefer numeric metric fields when computing aggregates. + +### Mandatory log checks + +- Fail review if any of these appear in Nethermind logs: + - `Exception` + - `Invalid Block` + - `Invalid Blocks` +- Workflow behavior requirement: any detected `Exception` in run output must fail the workflow after reporting matching lines. +- Also flag severe runtime signals if present: + - `Unhandled` + - `Fatal` + - `ERROR` +- Confirm normal shutdown markers at end: + - `Nethermind is shut down` + - `event="Cleanup completed"` + +### Notes for agents + +- The benchmark config is rendered to a temporary file and removed afterward; no source config revert is required. +- For `pull_request` and `push` auto-runs, default mode is currently `halfpath + superblocks`. +- Keep benchmark-related changes isolated to the workflow and benchmark guidance unless explicitly asked otherwise. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000000..285e0f5b36b4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@./AGENTS.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b9adeabc56da..53cf3198a300 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.Build.props b/Directory.Build.props index 5a491ae57e10..ffc0e5390d4f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,12 +4,12 @@ Debug true true - 13.0 - direct + 14.0 en - net9.0 + net10.0 true true + NU1901;NU1902;NU1903 diff --git a/Directory.Packages.props b/Directory.Packages.props index c69ec3164634..0c968919dcc0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,46 +6,48 @@ - + - - + + - + - + - - + + - - + + - - - + + + - + - + + - - - - + + + + + - - - + + + @@ -55,8 +57,9 @@ - - + + + @@ -64,28 +67,26 @@ - - + + - - + + - + - + - - - - - - - - - - - + + + + + + + + + - + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 9ef0f9a05699..a2eef01c8319 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,26 +1,31 @@ # SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited # SPDX-License-Identifier: LGPL-3.0-only -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0-noble AS build +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.103-noble@sha256:0a506ab0c8aa077361af42f82569d364ab1b8741e967955d883e3f23683d473a AS build ARG BUILD_CONFIG=release -ARG BUILD_TIMESTAMP -ARG CI +ARG CI=true ARG COMMIT_HASH +ARG SOURCE_DATE_EPOCH ARG TARGETARCH +WORKDIR /nethermind + COPY src/Nethermind src/Nethermind COPY Directory.*.props . +COPY global.json . COPY nuget.config . RUN arch=$([ "$TARGETARCH" = "amd64" ] && echo "x64" || echo "$TARGETARCH") && \ - dotnet publish src/Nethermind/Nethermind.Runner -c $BUILD_CONFIG -a $arch -o /publish --sc false \ - -p:BuildTimestamp=$BUILD_TIMESTAMP -p:Commit=$COMMIT_HASH + cd src/Nethermind/Nethermind.Runner && \ + dotnet restore --locked-mode && \ + dotnet publish -c $BUILD_CONFIG -a $arch -o /publish --no-restore --no-self-contained \ + -p:SourceRevisionId=$COMMIT_HASH # A temporary symlink to support the old executable name -RUN ln -s -r /publish/nethermind /publish/Nethermind.Runner +RUN ln -sr /publish/nethermind /publish/Nethermind.Runner -FROM mcr.microsoft.com/dotnet/aspnet:9.0-noble +FROM mcr.microsoft.com/dotnet/aspnet:10.0.3-noble@sha256:52dcfb4225fda614c38ba5997a4ec72cbd5260a624125174416e547ff9eb9b8c WORKDIR /nethermind diff --git a/Dockerfile.chiseled b/Dockerfile.chiseled index f356b92d6cdd..60b5b36ff752 100644 --- a/Dockerfile.chiseled +++ b/Dockerfile.chiseled @@ -1,21 +1,26 @@ # SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited # SPDX-License-Identifier: LGPL-3.0-only -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0-noble AS build +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.103-noble@sha256:0a506ab0c8aa077361af42f82569d364ab1b8741e967955d883e3f23683d473a AS build ARG BUILD_CONFIG=release -ARG BUILD_TIMESTAMP -ARG CI +ARG CI=true ARG COMMIT_HASH +ARG SOURCE_DATE_EPOCH ARG TARGETARCH +WORKDIR /nethermind + COPY src/Nethermind src/Nethermind COPY Directory.*.props . +COPY global.json . COPY nuget.config . RUN arch=$([ "$TARGETARCH" = "amd64" ] && echo "x64" || echo "$TARGETARCH") && \ - dotnet publish src/Nethermind/Nethermind.Runner -c $BUILD_CONFIG -a $arch -o /publish --sc false \ - -p:BuildTimestamp=$BUILD_TIMESTAMP -p:Commit=$COMMIT_HASH + cd src/Nethermind/Nethermind.Runner && \ + dotnet restore --locked-mode && \ + dotnet publish -c $BUILD_CONFIG -a $arch -o /publish --no-restore --no-self-contained \ + -p:SourceRevisionId=$COMMIT_HASH # Creating these directories here is needed to ensure the correct permissions RUN cd /publish && \ @@ -23,7 +28,7 @@ RUN cd /publish && \ mkdir logs && \ mkdir nethermind_db -FROM mcr.microsoft.com/dotnet/aspnet:9.0-noble-chiseled +FROM mcr.microsoft.com/dotnet/aspnet:10.0.3-noble-chiseled@sha256:3b0bd0fa83c55a73d85007ac6896b9e5ac61255d651be135b7d70622af56af78 WORKDIR /nethermind diff --git a/Dockerfile.diag b/Dockerfile.diag index eb91c1717c4b..526e0f29fb17 100644 --- a/Dockerfile.diag +++ b/Dockerfile.diag @@ -1,30 +1,33 @@ # SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited # SPDX-License-Identifier: LGPL-3.0-only -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0-noble AS build +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-noble AS build ARG BUILD_CONFIG=release -ARG BUILD_TIMESTAMP -ARG CI +ARG CI=true ARG COMMIT_HASH +ARG SOURCE_DATE_EPOCH ARG TARGETARCH +WORKDIR /nethermind + COPY src/Nethermind src/Nethermind COPY Directory.*.props . +COPY global.json . COPY nuget.config . RUN arch=$([ "$TARGETARCH" = "amd64" ] && echo "x64" || echo "$TARGETARCH") && \ - dotnet add src/Nethermind/Nethermind.Runner package JetBrains.dotMemory.Console.linux-$arch \ - --package-directory /tmp && \ - dotnet publish src/Nethermind/Nethermind.Runner -c $BUILD_CONFIG -a $arch -o /publish --sc false \ - -p:BuildTimestamp=$BUILD_TIMESTAMP -p:Commit=$COMMIT_HASH + dotnet add src/Nethermind/Nethermind.Runner package JetBrains.dotMemory.Console.linux-$arch \ + --package-directory /tmp && \ + dotnet publish src/Nethermind/Nethermind.Runner -c $BUILD_CONFIG -a $arch -o /publish --no-self-contained \ + -p:SourceRevisionId=$COMMIT_HASH RUN dotnet tool install -g dotnet-dump && \ - dotnet tool install -g dotnet-gcdump && \ - dotnet tool install -g dotnet-trace && \ - dotnet tool install -g JetBrains.dotTrace.GlobalTools + dotnet tool install -g dotnet-gcdump && \ + dotnet tool install -g dotnet-trace && \ + dotnet tool install -g JetBrains.dotTrace.GlobalTools -FROM mcr.microsoft.com/dotnet/aspnet:9.0-noble +FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble WORKDIR /nethermind @@ -38,7 +41,10 @@ EXPOSE 8545 8551 30303 COPY --from=build /publish . COPY --from=build /root/.dotnet/tools /opt/diag-tools COPY --from=build /tmp/jetbrains.dotmemory.console.*/**/tools /opt/diag-tools/dotmemory +COPY --chmod=0755 scripts/diag-entrypoint.sh entrypoint.sh ENV PATH="$PATH:/opt/diag-tools:/opt/diag-tools/dotmemory" -ENTRYPOINT ["./nethermind"] +STOPSIGNAL SIGINT + +ENTRYPOINT ["./entrypoint.sh"] diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 000000000000..285e0f5b36b4 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1 @@ +@./AGENTS.md diff --git a/README.md b/README.md index 5b34a6e61d39..7888790426b4 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,20 @@

- - - Nethermind + + + Nethermind

# Nethermind Ethereum client [![Tests](https://github.com/nethermindeth/nethermind/actions/workflows/nethermind-tests.yml/badge.svg)](https://github.com/nethermindeth/nethermind/actions/workflows/nethermind-tests.yml) -[![Follow us on X](https://img.shields.io/twitter/follow/nethermindeth?style=social&label=Follow%20us)](https://x.com/nethermindeth) +[![Follow us on X](https://img.shields.io/twitter/follow/nethermind?style=social&label=Follow%20us)](https://x.com/nethermind) [![Chat on Discord](https://img.shields.io/discord/629004402170134531?style=social&logo=discord)](https://discord.gg/GXJFaYk) [![GitHub Discussions](https://img.shields.io/github/discussions/nethermindeth/nethermind?style=social)](https://github.com/nethermindeth/nethermind/discussions) [![GitPOAPs](https://public-api.gitpoap.io/v1/repo/NethermindEth/nethermind/badge)](https://www.gitpoap.io/gh/NethermindEth/nethermind) -The Nethermind Ethereum execution client, built on .NET, delivers industry-leading performance in syncing and tip-of-chain processing. With its modular design and plugin system, it offers extensibility and features for new chains. As one of the most adopted execution clients on Ethereum, Nethermind plays a crucial role in enhancing the diversity and resilience of the Ethereum ecosystem. +Nethermind is an Ethereum execution client built on .NET. Nethermind is the fastest client in terms of throughput, block processing, transactions per second (TPS), and syncing new nodes. With its modular design and plugin system, it offers extensibility and features for new chains. As one of the most adopted execution clients on Ethereum, Nethermind plays a crucial role in enhancing the diversity and resilience of the Ethereum ecosystem. ## Documentation @@ -94,7 +94,7 @@ For more info, see [Building Docker image](https://docs.nethermind.io/developers **Prerequisites** -Install the [.NET SDK](https://aka.ms/dotnet/download). +Install [.NET SDK](https://aka.ms/dotnet/download) 10 or later. **Clone the repository** @@ -115,10 +115,10 @@ dotnet run -c release -- -c mainnet cd nethermind/src/Nethermind # Run Nethermind tests -dotnet test Nethermind.slnx -c release +dotnet test --solution Nethermind.slnx -c release # Run Ethereum Foundation tests -dotnet test EthereumTests.slnx -c release +dotnet test --solution EthereumTests.slnx -c release ``` For more info, see [Building standalone binaries](https://docs.nethermind.io/developers/building-from-source#building-standalone-binaries). diff --git a/cspell.json b/cspell.json new file mode 100644 index 000000000000..b50ffbfa7220 --- /dev/null +++ b/cspell.json @@ -0,0 +1,878 @@ +{ + "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", + "/--[a-z]+-[a-z]+/gi" + ], + "words": [ + "accesslist", + "accnt", + "adab", + "addmod", + "affinitize", + "agen", + "akhunov", + "alethia", + "alexey", + "analysed", + "apikey", + "archs", + "argjson", + "asmv", + "aspnet", + "aspnetcore", + "assertoor", + "autofac", + "autogen", + "auxdata", + "backpressure", + "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", + "bword", + "buterin", + "bylica", + "bytecodes", + "callcode", + "calldatacopy", + "calldataload", + "calldatasize", + "callf", + "callme", + "callvalue", + "callvirt", + "cand", + "canonicality", + "castagnoli", + "cctor", + "cctors", + "chainid", + "chainspec", + "chiado", + "cipherparams", + "ciphertext", + "ckzg", + "classinit", + "cloneable", + "cmix", + "cmov", + "cmova", + "cmovae", + "cmovb", + "cmovbe", + "cmove", + "cmovg", + "cmovge", + "cmovl", + "cmovle", + "cmovna", + "cmovnb", + "cmovnbe", + "cmovne", + "cmovng", + "cmovnge", + "cmovnl", + "cmovnle", + "cmovnz", + "cmovz", + "codecopy", + "codehash", + "codesection", + "codesize", + "coef", + "collectd", + "colour", + "CORINFO", + "commitset", + "compactable", + "comparand", + "concurrenc", + "configurer", + "configurers", + "conflictor's", + "containersection", + "contentfiles", + "corechart", + "corinfo", + "cpufrequency", + "crummey", + "cryptosuite", + "cryptotests", + "datacopy", + "datagram", + "dataloadn", + "datasection", + "datasize", + "dasm", + "dbdir", + "dbsize", + "deadlined", + "deauthorized", + "debhelper", + "decommit", + "decompiled", + "dedup", + "decompiler", + "deconfigure", + "deconfigured's", + "decryptor", + "defaultable", + "delegatecall", + "delegatecode", + "demerzel", + "deque", + "deserialised", + "dests", + "devirtualization", + "devirtualize", + "devirtualized", + "devnet", + "devnets", + "devp2p", + "diagnoser", + "diagnosers", + "diffable", + "disappearer's", + "disasm", + "discontiguous", + "discport", + "discv", + "distros", + "divmod", + "dklen", + "dont", + "dontneed", + "dotmemory", + "dotnetty's", + "dottrace", + "dpapi", + "dpkg", + "dupn", + "dynamicclass", + "ecies", + "ecrec", + "edgecase", + "efbbbf", + "eips", + "eliminable", + "emojize", + "emptish", + "emptystep", + "emptystring", + "encrypter", + "encryptor", + "energi", + "energyweb", + "enginehost", + "engineport", + "enode", + "enodes", + "enrs", + "enrtree", + "expb", + "EXPB", + "eofcall", + "eofcreate", + "eofdelegatecall", + "eofstaticcall", + "eoftest", + "ephem", + "eradir", + "eraexportdir", + "erigon", + "esac", + "ethash", + "ethdisco", + "ethermine", + "ethpool", + "ethrex", + "ethstats", + "ethxx", + "evmdis", + "ewasm", + "evex", + "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", + "fullopts", + "gasrefund", + "gbps", + "gcstatic", + "gcdump", + "geoff", + "getblobs", + "getnull", + "getpayloadv", + "getrlimit", + "getshared", + "gettrie", + "gopherium", + "gwat", + "gword", + "halfpath", + "hardfork", + "hardforks", + "hashas", + "hashcode", + "hashdb", + "hashimoto", + "hashkey", + "hashlib", + "hashrate", + "hashtable", + "hashtables", + "hbrns", + "headp", + "healthchecks", + "hellothere", + "hexary", + "hexencodetest", + "hexroot", + "highbits", + "hiveon", + "hmac", + "hmacsha", + "holesky", + "hoodi", + "hostnames", + "hotstuff", + "hyperthreading", + "idiv", + "idxs", + "iface", + "ikvp", + "immediates", + "includable", + "initcode", + "initcodes", + "inited", + "initialized", + "insens", + "insize", + "installdeb", + "instart", + "internaltype", + "interp", + "interruptible", + "invalidblockhash", + "isnull", + "iszero", + "ivalidatorregistrycontract", + "ivle", + "jemalloc", + "jimbojones", + "jitasm", + "jitted", + "jitting", + "jmps", + "jnbe", + "jnge", + "jnle", + "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", + "LINQ", + "liskov", + "logicalcpu", + "longdate", + "lookback", + "lukasz", + "lzcnt", + "machdep", + "machinename", + "madv", + "maiboroda", + "mainchain", + "mallopt", + "mapg", + "marshallers", + "maskz", + "masternode", + "masternodes", + "masterodes", + "materialisation", + "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", + "microarchitecture", + "microbenchmark", + "microbenchmarks", + "microsecs", + "midnib", + "millis", + "mingas", + "minlevel", + "minopt", + "mintable", + "misbehaviour", + "mispredictions", + "mklink", + "mload", + "mmap", + "modexp", + "modexpprecompile", + "morden", + "movbe", + "movsxd", + "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", + "nint", + "nito", + "nlog", + "nodedata", + "nodelay", + "nodestore", + "nodetype", + "nofile", + "nongcstatic", + "noninteractive", + "nonposdao", + "nonstring", + "nops", + "nostack", + "nowal", + "npoints", + "npushes", + "nsubstitute", + "nugettest", + "nuint", + "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", + "plinq", + "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", + "pshufb", + "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", + "realblocks", + "refint", + "regs", + "relbr", + "refstruct", + "regenesis", + "reitwiessner", + "reorgable", + "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", + "sbyte", + "scopable", + "sdiv", + "secp", + "securetrie", + "segmentations", + "selfbalance", + "selfdestruct", + "seqlock", + "serialised", + "setcode", + "setb", + "setbe", + "sete", + "setg", + "setge", + "setl", + "setle", + "setna", + "setnb", + "setnbe", + "setne", + "setnge", + "setnl", + "setnle", + "setng", + "setnz", + "setz", + "shamir", + "shlibs", + "shouldly", + "shuf", + "shutterized", + "sig₁", + "sig₂", + "signextend", + "sizeinbase", + "skiplastn", + "skylake", + "slnx", + "sload", + "smod", + "somelabel", + "spaceneth", + "spammy", + "sparkline", + "spinlocks", + "squarify", + "srcset", + "ssse", + "sstfiles", + "sstore", + "sswu", + "stackoverflow", + "starthash", + "statediff", + "stateroot", + "statetest", + "staticcall", + "stddev", + "stelem", + "stfld", + "stoppables", + "storagefuzz", + "streamable", + "stree", + "strs", + "stylesheet", + "subcall", + "subcalls", + "subchain", + "subcompaction", + "subcompactions", + "subcontainer", + "subcontext", + "subfolders", + "substate", + "subtrace", + "subtraces", + "superchain", + "superblocks", + "swapn", + "swende", + "synchronised", + "synclag", + "syscall", + "szalay", + "taiko", + "taskkill", + "tdxs", + "teku", + "testdata", + "testdb", + "testenvironments", + "testpassword", + "testpuppeth", + "testsimulate", + "testspec", + "testspecdir", + "threadid", + "threadlocal", + "threadpool", + "threadsafe", + "timestamper", + "titanbuilder", + "tipxdcx", + "tload", + "toobig", + "trambabamba", + "treemap", + "trieanyorder", + "trieexception", + "trienode", + "triestore", + "trietest", + "trietestnextprev", + "triggerable", + "tstore", + "tukey", + "tupleception", + "twap", + "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", + "unshifted", + "unsub", + "unsubscription", + "unsynchronized", + "unvote", + "uops", + "upnp", + "upto", + "upvoting", + "vbmi", + "vitalik", + "vmovdqu", + "vmovups", + "vmtrace", + "vote₁", + "vote₂", + "vote₃", + "voteₙ", + "vpaddd", + "vpcbr", + "vpermb", + "vpermi", + "vpor", + "vpshufb", + "vptest", + "vpxor", + "vzeroupper", + "wamp", + "warmcoinbase", + "wblob", + "winget", + "winsvega", + "wnew", + "wojciech", + "worklet", + "worklist", + "worldchain", + "worldstate", + "writebatch", + "writeoptions", + "wwwroot", + "wycheproof", + "xdai", + "xdcx", + "xmmword", + "xmlstarlet", + "xnpool", + "xvcj", + "yellowpaper", + "ymmword", + "yparity", + "zcompressor", + "zdecompressor", + "zhizhu", + "zmmword", + "zstandard", + "zstd", + "zwcm" + ], + "overrides": [ + { + "filename": "**/*.json", + "ignoreRegExpList": [ + "/\\\\u[0-9a-fA-F]{4}/g" + ] + } + ] +} diff --git a/global.json b/global.json index 37410ecff75e..bb2e7461e027 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,10 @@ { "sdk": { - "version": "9.0.2", + "version": "10.0.100", "allowPrerelease": false, "rollForward": "latestFeature" + }, + "test": { + "runner": "Microsoft.Testing.Platform" } } diff --git a/scripts/build/Dockerfile b/scripts/build/Dockerfile new file mode 100644 index 000000000000..57fad31cfd91 --- /dev/null +++ b/scripts/build/Dockerfile @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +# SPDX-License-Identifier: LGPL-3.0-only + +FROM mcr.microsoft.com/dotnet/sdk:10.0.103-noble@sha256:0a506ab0c8aa077361af42f82569d364ab1b8741e967955d883e3f23683d473a + +ARG COMMIT_HASH +ARG SOURCE_DATE_EPOCH + +ENV CI=true + +WORKDIR /nethermind + +COPY src/Nethermind src/Nethermind +COPY Directory.*.props . +COPY global.json . +COPY nuget.config . +COPY --chmod=0755 scripts/build/build.sh . + +RUN ./build.sh $COMMIT_HASH + +VOLUME /output + +CMD ["cp", "-r", "output/.", "/output"] diff --git a/scripts/build/archive.sh b/scripts/build/archive.sh index 77d91775abe0..0bd80138dac6 100755 --- a/scripts/build/archive.sh +++ b/scripts/build/archive.sh @@ -6,15 +6,14 @@ set -e echo "Archiving Nethermind packages" -cd $GITHUB_WORKSPACE mkdir $PACKAGE_DIR cd $PUB_DIR -cd linux-x64 && zip -r -y $GITHUB_WORKSPACE/$PACKAGE_DIR/$PACKAGE_PREFIX-linux-x64.zip . && cd .. -cd linux-arm64 && zip -r -y $GITHUB_WORKSPACE/$PACKAGE_DIR/$PACKAGE_PREFIX-linux-arm64.zip . && cd .. +cd linux-arm64 && zip -ry $GITHUB_WORKSPACE/$PACKAGE_DIR/$PACKAGE_PREFIX-linux-arm64.zip . && cd .. +cd linux-x64 && zip -ry $GITHUB_WORKSPACE/$PACKAGE_DIR/$PACKAGE_PREFIX-linux-x64.zip . && cd .. +cd osx-arm64 && zip -r $GITHUB_WORKSPACE/$PACKAGE_DIR/$PACKAGE_PREFIX-macos-arm64.zip . && cd .. +cd osx-x64 && zip -r $GITHUB_WORKSPACE/$PACKAGE_DIR/$PACKAGE_PREFIX-macos-x64.zip . && cd .. cd win-x64 && zip -r $GITHUB_WORKSPACE/$PACKAGE_DIR/$PACKAGE_PREFIX-windows-x64.zip . && cd .. -cd osx-x64 && zip -r -y $GITHUB_WORKSPACE/$PACKAGE_DIR/$PACKAGE_PREFIX-macos-x64.zip . && cd .. -cd osx-arm64 && zip -r -y $GITHUB_WORKSPACE/$PACKAGE_DIR/$PACKAGE_PREFIX-macos-arm64.zip . && cd .. cd ref && zip -r $GITHUB_WORKSPACE/$PACKAGE_DIR/$PACKAGE_PREFIX-ref-assemblies.zip . && cd .. echo "Archiving completed" diff --git a/scripts/build/build.sh b/scripts/build/build.sh index fc07af4c12bd..baf9eed41bfa 100755 --- a/scripts/build/build.sh +++ b/scripts/build/build.sh @@ -5,26 +5,27 @@ set -e build_config=release -output_path=$GITHUB_WORKSPACE/$PUB_DIR +output_path=/nethermind/output -cd $GITHUB_WORKSPACE/src/Nethermind/Nethermind.Runner +cd src/Nethermind/Nethermind.Runner echo "Building Nethermind" -for rid in "linux-x64" "linux-arm64" "win-x64" "osx-x64" "osx-arm64"; do +dotnet restore --locked-mode + +for rid in "linux-arm64" "linux-x64" "osx-arm64" "osx-x64" "win-x64"; do echo " Publishing for $rid" - dotnet publish -c $build_config -r $rid -o $output_path/$rid --sc true \ - -p:BuildTimestamp=$2 \ - -p:Commit=$1 \ + dotnet publish -c $build_config -r $rid -o $output_path/$rid --no-restore --sc \ -p:DebugType=embedded \ -p:IncludeAllContentForSelfExtract=true \ - -p:PublishSingleFile=true + -p:PublishSingleFile=true \ + -p:SourceRevisionId=$1 mkdir $output_path/$rid/keystore - # A temporary symlink for Linux and macOS for the old executable name - [[ $rid != win* ]] && ln -s -r $output_path/$rid/nethermind $output_path/$rid/Nethermind.Runner + # A temporary symlink for Linux to support the old executable name + [[ "$rid" == linux-* ]] && ln -sr $output_path/$rid/nethermind $output_path/$rid/Nethermind.Runner done mkdir $output_path/ref diff --git a/scripts/build/deb/Dockerfile b/scripts/build/deb/Dockerfile new file mode 100644 index 000000000000..0c93559f4151 --- /dev/null +++ b/scripts/build/deb/Dockerfile @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +# SPDX-License-Identifier: LGPL-3.0-only + +FROM ubuntu:noble-20251001@sha256:d22e4fb389065efa4a61bb36416768698ef6d955fe8a7e0cdb3cd6de80fa7eec + +ARG ARTIFACTS +ARG SOURCE_DATE_EPOCH + +WORKDIR /nethermind + +COPY src/Nethermind/Directory.Build.props . +COPY --chmod=0755 scripts/build/deb deb +COPY $ARTIFACTS/linux-x64 deb/nethermind/opt/nethermind + +RUN apt-get update && apt-get install xmlstarlet=1.6.1-4 -y --no-install-recommends && \ + rm -rf /var/lib/apt/lists/* + +RUN version_prefix=$(xmlstarlet sel -t -v "//Project/PropertyGroup/VersionPrefix" Directory.Build.props) && \ + version_suffix=$(xmlstarlet sel -t -v "//Project/PropertyGroup/VersionSuffix" -n Directory.Build.props) && \ + version=$([[ -n "$version_suffix" ]] && echo "$version_prefix-$version_suffix" || echo "$version_prefix") && \ + sed -i "s/^Version: .*/Version: $version/" deb/nethermind/DEBIAN/control + +WORKDIR /nethermind/deb + +RUN dpkg-deb --build --root-owner-group nethermind + +VOLUME /output + +CMD ["cp", "nethermind.deb", "/output/nethermind.deb"] diff --git a/scripts/build/deb/README.md b/scripts/build/deb/README.md new file mode 100644 index 000000000000..7664fb1f55af --- /dev/null +++ b/scripts/build/deb/README.md @@ -0,0 +1,9 @@ +# Building a Debian package + +Building a Debian (.deb) package requires Docker. Run the following from the repo root: + +```bash +scripts/build/deb/build.sh [path/to/output/dir] +``` + +The `path/to/output/dir` is optional. If not specified, the generated `nethermind.deb` package is copied to the repo root. diff --git a/scripts/build/deb/build.sh b/scripts/build/deb/build.sh new file mode 100644 index 000000000000..512eefd5b2ca --- /dev/null +++ b/scripts/build/deb/build.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +# SPDX-License-Identifier: LGPL-3.0-only + +set -e + +export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) + +build_output=.artifacts +package_output=${1:-.} + +mkdir -p $build_output + +docker build . -t nethermind-build -f scripts/build/Dockerfile \ + --build-arg COMMIT_HASH=$(git rev-parse HEAD) \ + --build-arg SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH + +docker run --rm --mount type=bind,source=$build_output,target=/output nethermind-build + +docker build . -t nethermind-deb -f scripts/build/deb/Dockerfile \ + --build-arg ARTIFACTS=$build_output \ + --build-arg SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH + +docker run --rm --mount "type=bind,source=$package_output,target=/output" nethermind-deb + +echo "Copied nethermind.deb to $package_output" + +rm -rf $build_output diff --git a/scripts/build/deb/nethermind/DEBIAN/control b/scripts/build/deb/nethermind/DEBIAN/control new file mode 100644 index 000000000000..b9e487cae921 --- /dev/null +++ b/scripts/build/deb/nethermind/DEBIAN/control @@ -0,0 +1,9 @@ +Package: nethermind +Version: - +Architecture: amd64 +Section: net +Priority: optional +Maintainer: Demerzel Solutions Limited +Homepage: https://nethermind.io/nethermind-client +Description: A robust execution client for Ethereum node operators. + The Nethermind Ethereum execution client, built on .NET, delivers industry-leading performance in syncing and tip-of-chain processing. With its modular design and plugin system, it offers extensibility and features for new chains. As one of the most adopted execution clients on Ethereum, Nethermind plays a crucial role in enhancing the diversity and resilience of the Ethereum ecosystem. diff --git a/scripts/build/publish-downloads.sh b/scripts/build/publish-downloads.sh deleted file mode 100755 index be0edd421846..000000000000 --- 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 - -export GPG_TTY=$(tty) - -set -e - -echo "Publishing packages to Downloads page" - -cd $GITHUB_WORKSPACE/$PACKAGE_DIR - -for rid in "linux-x64" "linux-arm64" "windows-x64" "macos-x64" "macos-arm64"; 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 586bb32cbe1e..000000000000 --- 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 [ "$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-x64" "linux-arm64" "windows-x64" "macos-x64" "macos-arm64" "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/config/testnet-matrix.json b/scripts/config/testnet-matrix.json index 412582e5d1a4..25f6ffa25908 100644 --- a/scripts/config/testnet-matrix.json +++ b/scripts/config/testnet-matrix.json @@ -7,14 +7,6 @@ "timeout": 180, "agent": "g6-standard-6" }, - { - "network": "holesky", - "cl": "lodestar", - "cl_image": "chainsafe/lodestar:latest", - "checkpoint-sync-url": "https://checkpoint-sync.holesky.ethpandaops.io/", - "timeout": 180, - "agent": "g6-standard-6" - }, { "network": "chiado", "cl": "lodestar", @@ -61,7 +53,7 @@ "cl_image": "", "checkpoint-sync-url": "", "timeout": 180, - "agent": "g6-standard-6" + "agent": "g6-standard-8" }, { "network": "world-sepolia", @@ -104,7 +96,7 @@ "agent": "g6-standard-6" }, { - "network": "taiko-hekla", + "network": "taiko-hoodi", "cl": "", "cl_image": "", "checkpoint-sync-url": "", @@ -126,21 +118,5 @@ "checkpoint-sync-url": "", "timeout": 180, "agent": "g6-standard-6" - }, - { - "network": "linea-mainnet", - "cl": "", - "cl_image": "", - "checkpoint-sync-url": "", - "timeout": 360, - "agent": "g6-standard-6" - }, - { - "network": "linea-sepolia", - "cl": "", - "cl_image": "", - "checkpoint-sync-url": "", - "timeout": 180, - "agent": "g6-standard-6" } ] diff --git a/scripts/diag-entrypoint.sh b/scripts/diag-entrypoint.sh new file mode 100644 index 000000000000..8c49a03ce859 --- /dev/null +++ b/scripts/diag-entrypoint.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +# SPDX-License-Identifier: LGPL-3.0-only + +set -eo pipefail + +start_dotmemory() { + echo "Starting dotMemory..." + + exec dotmemory start \ + --save-to-dir=/nethermind/diag/dotmemory \ + --service-output \ + ./nethermind -- "$@" +} + +start_dotnet_trace() { + echo "Starting dotnet-trace..." + + exec dotnet-trace collect \ + -o /nethermind/diag/dotnet.nettrace \ + --show-child-io \ + -- ./nethermind "$@" +} + +start_dottrace() { + echo "Starting dotTrace..." + + exec dottrace start \ + --framework=netcore \ + --profiling-type=timeline \ + --propagate-exit-code \ + --save-to=/nethermind/diag/dottrace \ + --service-output=on \ + -- ./nethermind "$@" +} + +case "$DIAG_WITH" in + "") + exec ./nethermind "$@" + ;; + dotmemory) + start_dotmemory "$@" + ;; + dotnet-trace) + start_dotnet_trace "$@" + ;; + dottrace) + start_dottrace "$@" + ;; + *) + printf '\e[31mUnknown DIAG_WITH value: %q\e[0m\n' "$DIAG_WITH" >&2 + exit 2 + ;; +esac diff --git a/scripts/hive-results.sh b/scripts/hive-results.sh index 412492122c08..126ed2c8c2c0 100755 --- a/scripts/hive-results.sh +++ b/scripts/hive-results.sh @@ -2,53 +2,50 @@ # SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited # SPDX-License-Identifier: LGPL-3.0-only -knownFailingTests=$(cat nethermind/scripts/known-failing-hive-tests.txt) -# in some test suits this test is a client setup and in some it's a master test. So just ignore it -launchTestName='client launch (nethermind)' +set -euo pipefail -shouldNotPass=() -shouldPass=() +known_fails=$(cat ./nethermind/scripts/known-failing-hive-tests.txt) +launch_test='client launch (nethermind)' # The launch test to ignore +should_pass=() +should_not_pass=() -for passed in "true" "false" -do +for passed in "true" "false"; do tmp=() - mapfile tmp < <(jq '.testCases | map_values(select(.summaryResult.pass == $p)) | map(.name) | .[]' --argjson p "$passed" -r $1) + mapfile tmp < <(jq '.testCases + | map_values(select(.summaryResult.pass == $p)) + | map(.name) + | .[]' \ + --argjson p "$passed" -r $1) IFS=$'\n' results=($(sort -f <<<"${tmp[*]}")); unset IFS - if ($passed == "true") - then - echo -e "\nPassed: ${#results[@]}\n" + if [[ "$passed" == "true" ]]; then + echo -e "\nPassed ${#results[@]}:\n" - for each in "${results[@]}"; - do - echo -e "\033[0;32m\u2714\033[0m $each" - if grep -qx "$each" <<< "$knownFailingTests" && [ "$each" != "$launchTestName" ]; then - shouldNotPass+=("$each") + for each in "${results[@]}"; do + if grep -Fqx "$each" <<< "$known_fails" && [[ "$each" != "$launch_test" ]]; then + should_not_pass+=("$each") + echo -e "\e[90m\u2714 $each\e[0m" + else + echo -e "\e[32m\u2714\e[0m $each" fi done else - echo -e "\nFailed: ${#results[@]}\n" - - for each in "${results[@]}"; - do - echo -e "\033[0;31m\u2716\033[0m $each" - if ! grep -qx "$each" <<< "$knownFailingTests" && [ "$each" != "$launchTestName" ]; then - shouldPass+=("$each") + echo -e "\nFailed ${#results[@]}:\n" + + for each in "${results[@]}"; do + if ! grep -Fqx "$each" <<< "$known_fails" && [[ "$each" != "$launch_test" ]]; then + should_pass+=("$each") + echo -e "\e[31m\u2716\e[0m $each" + else + echo -e "\e[90m\u2716 $each\e[0m" fi done fi done -if [ ${#shouldPass[@]} -gt 0 ]; then - echo -e "\nTests expected to pass but failed ${#shouldPass[@]}\n" - for each in "${shouldPass[@]}"; do echo -e "$each"; done -fi +echo -e "\n Unexpected passes: ${#should_not_pass[@]}" +echo " Unexpected fails: ${#should_pass[@]}" -if [ ${#shouldNotPass[@]} -gt 0 ]; then - echo -e "\nTests expected to fail but passed ${#shouldNotPass[@]}\n" - for each in "${shouldNotPass[@]}"; do echo -e "$each"; done -fi +[[ ${#should_pass[@]} -gt 0 || ${#should_not_pass[@]} -gt 0 ]] && exit 1 -if [[ ${#shouldNotPass[@]} -gt 0 || ${#shouldPass[@]} -gt 0 ]]; then - exit 1 -fi +exit 0 diff --git a/scripts/known-failing-hive-tests.txt b/scripts/known-failing-hive-tests.txt index 98ded03adc42..d9464669c7c3 100644 --- a/scripts/known-failing-hive-tests.txt +++ b/scripts/known-failing-hive-tests.txt @@ -1,65 +1,5 @@ # rpc-compat -debug_getRawBlock/get-invalid-number (nethermind) -debug_getRawHeader/get-invalid-number (nethermind) -debug_getRawReceipts/get-invalid-number (nethermind) -debug_getRawTransaction/get-invalid-hash (nethermind) -debug_getRawTransaction/get-tx (nethermind) -eth_call/call-revert-abi-error (nethermind) -eth_call/call-revert-abi-panic (nethermind) -eth_estimateGas/estimate-failed-call (nethermind) -eth_getBlockByHash/get-block-by-hash (nethermind) -eth_getBlockByNumber/get-block-london-fork (nethermind) -eth_getBlockByNumber/get-finalized (nethermind) -eth_getBlockByNumber/get-genesis (nethermind) -eth_getBlockByNumber/get-latest (nethermind) -eth_getBlockByNumber/get-safe (nethermind) -eth_getBlockReceipts/get-block-receipts-by-hash (nethermind) -eth_getBlockReceipts/get-block-receipts-n (nethermind) -eth_getProof/get-account-proof-with-storage (nethermind) -eth_getStorageAt/get-storage-invalid-key (nethermind) -eth_getStorageAt/get-storage-invalid-key-too-large (nethermind) -eth_getTransactionReceipt/get-legacy-contract (nethermind) -eth_getTransactionReceipt/get-legacy-input (nethermind) -eth_getTransactionReceipt/get-legacy-receipt (nethermind) -eth_sendRawTransaction/send-blob-tx (nethermind) -eth_simulateV1/ethSimulate-empty-with-block-num-set-firstblock (nethermind) -eth_simulateV1/ethSimulate-empty-with-block-num-set-plus1 (nethermind) -eth_simulateV1/ethSimulate-eth-send-should-not-produce-logs-on-revert (nethermind) -eth_simulateV1/ethSimulate-eth-send-should-produce-logs (nethermind) -eth_simulateV1/ethSimulate-eth-send-should-produce-more-logs-on-forward (nethermind) -eth_simulateV1/ethSimulate-eth-send-should-produce-no-logs-on-forward-revert (nethermind) -eth_simulateV1/ethSimulate-fee-recipient-receiving-funds (nethermind) -eth_simulateV1/ethSimulate-gas-fees-and-value-error-38014 (nethermind) -eth_simulateV1/ethSimulate-instrict-gas-38013 (nethermind) -eth_simulateV1/ethSimulate-logs (nethermind) -eth_simulateV1/ethSimulate-make-call-with-future-block (nethermind) -eth_simulateV1/ethSimulate-move-ecrecover-and-call (nethermind) -eth_simulateV1/ethSimulate-move-ecrecover-and-call-old-and-new (nethermind) -eth_simulateV1/ethSimulate-move-ecrecover-twice-and-call (nethermind) -eth_simulateV1/ethSimulate-move-to-address-itself-reference-38022 (nethermind) -eth_simulateV1/ethSimulate-move-two-accounts-to-same-38023 (nethermind) -eth_simulateV1/ethSimulate-move-two-non-precompiles-accounts-to-same (nethermind) -eth_simulateV1/ethSimulate-overflow-nonce (nethermind) -eth_simulateV1/ethSimulate-overflow-nonce-validation (nethermind) -eth_simulateV1/ethSimulate-override-address-twice (nethermind) -eth_simulateV1/ethSimulate-override-address-twice-in-separate-BlockStateCalls (nethermind) -eth_simulateV1/ethSimulate-run-gas-spending (nethermind) -eth_simulateV1/ethSimulate-run-out-of-gas-in-block-38015 (nethermind) -eth_simulateV1/ethSimulate-self-destructive-contract-produces-logs (nethermind) -eth_simulateV1/ethSimulate-send-eth-and-delegate-call (nethermind) -eth_simulateV1/ethSimulate-send-eth-and-delegate-call-to-eoa (nethermind) -eth_simulateV1/ethSimulate-send-eth-and-delegate-call-to-payble-contract (nethermind) -eth_simulateV1/ethSimulate-simple-more-params-validate (nethermind) -eth_simulateV1/ethSimulate-simple-no-funds (nethermind) -eth_simulateV1/ethSimulate-simple-no-funds-with-balance-querying (nethermind) -eth_simulateV1/ethSimulate-simple-send-from-contract (nethermind) -eth_simulateV1/ethSimulate-simple-send-from-contract-no-balance (nethermind) -eth_simulateV1/ethSimulate-transaction-too-high-nonce (nethermind) -eth_simulateV1/ethSimulate-try-to-move-non-precompile (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/scripts/superchain.py b/scripts/superchain.py index db54959aaf92..1e4311f81a0f 100644 --- a/scripts/superchain.py +++ b/scripts/superchain.py @@ -16,7 +16,7 @@ from zipfile import ZipFile SUPERCHAIN_REPOSITORY = "https://github.com/ethereum-optimism/superchain-registry/archive/refs/heads/main.zip" -IGNORED_CHAINS = ["arena-z-testnet", "creator-chain-testnet", "rehearsal-0-bn-0", "rehearsal-0-bn-1", "celo", "radius_testnet"] +IGNORED_CHAINS = ["arena-z-testnet", "creator-chain-testnet", "rehearsal-0-bn-0", "rehearsal-0-bn-1", "celo", "radius_testnet", "silent-data-mainnet"] IGNORED_L1S = ["sepolia-dev-0"] @@ -113,6 +113,7 @@ def to_nethermind_chainspec(chain_name, l1, superchain, chain, genesis): "graniteTimestamp": fmap(hex, (lookup(config, ["hardforks", "granite_time"]))), "holoceneTimestamp": fmap(hex, (lookup(config, ["hardforks", "holocene_time"]))), "isthmusTimestamp": fmap(hex, (lookup(config, ["hardforks", "isthmus_time"]))), + "jovianTimestamp": fmap(hex, (lookup(config, ["hardforks", "jovian_time"]))), "canyonBaseFeeChangeDenominator": str(lookup(config, ["optimism", "eip1559_denominator_canyon"])), "l1FeeRecipient": "0x420000000000000000000000000000000000001A", "l1BlockAddress": "0x4200000000000000000000000000000000000015", @@ -216,6 +217,7 @@ def to_nethermind_chainspec(chain_name, l1, superchain, chain, genesis): "opGraniteTransitionTimestamp": fmap(hex, (lookup(config, ["hardforks", "granite_time"]))), "opHoloceneTransitionTimestamp": fmap(hex, (lookup(config, ["hardforks", "holocene_time"]))), "opIsthmusTransitionTimestamp": fmap(hex, (lookup(config, ["hardforks", "isthmus_time"]))), + "opJovianTransitionTimestamp": fmap(hex, (lookup(config, ["hardforks", "jovian_time"]))), "eip7702TransitionTimestamp": fmap(hex, (lookup(config, ["hardforks", "isthmus_time"]))), "eip2537TransitionTimestamp": fmap(hex, (lookup(config, ["hardforks", "isthmus_time"]))), "eip2935TransitionTimestamp": fmap(hex, (lookup(config, ["hardforks", "isthmus_time"]))), @@ -305,7 +307,7 @@ def to_nethermind_runner(chain_name, l1, chain): "Sync": {"FastSync": True, "SnapSync": True, "FastSyncCatchUpHeightDelta": "10000000000"}, "Discovery": {"DiscoveryVersion": "V5"}, "JsonRpc": {"Enabled": True, "Port": 8545, "EnginePort": 8551}, - "Pruning": {"PruningBoundary": 192}, + "Pruning": {"PruningBoundary": 256}, "Blocks": {"SecondsPerSlot": lookup(chain, ["block_time"])}, "Merge": {"Enabled": True}, "Optimism": {"SequencerUrl": lookup(chain, ["sequencer_rpc"])}, diff --git a/scripts/sync-settings.py b/scripts/sync-settings.py index c4320f588139..6a92eac817f9 100644 --- a/scripts/sync-settings.py +++ b/scripts/sync-settings.py @@ -13,7 +13,7 @@ configs = { # fast sync section "mainnet": { - "url": "api.etherscan.io", + "url": "https://api.etherscan.io/v2/api?chainid=1", "blockReduced": 1000, "multiplierRequirement": 1000, "isPoS": True @@ -31,7 +31,7 @@ "isPoS": True }, "sepolia": { - "url": "api-sepolia.etherscan.io", + "url": "https://api.etherscan.io/v2/api?chainid=11155111", "blockReduced": 1000, "multiplierRequirement": 1000, "isPoS": True @@ -105,7 +105,7 @@ def fastBlocksSettings(configuration, apiUrl, blockReduced, multiplierRequiremen 'action': 'eth_blockNumber', 'apikey': key, } - response = requests.get(f'https://{apiUrl}/api', params=params) + response = requests.get(apiUrl, params=params) else: data_req = '{"id":0,"jsonrpc":"2.0","method": "eth_blockNumber","params": []}' response = requests.post(apiUrl, headers=APPLICATION_JSON, data=data_req) @@ -122,7 +122,7 @@ def fastBlocksSettings(configuration, apiUrl, blockReduced, multiplierRequiremen 'boolean': 'true', 'apikey': key, } - response = requests.get(f'https://{apiUrl}/api', params=params) + response = requests.get(apiUrl, params=params) else: data_req = f'{{"id":0,"jsonrpc":"2.0","method": "eth_getBlockByNumber","params": ["{hex(baseBlock)}", false]}}' response = requests.post(apiUrl, headers=APPLICATION_JSON, data=data_req) diff --git a/src/Nethermind/Chains/arena-z-mainnet.json.zst b/src/Nethermind/Chains/arena-z-mainnet.json.zst index e9113e55b220..10503dbf9dbf 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 cdb5309b4711..0051877e65c1 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 0e9e27a6116d..b2fba4976c6d 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 3c4823ae55b5..450c06d9d0d3 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 4bde8d050d70..c887ddeaf8db 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 d7bfae7bcff5..9b2e1d89607d 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 da7c1824172f..b9ada0eee633 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 4fae065e1580..4191037f6918 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 2c3309f68c10..1faf3709e11f 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 000000000000..222d78216eee Binary files /dev/null and b/src/Nethermind/Chains/celo-sep-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/chiado.json b/src/Nethermind/Chains/chiado.json index 1b178a856217..60a243dbe2c5 100644 --- a/src/Nethermind/Chains/chiado.json +++ b/src/Nethermind/Chains/chiado.json @@ -91,7 +91,7 @@ "eip7623TransitionTimestamp": "0x67c96e4c", "eip7702TransitionTimestamp": "0x67c96e4c", "eip4844FeeCollectorTransitionTimestamp": "0x67c96e4c", - "depositContractAddress": "0xb97036A26259B7147018913bD58a774cf91acf25", + "depositContractAddress": "0xb97036a26259b7147018913bd58a774cf91acf25", "blobSchedule": [ { "name": "cancun", diff --git a/src/Nethermind/Chains/cyber-mainnet.json.zst b/src/Nethermind/Chains/cyber-mainnet.json.zst index c39e500211bb..46cb69635599 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 214f3c4ca664..244f260d8941 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 ad0ccadf414f..9617a6705bd5 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 f23bd1dc90f7..3cbad235b874 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 4bc6ebf7aa2f..1ce360a87a87 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/foundation.json b/src/Nethermind/Chains/foundation.json index 554155c6879a..de609b935828 100644 --- a/src/Nethermind/Chains/foundation.json +++ b/src/Nethermind/Chains/foundation.json @@ -199,6 +199,14 @@ "eip7251TransitionTimestamp": "0x681b3057", "eip7702TransitionTimestamp": "0x681b3057", "eip7623TransitionTimestamp": "0x681b3057", + "eip7594TransitionTimestamp": "0x6930b057", + "eip7823TransitionTimestamp": "0x6930b057", + "eip7825TransitionTimestamp": "0x6930b057", + "eip7883TransitionTimestamp": "0x6930b057", + "eip7918TransitionTimestamp": "0x6930b057", + "eip7934TransitionTimestamp": "0x6930b057", + "eip7939TransitionTimestamp": "0x6930b057", + "eip7951TransitionTimestamp": "0x6930b057", "depositContractAddress": "0x00000000219ab540356cbb839cbe05303d7705fa", "terminalTotalDifficulty": "C70D808A128D7380000", "blobSchedule": [ @@ -208,6 +216,20 @@ "target": 6, "max": 9, "baseFeeUpdateFraction": "0x4c6964" + }, + { + "name": "bpo1", + "timestamp": "0x69383057", + "target": 10, + "max": 15, + "baseFeeUpdateFraction": "0x7f5a51" + }, + { + "name": "bpo2", + "timestamp": "0x695db057", + "target": 14, + "max": 21, + "baseFeeUpdateFraction": "0xb24b3f" } ] }, @@ -3908,8 +3930,8 @@ "enode://fdd1b9bb613cfbc200bba17ce199a9490edc752a833f88d4134bf52bb0d858aa5524cb3ec9366c7a4ef4637754b8b15b5dc913e4ed9fdb6022f7512d7b63f181@212.47.247.103:30303", "enode://cc26c9671dffd3ee8388a7c8c5b601ae9fe75fc0a85cedb72d2dd733d5916fad1d4f0dcbebad5f9518b39cc1f96ba214ab36a7fa5103aaf17294af92a89f227b@52.79.241.155:30303", "enode://140872ce4eee37177fbb7a3c3aa4aaebe3f30bdbf814dd112f6c364fc2e325ba2b6a942f7296677adcdf753c33170cb4999d2573b5ff7197b4c1868f25727e45@52.78.149.82:30303", - "enode://2b252ab6a1d0f971d9722cb839a42cb81db019ba44c08754628ab4a823487071b5695317c8ccd085219c3a03af063495b2f1da8d18218da2d6a82981b45e6ffc@65.108.70.101:30303", - "enode://4aeb4ab6c14b23e2c4cfdce879c04b0748a20d8e9b59e25ded2a08143e265c6c25936e74cbc8e641e3312ca288673d91f2f93f8e277de3cfa444ecdaaf982052@157.90.35.166:30303" + "enode://2b252ab6a1d0f971d9722cb839a42cb81db019ba44c08754628ab4a823487071b5695317c8ccd085219c3a03af063495b2f1da8d18218da2d6a82981b45e6ffc@65.108.70.101:30303", + "enode://4aeb4ab6c14b23e2c4cfdce879c04b0748a20d8e9b59e25ded2a08143e265c6c25936e74cbc8e641e3312ca288673d91f2f93f8e277de3cfa444ecdaaf982052@157.90.35.166:30303" ], "accounts": { "0x0000000000000000000000000000000000000001": { diff --git a/src/Nethermind/Chains/fraxtal-mainnet.json.zst b/src/Nethermind/Chains/fraxtal-mainnet.json.zst index ed91032e2d24..debfe6488fec 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 09b2dcbc7b2c..9c0e92a9b5b5 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 492b9ee72307..fe59a54d0e37 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 44b95dd869f6..3ed45d0397eb 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 635c53a69d8d..ffdccc305982 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 4962aeaa8a9f..0c57748609fd 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 355e98d01863..100be96e49e1 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 d4d361d71849..6fe008cb3b89 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 e11f1a1546de..be4863971051 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 b861f8eab4cf..6c902a374cad 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 e5e0ef57af6d..2d100695c766 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 118816e9466f..7dc989ec6092 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 a09d22cca6af..f422775e3f91 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 a3675cb52591..d8656b6d2ce9 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 767212c16dff..2ca717cfc1fe 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 df4f98be4c7d..19d3cdf32984 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 a3dcaf0946d9..ed6795c8ca25 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 b3704e1520dd..470120f31209 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 e30576647c0b..a444896f3912 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 2106c3170010..833bbf0818ac 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 13d9a20574fc..82c2f9f6dfa3 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 457eeee0f749..45fd5a197893 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 38de4e507a15..a5e9f89e056d 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 ac9c1856ddfa..c4259e7c8197 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 9b1cdf012dbb..f2bef6a125e7 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 e7ca96a6cc01..a5d41913ef9f 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 e6433f92b1d9..19fde114fbeb 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 1a3e94b1c9d1..39a352cfee12 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 a0d2fb851847..c7b02685b219 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 83fc7ea3d943..5c1d2f156dec 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/snax-mainnet.json.zst b/src/Nethermind/Chains/snax-mainnet.json.zst deleted file mode 100644 index e003e16b078f..000000000000 Binary files a/src/Nethermind/Chains/snax-mainnet.json.zst and /dev/null differ diff --git a/src/Nethermind/Chains/soneium-mainnet.json.zst b/src/Nethermind/Chains/soneium-mainnet.json.zst index 101045bd9729..861b795eb4ba 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 b27bf8936d32..55a1cebe057b 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/spaceneth.json b/src/Nethermind/Chains/spaceneth.json index 8496e7d10621..a00a52e9644c 100644 --- a/src/Nethermind/Chains/spaceneth.json +++ b/src/Nethermind/Chains/spaceneth.json @@ -12,6 +12,9 @@ "accountStartNonce": "0x0", "maximumExtraDataSize": "0x20", "minGasLimit": "0x1388", + "maxCodeSize": "0x6000", + "maxCodeSizeTransition": "0x0", + "chainID": "0x63", "networkID" : "0x63", "eip140Transition": "0x0", "eip145Transition": "0x0", @@ -87,6 +90,36 @@ ], "accounts": { "0000000000000000000000000000000000000000": { "balance": "1" }, + "000F3df6D732807Ef1319fB7B8bB8522d0Beac02": { + "comment": "EIP-4788: Beacon block root in the EVM", + "balance": "0", + "nonce": "1", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500" + }, + "0000F90827F1C53a10cb7A02335B175320002935": { + "comment": "EIP-2935: Serve historical block hashes from state", + "balance": "0", + "nonce": "1", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500" + }, + "00000961Ef480Eb55e80D19ad83579A64c007002": { + "comment": "EIP-7002: Execution layer triggerable withdrawals", + "balance": "0", + "nonce": "1", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460cb5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101f457600182026001905f5b5f82111560685781019083028483029004916001019190604d565b909390049250505036603814608857366101f457346101f4575f5260205ff35b34106101f457600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260385f601437604c5fa0600101600355005b6003546002548082038060101160df575060105b5f5b8181146101835782810160030260040181604c02815460601b8152601401816001015481526020019060020154807fffffffffffffffffffffffffffffffff00000000000000000000000000000000168252906010019060401c908160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c81600101535360010160e1565b910180921461019557906002556101a0565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14156101cd57505f5b6001546002828201116101e25750505f6101e8565b01600290035b5f555f600155604c025ff35b5f5ffd", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + } + }, + "0000BBdDc7CE488642fb579F8B00f3a590007251": { + "comment": "EIP-7251: Increase the MAX_EFFECTIVE_BALANCE (consolidations)", + "balance": "0", + "nonce": "1", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + } + }, "0000000000000000000000000000000000000001": { "balance": "1", "nonce": "0", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, "0000000000000000000000000000000000000002": { "balance": "1", "nonce": "0", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, "0000000000000000000000000000000000000003": { "balance": "1", "nonce": "0", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, diff --git a/src/Nethermind/Chains/sseed-mainnet.json.zst b/src/Nethermind/Chains/sseed-mainnet.json.zst index 1b4631ce23a0..28bfeda14c46 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/surge-hoodi.json b/src/Nethermind/Chains/surge-hoodi.json index f5ab088db792..2867e847018c 100644 --- a/src/Nethermind/Chains/surge-hoodi.json +++ b/src/Nethermind/Chains/surge-hoodi.json @@ -3,18 +3,19 @@ "engine": { "Taiko": { "ontakeTransition": "0x1", - "taikoL2Address": "0x7633750000000000000000000000000000010001", - "UseSurgeGasPriceOracle": false + "pacayaTransition": "0x1", + "UseSurgeGasPriceOracle": true, + "taikoL2Address": "0x7633750000000000000000000000000000010001" } }, "params": { "eip150Transition": "0x0", + "eip155Transition": "0x0", "eip160Transition": "0x0", "eip161abcTransition": "0x0", "eip161dTransition": "0x0", - "eip155Transition": "0x0", - "maxCodeSizeTransition": "0x0", "maxCodeSize": "0x6000", + "maxCodeSizeTransition": "0x0", "maximumExtraDataSize": "0x20", "eip140Transition": "0x0", "eip211Transition": "0x0", @@ -36,10 +37,10 @@ "eip2929Transition": "0x0", "eip2930Transition": "0x0", "eip1559Transition": "0x0", + "eip3198Transition": "0x0", "eip3238Transition": "0x0", "eip3529Transition": "0x0", "eip3541Transition": "0x0", - "eip3198Transition": "0x0", "eip3651TransitionTimestamp": "0x0", "eip3855TransitionTimestamp": "0x0", "eip3860TransitionTimestamp": "0x0", @@ -62,18 +63,18 @@ }, "accounts": { "0x3bc256069FF9af461F3e04494A3ece3f62F183fC": { - "balance": "0x2386f26fc10000" + "balance": "0x3635c9adc5dea00000" }, "0x0763375000000000000000000000000000000006": { - "contractName": "SharedAddressManagerImpl", + "contractName": "SharedResolverImpl", "storage": { "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000003bc256069ff9af461f3e04494a3ece3f62f183fc" }, - "code": "0x608060405260043610610110575f3560e01c806352d1902d1161009d5780638da5cb5b116100625780638da5cb5b146102d7578063a86f9d9e146102f4578063d8f4648f14610313578063e07baba614610332578063f2fde38b14610371575f5ffd5b806352d1902d146102595780635c975abb1461027b578063715018a61461029b5780638456cb59146102af5780638abf6077146102c3575f5ffd5b80633659cfe6116100e35780633659cfe6146101d55780633ab76e9f146101f45780633eb6b8cf146102135780633f4ba83a146102325780634f1ef28614610246575f5ffd5b8063069489a21461011457806319ab453c1461012a57806328f713cc146101495780633075db56146101b1575b5f5ffd5b34801561011f575f5ffd5b50610128610390565b005b348015610135575f5ffd5b5061012861014436600461105d565b610440565b348015610154575f5ffd5b50610194610163366004611094565b67ffffffffffffffff919091165f90815260c96020908152604080832093835292905220546001600160a01b031690565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156101bc575f5ffd5b506101c5610518565b60405190151581526020016101a8565b3480156101e0575f5ffd5b506101286101ef36600461105d565b610530565b3480156101ff575f5ffd5b50606554610194906001600160a01b031681565b34801561021e575f5ffd5b5061019461022d3660046110cb565b6105f7565b34801561023d575f5ffd5b5061012861060d565b610128610254366004611118565b610621565b348015610264575f5ffd5b5061026d6106d6565b6040519081526020016101a8565b348015610286575f5ffd5b506101c5609754610100900460ff1660021490565b3480156102a6575f5ffd5b50610128610787565b3480156102ba575f5ffd5b50610128610798565b3480156102ce575f5ffd5b506101946107b3565b3480156102e2575f5ffd5b506033546001600160a01b0316610194565b3480156102ff575f5ffd5b5061019461030e3660046111de565b6107c1565b34801561031e575f5ffd5b5061012861032d366004611208565b6107cd565b34801561033d575f5ffd5b506097546103589062010000900467ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016101a8565b34801561037c575f5ffd5b5061012861038b36600461105d565b6108ab565b610398610921565b5f54600290610100900460ff161580156103b857505f5460ff8083169116105b6103dd5760405162461bcd60e51b81526004016103d490611245565b60405180910390fd5b5f8054606580546001600160a01b0319163017905561ffff191660ff83169081176101001761ff0019169091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a150565b5f54610100900460ff161580801561045e57505f54600160ff909116105b806104775750303b15801561047757505f5460ff166001145b6104935760405162461bcd60e51b81526004016103d490611245565b5f805460ff1916600117905580156104b4575f805461ff0019166101001790555b6104bd8261097b565b606580546001600160a01b031916301790558015610514575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b5f600261052760975460ff1690565b60ff1614905090565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000000061630036105785760405162461bcd60e51b81526004016103d490611293565b7f00000000000000000000000007633750000000000000000000000000000000066001600160a01b03166105aa6109d9565b6001600160a01b0316146105d05760405162461bcd60e51b81526004016103d4906112df565b6105d9816109f4565b604080515f808252602082019092526105f4918391906109fc565b50565b5f610603848484610b6b565b90505b9392505050565b610615610bbe565b61061f335f610c4f565b565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000000061630036106695760405162461bcd60e51b81526004016103d490611293565b7f00000000000000000000000007633750000000000000000000000000000000066001600160a01b031661069b6109d9565b6001600160a01b0316146106c15760405162461bcd60e51b81526004016103d4906112df565b6106ca826109f4565b610514828260016109fc565b5f306001600160a01b037f000000000000000000000000076337500000000000000000000000000000000616146107755760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c000000000000000060648201526084016103d4565b505f5160206114185f395f51905f5290565b61078f610921565b61061f5f610c68565b6107a0610cb9565b6107a8610cd2565b61061f336001610c4f565b5f6107bc6109d9565b905090565b5f610606468484610b6b565b6107d5610921565b67ffffffffffffffff83165f90815260c9602090815260408083208584529091529020546001600160a01b039081169082168190036108275760405163a883089360e01b815260040160405180910390fd5b67ffffffffffffffff84165f81815260c96020908152604080832087845282529182902080546001600160a01b0319166001600160a01b038781169182179092558351908152908516918101919091528592917f500dcd607a98daece9bccc2511bf6032471252929de73caf507aae0e082f8453910160405180910390a350505050565b6108b3610921565b6001600160a01b0381166109185760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016103d4565b6105f481610c68565b6033546001600160a01b0316331461061f5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103d4565b5f54610100900460ff166109a15760405162461bcd60e51b81526004016103d49061132b565b6109a9610d43565b6109c76001600160a01b038216156109c15781610c68565b33610c68565b506097805461ff001916610100179055565b5f5160206114185f395f51905f52546001600160a01b031690565b6105f4610921565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff1615610a3457610a2f83610d69565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015610a8e575060408051601f3d908101601f19168201909252610a8b91810190611376565b60015b610af15760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b60648201526084016103d4565b5f5160206114185f395f51905f528114610b5f5760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b60648201526084016103d4565b50610a2f838383610e04565b5f610b768484610e2e565b905081158015610b8d57506001600160a01b038116155b1561060657604051632b0d65db60e01b815267ffffffffffffffff85166004820152602481018490526044016103d4565b610bd2609754610100900460ff1660021490565b610bef5760405163bae6e2a960e01b815260040160405180910390fd5b6097805461010069ffffffffffffffffff0019909116620100004267ffffffffffffffff1602171790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa906020015b60405180910390a1565b604051630c2b8f8f60e11b815260040160405180910390fd5b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b60405163a87dd7cf60e01b815260040160405180910390fd5b610ce6609754610100900460ff1660021490565b15610d045760405163bae6e2a960e01b815260040160405180910390fd5b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a25890602001610c45565b5f54610100900460ff1661061f5760405162461bcd60e51b81526004016103d49061132b565b6001600160a01b0381163b610dd65760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016103d4565b5f5160206114185f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b610e0d83610ed9565b5f82511180610e195750805b15610a2f57610e288383610f18565b50505050565b6065545f906001600160a01b031680610e5a57604051638ed88b2560e01b815260040160405180910390fd5b604051630a3dc4f360e21b815267ffffffffffffffff85166004820152602481018490526001600160a01b038216906328f713cc90604401602060405180830381865afa158015610ead573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ed1919061138d565b949350505050565b610ee281610d69565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606061060683836040518060600160405280602781526020016114386027913960605f5f856001600160a01b031685604051610f5491906113ca565b5f60405180830381855af49150503d805f8114610f8c576040519150601f19603f3d011682016040523d82523d5f602084013e610f91565b606091505b5091509150610fa286838387610fac565b9695505050505050565b6060831561101a5782515f03611013576001600160a01b0385163b6110135760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016103d4565b5081610ed1565b610ed1838381511561102f5781518083602001fd5b8060405162461bcd60e51b81526004016103d491906113e5565b6001600160a01b03811681146105f4575f5ffd5b5f6020828403121561106d575f5ffd5b813561060681611049565b803567ffffffffffffffff8116811461108f575f5ffd5b919050565b5f5f604083850312156110a5575f5ffd5b6110ae83611078565b946020939093013593505050565b8035801515811461108f575f5ffd5b5f5f5f606084860312156110dd575f5ffd5b6110e684611078565b9250602084013591506110fb604085016110bc565b90509250925092565b634e487b7160e01b5f52604160045260245ffd5b5f5f60408385031215611129575f5ffd5b823561113481611049565b9150602083013567ffffffffffffffff81111561114f575f5ffd5b8301601f8101851361115f575f5ffd5b803567ffffffffffffffff81111561117957611179611104565b604051601f8201601f19908116603f0116810167ffffffffffffffff811182821017156111a8576111a8611104565b6040528181528282016020018710156111bf575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f5f604083850312156111ef575f5ffd5b823591506111ff602084016110bc565b90509250929050565b5f5f5f6060848603121561121a575f5ffd5b61122384611078565b925060208401359150604084013561123a81611049565b809150509250925092565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f60208284031215611386575f5ffd5b5051919050565b5f6020828403121561139d575f5ffd5b815161060681611049565b5f5b838110156113c25781810151838201526020016113aa565b50505f910152565b5f82516113db8184602087016113a8565b9190910192915050565b602081525f82518060208401526114038160408501602087016113a8565b601f01601f1916919091016040019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220e7291c88c86669de0606134d51e7ba077712eb05e1336d7678ac3d3e5bf2591464736f6c634300081b0033", + "code": "0x6080604052600436106100e4575f3560e01c80635c975abb116100875780638abf6077116100575780638abf6077146102275780638da5cb5b1461023b578063b490d87f14610258578063f2fde38b14610277575f5ffd5b80635c975abb146101c05780636c6563f6146101e0578063715018a6146101ff5780638456cb5914610213575f5ffd5b80633659cfe6116100c25780633659cfe6146101585780633f4ba83a146101775780634f1ef2861461018b57806352d1902d1461019e575f5ffd5b806304f3bcec146100e857806319ab453c146101135780633075db5614610134575b5f5ffd5b3480156100f3575f5ffd5b50305b6040516001600160a01b0390911681526020015b60405180910390f35b34801561011e575f5ffd5b5061013261012d366004610db7565b610296565b005b34801561013f575f5ffd5b506101486103a8565b604051901515815260200161010a565b348015610163575f5ffd5b50610132610172366004610db7565b6103c0565b348015610182575f5ffd5b50610132610487565b610132610199366004610de4565b610513565b3480156101a9575f5ffd5b506101b26105c8565b60405190815260200161010a565b3480156101cb575f5ffd5b50610148609754610100900460ff1660021490565b3480156101eb575f5ffd5b506100f66101fa366004610ea8565b610679565b34801561020a575f5ffd5b506101326106ca565b34801561021e575f5ffd5b506101326106db565b348015610232575f5ffd5b506100f6610762565b348015610246575f5ffd5b506033546001600160a01b03166100f6565b348015610263575f5ffd5b50610132610272366004610ee2565b610770565b348015610282575f5ffd5b50610132610291366004610db7565b6107f1565b5f54610100900460ff16158080156102b457505f54600160ff909116105b806102cd5750303b1580156102cd57505f5460ff166001145b6103355760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b5f805460ff191660011790558015610356575f805461ff0019166101001790555b61035f82610867565b80156103a4575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b5f60026103b760975460ff1690565b60ff1614905090565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000000061630036104085760405162461bcd60e51b815260040161032c90610f14565b7f00000000000000000000000007633750000000000000000000000000000000066001600160a01b031661043a6108c5565b6001600160a01b0316146104605760405162461bcd60e51b815260040161032c90610f60565b610469816108e0565b604080515f80825260208201909252610484918391906108e8565b50565b61049b609754610100900460ff1660021490565b6104b85760405163bae6e2a960e01b815260040160405180910390fd5b6104c0610a57565b6104d46097805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610511335f610a70565b565b6001600160a01b037f000000000000000000000000076337500000000000000000000000000000000616300361055b5760405162461bcd60e51b815260040161032c90610f14565b7f00000000000000000000000007633750000000000000000000000000000000066001600160a01b031661058d6108c5565b6001600160a01b0316146105b35760405162461bcd60e51b815260040161032c90610f60565b6105bc826108e0565b6103a4828260016108e8565b5f306001600160a01b037f000000000000000000000000076337500000000000000000000000000000000616146106675760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c0000000000000000606482015260840161032c565b505f51602061107e5f395f51905f5290565b5f83815260c9602090815260408083208584529091529020546001600160a01b0316801515806106a65750815b6106c357604051631692906160e11b815260040160405180910390fd5b9392505050565b6106d2610a89565b6105115f610ae3565b6106ef609754610100900460ff1660021490565b1561070d5760405163bae6e2a960e01b815260040160405180910390fd5b610715610a57565b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a1610511336001610a70565b5f61076b6108c5565b905090565b610778610a89565b5f83815260c96020908152604080832085845282529182902080546001600160a01b038581166001600160a01b0319831681179093558451928352169181018290529091849186917f3fd0559a7b01eb7106f9d9ce79ec76bb44f608a295878cce50856e54dba83d35910160405180910390a350505050565b6107f9610a89565b6001600160a01b03811661085e5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b606482015260840161032c565b61048481610ae3565b5f54610100900460ff1661088d5760405162461bcd60e51b815260040161032c90610fac565b610895610b34565b6108b36001600160a01b038216156108ad5781610ae3565b33610ae3565b506097805461ff001916610100179055565b5f51602061107e5f395f51905f52546001600160a01b031690565b610484610a89565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156109205761091b83610b5a565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa92505050801561097a575060408051601f3d908101601f1916820190925261097791810190610ff7565b60015b6109dd5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b606482015260840161032c565b5f51602061107e5f395f51905f528114610a4b5760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b606482015260840161032c565b5061091b838383610bf5565b60405163a87dd7cf60e01b815260040160405180910390fd5b604051630c2b8f8f60e11b815260040160405180910390fd5b6033546001600160a01b031633146105115760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161032c565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f54610100900460ff166105115760405162461bcd60e51b815260040161032c90610fac565b6001600160a01b0381163b610bc75760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161032c565b5f51602061107e5f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b610bfe83610c1f565b5f82511180610c0a5750805b1561091b57610c198383610c5e565b50505050565b610c2881610b5a565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606106c3838360405180606001604052806027815260200161109e6027913960605f5f856001600160a01b031685604051610c9a9190611030565b5f60405180830381855af49150503d805f8114610cd2576040519150601f19603f3d011682016040523d82523d5f602084013e610cd7565b606091505b5091509150610ce886838387610cf2565b9695505050505050565b60608315610d605782515f03610d59576001600160a01b0385163b610d595760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161032c565b5081610d6a565b610d6a8383610d72565b949350505050565b815115610d825781518083602001fd5b8060405162461bcd60e51b815260040161032c919061104b565b80356001600160a01b0381168114610db2575f5ffd5b919050565b5f60208284031215610dc7575f5ffd5b6106c382610d9c565b634e487b7160e01b5f52604160045260245ffd5b5f5f60408385031215610df5575f5ffd5b610dfe83610d9c565b9150602083013567ffffffffffffffff811115610e19575f5ffd5b8301601f81018513610e29575f5ffd5b803567ffffffffffffffff811115610e4357610e43610dd0565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610e7257610e72610dd0565b604052818152828201602001871015610e89575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f5f5f60608486031215610eba575f5ffd5b833592506020840135915060408401358015158114610ed7575f5ffd5b809150509250925092565b5f5f5f60608486031215610ef4575f5ffd5b8335925060208401359150610f0b60408501610d9c565b90509250925092565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f60208284031215611007575f5ffd5b5051919050565b5f5b83811015611028578181015183820152602001611010565b50505f910152565b5f825161104181846020870161100e565b9190910192915050565b602081525f825180602084015261106981604085016020870161100e565b601f01601f1916919091016040019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212205acfcd4888784dee86ffa66457557af2e86169bc3c0d96c2503aa0ac7da2852064736f6c634300081b0033", "balance": "0x0" }, "0x7633750000000000000000000000000000000006": { - "contractName": "SharedAddressManager", + "contractName": "SharedResolver", "storage": { "0x0000000000000000000000000000000000000000000000000000000000000097": "0x0000000000000000000000000000000000000000000000000000000000000101", "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", @@ -86,9 +87,10 @@ "0xfbdfd0808fe2274f7f1f872d2e350e8efd4bafef4d9c8228889ec228c40f52b5": "0x0000000000000000000000000763375000000000000000000000000000010096", "0x53b82a1bf878a794328f1bf688a5c65f25ced9560dd767cc7ba8ab5dcf1351de": "0x0000000000000000000000000763375000000000000000000000000000010097", "0x0222d5b81afac84a464e606cb81b37e4a2ba868744da770ad81edcd4b50f12bc": "0x0000000000000000000000000763375000000000000000000000000000010098", + "0x2cd27659d78a42ff2ee81164df32f17f477725e604d6a8acc7b99ff0d62a818f": "0x0000000000000000000000007633750000000000000000000000000000010001", "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0763375000000000000000000000000000000006" }, - "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea26469706673582212205339f45a82df35ebd66aff7c8a93c20457e46388cef179bebf6908c53edcb0ec64736f6c634300081b0033", + "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033", "balance": "0x0" }, "0x0763375000000000000000000000000000000001": { @@ -96,7 +98,7 @@ "storage": { "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000003bc256069ff9af461f3e04494a3ece3f62f183fc" }, - "code": "0x608060405260043610610207575f3560e01c806382b5e88911610113578063b8acae0e1161009d578063d1aaa5df1161006d578063d1aaa5df14610621578063e07baba614610641578063eefbf17e1461067e578063f09a4016146106a4578063f2fde38b146106c3575f5ffd5b8063b8acae0e14610585578063be880c81146105a4578063c012fa77146105ba578063d0496d6a146105d9575f5ffd5b80638e3881a9116100e35780638e3881a9146104d4578063913b16cb146105125780639efc7a2e14610531578063a730cdfb14610550578063a86f9d9e14610566575f5ffd5b806382b5e889146104705780638456cb591461048f5780638abf6077146104a35780638da5cb5b146104b7575f5ffd5b80633f4ba83a116101945780635862f6e1116101645780635862f6e1146103ca5780635c975abb146103e957806360620c6b14610409578063715018a6146104285780637cbadfaa1461043c575f5ffd5b80633f4ba83a1461036b578063422770fa1461037f5780634f1ef286146103a357806352d1902d146103b6575f5ffd5b80633075db56116101da5780633075db56146102975780633659cfe6146102bb5780633ab76e9f146102da5780633c6cf473146103115780633eb6b8cf1461034c575f5ffd5b80630432873c1461020b578063069489a21461022c5780631bdb0037146102405780632035065e1461026a575b5f5ffd5b348015610216575f5ffd5b5061022a610225366004612ea6565b6106e2565b005b348015610237575f5ffd5b5061022a6109e0565b61025361024e366004612ef4565b610a99565b604051610261929190613080565b60405180910390f35b348015610275575f5ffd5b50610289610284366004613098565b610e4f565b604051610261929190613155565b3480156102a2575f5ffd5b506102ab61130f565b6040519015158152602001610261565b3480156102c6575f5ffd5b5061022a6102d53660046131a4565b611327565b3480156102e5575f5ffd5b506065546102f9906001600160a01b031681565b6040516001600160a01b039091168152602001610261565b34801561031c575f5ffd5b5061033f61032b3660046131bf565b60ca6020525f908152604090205460ff1681565b60405161026191906131d6565b348015610357575f5ffd5b506102f96103663660046131fa565b6113ee565b348015610376575f5ffd5b5061022a611404565b34801561038a575f5ffd5b5061039562030d4081565b604051908152602001610261565b61022a6103b13660046132fd565b611418565b3480156103c1575f5ffd5b506103956114d1565b3480156103d5575f5ffd5b506102ab6103e4366004613098565b611583565b3480156103f4575f5ffd5b506102ab609754610100900460ff1660021490565b348015610414575f5ffd5b506102ab610423366004612ef4565b6115ee565b348015610433575f5ffd5b5061022a6116bc565b348015610447575f5ffd5b5061045b6104563660046131bf565b6116cd565b60405163ffffffff9091168152602001610261565b34801561047b575f5ffd5b5061022a61048a3660046131a4565b6116eb565b34801561049a575f5ffd5b5061022a61176b565b3480156104ae575f5ffd5b506102f9611786565b3480156104c2575f5ffd5b506033546001600160a01b03166102f9565b3480156104df575f5ffd5b506104f36104ee366004613349565b611794565b6040805192151583526001600160a01b03909116602083015201610261565b34801561051d575f5ffd5b5061022a61052c366004612ef4565b6117c0565b34801561053c575f5ffd5b5061022a61054b366004613098565b6119af565b34801561055b575f5ffd5b5061045b6201d4c081565b348015610571575f5ffd5b506102f9610580366004613362565b611cb3565b348015610590575f5ffd5b506102ab61059f366004613098565b611cbf565b3480156105af575f5ffd5b5061045b620c350081565b3480156105c5575f5ffd5b506103956105d4366004613485565b611d20565b3480156105e4575f5ffd5b506105ed611d4f565b60408051825181526020808401516001600160a01b031690820152918101516001600160401b031690820152606001610261565b34801561062c575f5ffd5b5061039561063b3660046131bf565b60031890565b34801561064c575f5ffd5b50609754610666906201000090046001600160401b031681565b6040516001600160401b039091168152602001610261565b348015610689575f5ffd5b5060c95461066690600160401b90046001600160401b031681565b3480156106af575f5ffd5b5061022a6106be3660046134b6565b611df6565b3480156106ce575f5ffd5b5061022a6106dd3660046131a4565b611ebd565b6106f260e0830160c08401613349565b46816001600160401b03161461071b57604051631c6c777560e31b815260040160405180910390fd5b61072b60a0840160808501613349565b6001600160401b0381161580610749575046816001600160401b0316145b1561076757604051631c6c777560e31b815260040160405180910390fd5b61077b609754610100900460ff1660021490565b156107995760405163bae6e2a960e01b815260040160405180910390fd5b60026107a760975460ff1690565b60ff16036107c85760405163dfc60d8560e01b815260040160405180910390fd5b6107d26002611f33565b5f6107df6105d4866134e2565b90506107ec816001611f49565b6107fa856101200135611f9b565b610817576040516335856fbd60e21b815260040160405180910390fd5b5f61083c866108376d7369676e616c5f7365727669636560901b5f611cb3565b61203c565b15610887576108808661012001356188b860405180602001604052805f8152508960e001602081019061086f91906131a4565b6001600160a01b0316929190612133565b9050610901565b61089760608701604088016134ed565b63ffffffff1615806108a65750845b80156108d457506108be610100870160e088016131a4565b6001600160a01b0316336001600160a01b031614155b156108f2576040516372b6e1c360e11b815260040160405180910390fd5b6108fe86835a5f612170565b90505b801561091757610912826002612294565b6109ce565b84156109b557610928826003612294565b6109436d7369676e616c5f7365727669636560901b5f611cb3565b60405163019b28af60e61b81526003841860048201526001600160a01b0391909116906366ca2bc0906024016020604051808303815f875af115801561098b573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109af9190613506565b506109ce565b60405163161e3ead60e01b815260040160405180910390fd5b50506109da6001611f33565b50505050565b6109e8612351565b5f54600290610100900460ff16158015610a0857505f5460ff8083169116105b610a2d5760405162461bcd60e51b8152600401610a249061351d565b60405180910390fd5b5f805460c9805467ffffffffffffffff1916905560cd82905560ce82905561ffff191660ff83169081176101001761ff0019169091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a150565b60408051610160810182525f8082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e0820183905261010082018390526101208201839052610140820152610aff60c0840160a085016131a4565b6001600160a01b038116610b265760405163538ba4f960e01b815260040160405180910390fd5b610b37610100850160e086016131a4565b6001600160a01b038116610b5e5760405163538ba4f960e01b815260040160405180910390fd5b610b6e60e0860160c08701613349565b6001600160401b0381161580610b8c575046816001600160401b0316145b15610baa57604051631c6c777560e31b815260040160405180910390fd5b610bbe609754610100900460ff1660021490565b15610bdc5760405163bae6e2a960e01b815260040160405180910390fd5b6002610bea60975460ff1690565b60ff1603610c0b5760405163dfc60d8560e01b815260040160405180910390fd5b610c156002611f33565b610c2560608701604088016134ed565b63ffffffff165f03610c6d57610c416040870160208801613349565b6001600160401b031615610c685760405163c9f5178760e01b815260040160405180910390fd5b610c95565b610c76866123ab565b5f03610c95576040516308c2ad5360e01b815260040160405180910390fd5b5f610ca96104ee60e0890160c08a01613349565b50905080610cca57604051631c6c777560e31b815260040160405180910390fd5b34610cdb6040890160208a01613349565b610cf3906001600160401b03166101208a013561357f565b14610d1157604051634ac2abdf60e11b815260040160405180910390fd5b610d1a876134e2565b60c98054919650600160401b9091046001600160401b0316906008610d3e83613592565b82546101009290920a6001600160401b03818102199093169183160217909155908116865233606087015246166080860152610d7985611d20565b9550857fe33fd33b4f45b95b1c196242240c5b5233129d724b578f95b66ce8d8aae9351786604051610dab91906135bc565b60405180910390a2610dce6d7369676e616c5f7365727669636560901b5f611cb3565b6001600160a01b03166366ca2bc0876040518263ffffffff1660e01b8152600401610dfb91815260200190565b6020604051808303815f875af1158015610e17573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610e3b9190613506565b5050610e476001611f33565b505050915091565b5f5f610e65609754610100900460ff1660021490565b15610e835760405163bae6e2a960e01b815260040160405180910390fd5b6002610e9160975460ff1690565b60ff1603610eb25760405163dfc60d8560e01b815260040160405180910390fd5b610ebc6002611f33565b5f5a905046610ed160e0880160c08901613349565b6001600160401b031614610ef857604051631c6c777560e31b815260040160405180910390fd5b610f0860a0870160808801613349565b6001600160401b03161580610f34575046610f2960a0880160808901613349565b6001600160401b0316145b15610f5257604051631c6c777560e31b815260040160405180910390fd5b604080516080810182525f808252602082018190529181018290526060810191909152610f86610100880160e089016131a4565b6001600160a01b0316331480156060830152610ff557610fac60608801604089016134ed565b63ffffffff165f03610fd1576040516372b6e1c360e11b815260040160405180910390fd5b62030d40851115610ff557604051631e3b03c960e01b815260040160405180910390fd5b5f6110026105d4896134e2565b905061100e815f611f49565b5f61102a6d7369676e616c5f7365727669636560901b5f611cb3565b63ffffffff881660208501529050611054818361104d60a08d0160808e01613349565b8b8b6123fe565b63ffffffff1660408085019190915261109390611076908b0160208c01613349565b61108e906001600160401b03166101208c013561357f565b611f9b565b6110b0576040516335856fbd60e21b815260040160405180910390fd5b5f6110bb8a8361203c565b156110d45750600295506001945061012089013561111b565b5f84606001516110e4575a6110ed565b6110ed8b6123ab565b90506110ff8b85838860600151612170565b1561111057600297505f9650611119565b60019750600296505b505b61112b60408b0160208c01613349565b6001600160401b03161561128c5761114960408b0160208c01613349565b61115c906001600160401b03168261357f565b905083606001518015611183575061117a60608b0160408c016134ed565b63ffffffff1615155b1561128c57604084015163ffffffff16614e20025a6111c06111a96101408e018e6135ce565b6020601f909101819004026101a00160041b919050565b63ffffffff9081168801919091036201d4c00181168087525f9183916111e9918391906124a916565b0390505f6111fd60608e0160408f016134ed565b63ffffffff168d60200160208101906112169190613349565b6001600160401b031683028161122e5761122e613617565b0490505f48830290505f6112728f602001602081019061124e9190613349565b6001600160401b03168484101561126a5784840160011c61126c565b845b906124be565b9586900395905061128633826188b86124d2565b50505050505b6112b4816188b86112a46101008e0160e08f016131a4565b6001600160a01b031691906124d2565b6112be8388612294565b827f8580f507761043ecdd2bdca084d6fb0109150b3d9842d854d34e3dea6d69387d8b866040516112f09291906137af565b60405180910390a250505050506113076001611f33565b935093915050565b5f600261131e60975460ff1690565b60ff1614905090565b6001600160a01b037f000000000000000000000000076337500000000000000000000000000000000116300361136f5760405162461bcd60e51b8152600401610a2490613803565b7f00000000000000000000000007633750000000000000000000000000000000016001600160a01b03166113a1612515565b6001600160a01b0316146113c75760405162461bcd60e51b8152600401610a249061384f565b6113d081612530565b604080515f808252602082019092526113eb91839190612538565b50565b5f6113fa8484846126a2565b90505b9392505050565b61140c6126f4565b611416335f612784565b565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000000011630036114605760405162461bcd60e51b8152600401610a2490613803565b7f00000000000000000000000007633750000000000000000000000000000000016001600160a01b0316611492612515565b6001600160a01b0316146114b85760405162461bcd60e51b8152600401610a249061384f565b6114c182612530565b6114cd82826001612538565b5050565b5f306001600160a01b037f000000000000000000000000076337500000000000000000000000000000000116146115705760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610a24565b505f516020613a695f395f51905f525b90565b5f4661159560a0860160808701613349565b6001600160401b0316146115aa57505f6113fd565b6113fa6115c86d7369676e616c5f7365727669636560901b5f611cb3565b6115d761063b6105d4886134e2565b6115e760e0880160c08901613349565b8686612831565b5f4661160060a0840160808501613349565b6001600160401b03161461161557505f919050565b6116306d7369676e616c5f7365727669636560901b5f611cb3565b6001600160a01b03166332676bc63061164b6105d4866134e2565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401602060405180830381865afa158015611692573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116b6919061389b565b92915050565b6116c4612351565b6114165f6128b3565b5f6116b6620c35006101a06020601f8601819004020160041b6138b6565b806001600160a01b0381166117135760405163538ba4f960e01b815260040160405180910390fd5b6040516317066a5760e21b81523060048201526001600160a01b03831690635c19a95c906024015f604051808303815f87803b158015611751575f5ffd5b505af1158015611763573d5f5f3e3d5ffd5b505050505050565b611773612904565b61177b61291d565b611416336001612784565b5f61178f612515565b905090565b5f5f6117ab836562726964676560d01b60016113ee565b6001600160a01b038116151594909350915050565b6117d060e0820160c08301613349565b46816001600160401b0316146117f957604051631c6c777560e31b815260040160405180910390fd5b61180960a0830160808401613349565b6001600160401b0381161580611827575046816001600160401b0316145b1561184557604051631c6c777560e31b815260040160405180910390fd5b611859609754610100900460ff1660021490565b156118775760405163bae6e2a960e01b815260040160405180910390fd5b600261188560975460ff1690565b60ff16036118a65760405163dfc60d8560e01b815260040160405180910390fd5b6118b06002611f33565b6118c1610100840160e085016131a4565b6001600160a01b0316336001600160a01b0316146118f2576040516372b6e1c360e11b815260040160405180910390fd5b5f6118ff6105d4856134e2565b905061190c816001611f49565b611917816003612294565b6119326d7369676e616c5f7365727669636560901b5f611cb3565b60405163019b28af60e61b81526003831860048201526001600160a01b0391909116906366ca2bc0906024016020604051808303815f875af115801561197a573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061199e9190613506565b50506119aa6001611f33565b505050565b6119bf60a0840160808501613349565b46816001600160401b0316146119e857604051631c6c777560e31b815260040160405180910390fd5b6119f860e0850160c08601613349565b6001600160401b0381161580611a16575046816001600160401b0316145b15611a3457604051631c6c777560e31b815260040160405180910390fd5b611a48609754610100900460ff1660021490565b15611a665760405163bae6e2a960e01b815260040160405180910390fd5b6002611a7460975460ff1690565b60ff1603611a955760405163dfc60d8560e01b815260040160405180910390fd5b611a9f6002611f33565b5f611aac6105d4876134e2565b9050611ab8815f611f49565b5f611ad46d7369676e616c5f7365727669636560901b5f611cb3565b604051631933b5e360e11b8152306004820152602481018490529091506001600160a01b038216906332676bc690604401602060405180830381865afa158015611b20573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b44919061389b565b611b615760405163ab035ad560e01b815260040160405180910390fd5b611b808160038418611b7960e08b0160c08c01613349565b89896123fe565b50611b8c826004612294565b611b9a876101200135611f9b565b611bb7576040516335856fbd60e21b815260040160405180910390fd5b611be062bc399d60e11b611bd160808a0160608b016131a4565b6001600160a01b03169061298e565b15611c8457611bff8230611bfa60a08b0160808c01613349565b612a1b565b611c0f60808801606089016131a4565b6001600160a01b0316630178733a88610120013589856040518463ffffffff1660e01b8152600401611c429291906138d2565b5f604051808303818588803b158015611c59575f5ffd5b505af1158015611c6b573d5f5f3e3d5ffd5b5050505050611c7f5f195f1b5f1980612a1b565b611ca0565b611ca06101208801356188b86112a460c08b0160a08c016131a4565b5050611cac6001611f33565b5050505050565b5f6113fd4684846126a2565b5f46611cd160e0860160c08701613349565b6001600160401b031614611ce657505f6113fd565b6113fa611d046d7369676e616c5f7365727669636560901b5f611cb3565b611d106105d4876134e2565b6115e760a0880160808901613349565b5f81604051602001611d3291906138f3565b604051602081830303815290604052805190602001209050919050565b604080516060810182525f8082526020820181905291810191909152611dc6604080516060810182525f8082526020820181905291810191909152506040805160608101825260cb54815260cc546001600160a01b0381166020830152600160a01b90046001600160401b03169181019190915290565b80519091501580611dd8575080515f19145b1561158057604051635ceed17360e01b815260040160405180910390fd5b5f54610100900460ff1615808015611e1457505f54600160ff909116105b80611e2d5750303b158015611e2d57505f5460ff166001145b611e495760405162461bcd60e51b8152600401610a249061351d565b5f805460ff191660011790558015611e6a575f805461ff0019166101001790555b611e748383612a70565b80156119aa575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a1505050565b611ec5612351565b6001600160a01b038116611f2a5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610a24565b6113eb816128b3565b6097805460ff191660ff92909216919091179055565b806004811115611f5b57611f5b61312d565b5f83815260ca602052604090205460ff166004811115611f7d57611f7d61312d565b146114cd576040516319d893ad60e21b815260040160405180910390fd5b5f5f611fb86c38bab7ba30afb6b0b730b3b2b960991b6001611cb3565b90506001600160a01b038116611fd15750600192915050565b6040516315c638fb60e31b81525f6004820152602481018490526001600160a01b0382169063ae31c7d8906044015f604051808303815f87803b158015612016575f5ffd5b505af1925050508015612027575060015b61203357505f92915050565b50600192915050565b5f80612050610120850161010086016131a4565b6001600160a01b031603612066575060016116b6565b30612079610120850161010086016131a4565b6001600160a01b03160361208f575060016116b6565b6001600160a01b0382166120ab610120850161010086016131a4565b6001600160a01b0316036120c1575060016116b6565b60046120d16101408501856135ce565b9050101580156121085750637f07c94760e01b6120f26101408501856135ce565b6120fb91613929565b6001600160e01b03191614155b80156113fd57506113fd612124610120850161010086016131a4565b6001600160a01b03163b151590565b5f6001600160a01b03851661215b57604051634c67134d60e11b815260040160405180910390fd5b5f5f835160208501878988f195945050505050565b5f3061218260808701606088016131a4565b6001600160a01b03160361219857612198613961565b6101208501351580156121b857506121b46101408601866135ce565b1590505b156121c55750600161228c565b825f036121d357505f61228c565b6121f7846121e760808801606089016131a4565b611bfa60a0890160808a01613349565b5f61220a610120870161010088016131a4565b90506101208601355f6122216101408901896135ce565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92018290525084519495509384935083925090506020850186888cf194505a9050851561227b5761227b8188612aaa565b6122875f198080612a1b565b505050505b949350505050565b8060048111156122a6576122a661312d565b5f83815260ca602052604090205460ff1660048111156122c8576122c861312d565b036122e6576040516319d893ad60e21b815260040160405180910390fd5b5f82815260ca60205260409020805482919060ff191660018360048111156123105761231061312d565b0217905550817f6c51882bc2ed67617f77a1e9b9a25d2caad8448647ecb093b357a603b25756348260405161234591906131d6565b60405180910390a25050565b6033546001600160a01b031633146114165760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610a24565b5f806123c56123be6101408501856135ce565b90506116cd565b63ffffffff169050806123f66123e160608601604087016134ed565b63ffffffff16836124a990919063ffffffff16565b039392505050565b5f856001600160a01b031663910af6ed85612423876562726964676560d01b5f6113ee565b8887876040518663ffffffff1660e01b8152600401612446959493929190613975565b6020604051808303815f875af1925050508015612480575060408051601f3d908101601f1916820190925261247d91810190613506565b60015b61249d576040516314504c7360e31b815260040160405180910390fd5b90505b95945050505050565b5f8183116124b757816113fd565b5090919050565b5f8183116124cc57826113fd565b50919050565b815f036124de57505050565b6124f883838360405180602001604052805f815250612133565b6119aa57604051634c67134d60e11b815260040160405180910390fd5b5f516020613a695f395f51905f52546001600160a01b031690565b6113eb612351565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff161561256b576119aa83612abe565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156125c5575060408051601f3d908101601f191682019092526125c291810190613506565b60015b6126285760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610a24565b5f516020613a695f395f51905f5281146126965760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610a24565b506119aa838383612b59565b5f6126ad8484612b7d565b9050811580156126c457506001600160a01b038116155b156113fd57604051632b0d65db60e01b81526001600160401b038516600482015260248101849052604401610a24565b612708609754610100900460ff1660021490565b6127255760405163bae6e2a960e01b815260040160405180910390fd5b6097805461010069ffffffffffffffffff001990911662010000426001600160401b031602171790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa906020015b60405180910390a1565b6033546001600160a01b03838116911614806127cc57506127b76d636861696e5f7761746368646f6760901b6001611cb3565b6001600160a01b0316826001600160a01b0316145b156127d5575050565b80801561280f57506127fa6e6272696467655f7761746368646f6760881b6001611cb3565b6001600160a01b0316826001600160a01b0316145b15612818575050565b604051630d85cccf60e11b815260040160405180910390fd5b5f856001600160a01b031663ce9d082085612856876562726964676560d01b5f6113ee565b8887876040518663ffffffff1660e01b8152600401612879959493929190613975565b5f6040518083038186803b15801561288f575f5ffd5b505afa9250505080156128a0575060015b6128ab57505f6124a0565b5060016124a0565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b60405163a87dd7cf60e01b815260040160405180910390fd5b612931609754610100900460ff1660021490565b1561294f5760405163bae6e2a960e01b815260040160405180910390fd5b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200161277a565b5f6001600160a01b0383163b6129a557505f6116b6565b6040516301ffc9a760e01b81526001600160e01b0319831660048201526001600160a01b038416906301ffc9a790602401602060405180830381865afa925050508015612a0f575060408051601f3d908101601f19168201909252612a0c9181019061389b565b60015b156116b6579392505050565b604080516060810182528481526001600160a01b03909316602084018190526001600160401b03909216920182905260cb9290925560cc8054600160a01b9092026001600160e01b0319909216909217179055565b806001600160a01b038116612a985760405163538ba4f960e01b815260040160405180910390fd5b612aa183612c1f565b6119aa82612c7d565b612ab5603f826139b6565b8210156114cd57fe5b6001600160a01b0381163b612b2b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610a24565b5f516020613a695f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b612b6283612ced565b5f82511180612b6e5750805b156119aa576109da8383612d2c565b6065545f906001600160a01b031680612ba957604051638ed88b2560e01b815260040160405180910390fd5b604051630a3dc4f360e21b81526001600160401b0385166004820152602481018490526001600160a01b038216906328f713cc90604401602060405180830381865afa158015612bfb573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061228c91906139d5565b5f54610100900460ff16612c455760405162461bcd60e51b8152600401610a24906139f0565b612c4d612d51565b612c6b6001600160a01b03821615612c6557816128b3565b336128b3565b506097805461ff001916610100179055565b5f54610100900460ff16612ca35760405162461bcd60e51b8152600401610a24906139f0565b6001600160401b03461115612ccb5760405163a12e8fa960e01b815260040160405180910390fd5b606580546001600160a01b0319166001600160a01b0392909216919091179055565b612cf681612abe565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606113fd8383604051806060016040528060278152602001613a8960279139612d77565b5f54610100900460ff166114165760405162461bcd60e51b8152600401610a24906139f0565b60605f5f856001600160a01b031685604051612d939190613a3b565b5f60405180830381855af49150503d805f8114612dcb576040519150601f19603f3d011682016040523d82523d5f602084013e612dd0565b606091505b5091509150612de186838387612deb565b9695505050505050565b60608315612e595782515f03612e52576001600160a01b0385163b612e525760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610a24565b508161228c565b61228c8383815115612e6e5781518083602001fd5b8060405162461bcd60e51b8152600401610a249190613a56565b5f61016082840312156124cc575f5ffd5b80151581146113eb575f5ffd5b5f5f60408385031215612eb7575f5ffd5b82356001600160401b03811115612ecc575f5ffd5b612ed885828601612e88565b9250506020830135612ee981612e99565b809150509250929050565b5f60208284031215612f04575f5ffd5b81356001600160401b03811115612f19575f5ffd5b61228c84828501612e88565b5f5b83811015612f3f578181015183820152602001612f27565b50505f910152565b5f8151808452612f5e816020860160208601612f25565b601f01601f19169290920160200192915050565b80516001600160401b031682525f6020820151612f9a60208501826001600160401b03169052565b506040820151612fb2604085018263ffffffff169052565b506060820151612fcd60608501826001600160a01b03169052565b506080820151612fe860808501826001600160401b03169052565b5060a082015161300360a08501826001600160a01b03169052565b5060c082015161301e60c08501826001600160401b03169052565b5060e082015161303960e08501826001600160a01b03169052565b506101008201516130566101008501826001600160a01b03169052565b5061012082015161012084015261014082015161016061014085015261228c610160850182612f47565b828152604060208201525f6113fa6040830184612f72565b5f5f5f604084860312156130aa575f5ffd5b83356001600160401b038111156130bf575f5ffd5b6130cb86828701612e88565b93505060208401356001600160401b038111156130e6575f5ffd5b8401601f810186136130f6575f5ffd5b80356001600160401b0381111561310b575f5ffd5b86602082840101111561311c575f5ffd5b939660209190910195509293505050565b634e487b7160e01b5f52602160045260245ffd5b600581106131515761315161312d565b9052565b604081016131638285613141565b600483106131735761317361312d565b8260208301529392505050565b6001600160a01b03811681146113eb575f5ffd5b803561319f81613180565b919050565b5f602082840312156131b4575f5ffd5b81356113fd81613180565b5f602082840312156131cf575f5ffd5b5035919050565b602081016116b68284613141565b80356001600160401b038116811461319f575f5ffd5b5f5f5f6060848603121561320c575f5ffd5b613215846131e4565b925060208401359150604084013561322c81612e99565b809150509250925092565b634e487b7160e01b5f52604160045260245ffd5b60405161016081016001600160401b038111828210171561326e5761326e613237565b60405290565b5f82601f830112613283575f5ffd5b81356001600160401b0381111561329c5761329c613237565b604051601f8201601f19908116603f011681016001600160401b03811182821017156132ca576132ca613237565b6040528181528382016020018510156132e1575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f5f6040838503121561330e575f5ffd5b823561331981613180565b915060208301356001600160401b03811115613333575f5ffd5b61333f85828601613274565b9150509250929050565b5f60208284031215613359575f5ffd5b6113fd826131e4565b5f5f60408385031215613373575f5ffd5b823591506020830135612ee981612e99565b803563ffffffff8116811461319f575f5ffd5b5f61016082840312156133a9575f5ffd5b6133b161324b565b90506133bc826131e4565b81526133ca602083016131e4565b60208201526133db60408301613385565b60408201526133ec60608301613194565b60608201526133fd608083016131e4565b608082015261340e60a08301613194565b60a082015261341f60c083016131e4565b60c082015261343060e08301613194565b60e08201526134426101008301613194565b61010082015261012082810135908201526101408201356001600160401b0381111561346c575f5ffd5b61347884828501613274565b6101408301525092915050565b5f60208284031215613495575f5ffd5b81356001600160401b038111156134aa575f5ffd5b61228c84828501613398565b5f5f604083850312156134c7575f5ffd5b82356134d281613180565b91506020830135612ee981613180565b5f6116b63683613398565b5f602082840312156134fd575f5ffd5b6113fd82613385565b5f60208284031215613516575f5ffd5b5051919050565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b634e487b7160e01b5f52601160045260245ffd5b808201808211156116b6576116b661356b565b5f6001600160401b0382166001600160401b0381036135b3576135b361356b565b60010192915050565b602081525f6113fd6020830184612f72565b5f5f8335601e198436030181126135e3575f5ffd5b8301803591506001600160401b038211156135fc575f5ffd5b602001915036819003821315613610575f5ffd5b9250929050565b634e487b7160e01b5f52601260045260245ffd5b5f5f8335601e19843603018112613640575f5ffd5b83016020810192503590506001600160401b0381111561365e575f5ffd5b803603821315613610575f5ffd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b6136ae826136a1836131e4565b6001600160401b03169052565b5f6136bb602083016131e4565b6001600160401b031660208401526136d560408301613385565b63ffffffff1660408401526136ec60608301613194565b6001600160a01b03166060840152613706608083016131e4565b6001600160401b0316608084015261372060a08301613194565b6001600160a01b031660a084015261373a60c083016131e4565b6001600160401b031660c084015261375460e08301613194565b6001600160a01b031660e084015261376f6101008301613194565b6001600160a01b0316610100840152610120828101359084015261379761014083018361362b565b6101606101408601526124a06101608601828461366c565b60a081525f6137c160a0830185613694565b905063ffffffff835116602083015263ffffffff602084015116604083015263ffffffff60408401511660608301526060830151151560808301529392505050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b5f602082840312156138ab575f5ffd5b81516113fd81612e99565b63ffffffff81811683821601908111156116b6576116b661356b565b604081525f6138e46040830185613694565b90508260208301529392505050565b60408152600d60408201526c5441494b4f5f4d45535341474560981b6060820152608060208201525f6113fd6080830184612f72565b80356001600160e01b0319811690600484101561395a576001600160e01b0319600485900360031b81901b82161691505b5092915050565b634e487b7160e01b5f52600160045260245ffd5b6001600160401b038616815260018060a01b0385166020820152836040820152608060608201525f6139ab60808301848661366c565b979650505050505050565b5f826139d057634e487b7160e01b5f52601260045260245ffd5b500490565b5f602082840312156139e5575f5ffd5b81516113fd81613180565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f8251613a4c818460208701612f25565b9190910192915050565b602081525f6113fd6020830184612f4756fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212204833b042a261f6ee4e21f5cebb96844ccfb99e586120a1f794c29e3857f4d72864736f6c634300081b0033", + "code": "0x6080604052600436106101f1575f3560e01c806364d391b4116101085780639efc7a2e1161009d578063c012fa771161006d578063c012fa77146105d7578063d0496d6a146105f6578063d1aaa5df1461063e578063eefbf17e1461065e578063f2fde38b1461069c575f5ffd5b80639efc7a2e1461056d578063a730cdfb1461058c578063b8acae0e146105a2578063be880c81146105c1575f5ffd5b80638abf6077116100d85780638abf6077146104df5780638da5cb5b146104f35780638e3881a914610510578063913b16cb1461054e575f5ffd5b806364d391b414610450578063715018a6146104835780637cbadfaa146104975780638456cb59146104cb575f5ffd5b80633c6cf4731161018957806352d1902d1161015957806352d1902d146103ab5780635862f6e1146103bf5780635c975abb146103de57806360620c6b146103fe57806362d094531461041d575f5ffd5b80633c6cf473146103255780633f4ba83a14610360578063422770fa146103745780634f1ef28614610398575f5ffd5b80631bdb0037116101c45780631bdb0037146102945780632035065e146102b55780633075db56146102e25780633659cfe614610306575f5ffd5b80630432873c146101f557806304f3bcec14610216578063069489a21461026157806319ab453c14610275575b5f5ffd5b348015610200575f5ffd5b5061021461020f366004612e20565b6106bb565b005b348015610221575f5ffd5b507f00000000000000000000000076337500000000000000000000000000000000065b6040516001600160a01b0390911681526020015b60405180910390f35b34801561026c575f5ffd5b506102146109c2565b348015610280575f5ffd5b5061021461028f366004612e8d565b610a7b565b6102a76102a2366004612ea8565b610b41565b604051610258929190613034565b3480156102c0575f5ffd5b506102d46102cf36600461304c565b610ef7565b604051610258929190613109565b3480156102ed575f5ffd5b506102f66113d5565b6040519015158152602001610258565b348015610311575f5ffd5b50610214610320366004612e8d565b6113ed565b348015610330575f5ffd5b5061035361033f366004613134565b60ca6020525f908152604090205460ff1681565b604051610258919061314b565b34801561036b575f5ffd5b506102146114b4565b34801561037f575f5ffd5b5061038a62030d4081565b604051908152602001610258565b6102146103a636600461321f565b611540565b3480156103b6575f5ffd5b5061038a6115f5565b3480156103ca575f5ffd5b506102f66103d936600461304c565b6116a7565b3480156103e9575f5ffd5b506102f6609754610100900460ff1660021490565b348015610409575f5ffd5b506102f6610418366004612ea8565b611722565b348015610428575f5ffd5b506102447f000000000000000000000000763375000000000000000000000000000000000581565b34801561045b575f5ffd5b506102447f000000000000000000000000000000000000000000000000000000000000000081565b34801561048e575f5ffd5b506102146117f6565b3480156104a2575f5ffd5b506104b66104b1366004613134565b611807565b60405163ffffffff9091168152602001610258565b3480156104d6575f5ffd5b50610214611825565b3480156104ea575f5ffd5b506102446118ac565b3480156104fe575f5ffd5b506033546001600160a01b0316610244565b34801561051b575f5ffd5b5061052f61052a366004613281565b6118ba565b6040805192151583526001600160a01b03909116602083015201610258565b348015610559575f5ffd5b50610214610568366004612ea8565b6118e6565b348015610578575f5ffd5b5061021461058736600461304c565b611ad8565b348015610597575f5ffd5b506104b66201d4c081565b3480156105ad575f5ffd5b506102f66105bc36600461304c565b611dfc565b3480156105cc575f5ffd5b506104b6620c350081565b3480156105e2575f5ffd5b5061038a6105f136600461339a565b611e63565b348015610601575f5ffd5b5061060a611e92565b60408051825181526020808401516001600160a01b031690820152918101516001600160401b031690820152606001610258565b348015610649575f5ffd5b5061038a610658366004613134565b60031890565b348015610669575f5ffd5b5060c95461068490600160401b90046001600160401b031681565b6040516001600160401b039091168152602001610258565b3480156106a7575f5ffd5b506102146106b6366004612e8d565b611f39565b6106cb60e0830160c08401613281565b46816001600160401b0316146106f457604051631c6c777560e31b815260040160405180910390fd5b61070460a0840160808501613281565b6001600160401b0381161580610722575046816001600160401b0316145b1561074057604051631c6c777560e31b815260040160405180910390fd5b610754609754610100900460ff1660021490565b156107725760405163bae6e2a960e01b815260040160405180910390fd5b600261078060975460ff1690565b60ff16036107a15760405163dfc60d8560e01b815260040160405180910390fd5b6107ab6002611faf565b5f6107b86105f1866133cb565b90506107c5816001611fc5565b6107d3856101200135612017565b6107f0576040516335856fbd60e21b815260040160405180910390fd5b5f61081b867f00000000000000000000000076337500000000000000000000000000000000056120dc565b156108665761085f8661012001356188b860405180602001604052805f8152508960e001602081019061084e9190612e8d565b6001600160a01b03169291906121d3565b90506108e0565b61087660608701604088016133d6565b63ffffffff1615806108855750845b80156108b3575061089d610100870160e08801612e8d565b6001600160a01b0316336001600160a01b031614155b156108d1576040516372b6e1c360e11b815260040160405180910390fd5b6108dd86835a5f612210565b90505b80156108f6576108f1826002612334565b6109b0565b841561099757610907826003612334565b60405163019b28af60e61b81526003831860048201527f00000000000000000000000076337500000000000000000000000000000000056001600160a01b0316906366ca2bc0906024016020604051808303815f875af115801561096d573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061099191906133ef565b506109b0565b60405163161e3ead60e01b815260040160405180910390fd5b50506109bc6001611faf565b50505050565b6109ca6123f1565b5f54600290610100900460ff161580156109ea57505f5460ff8083169116105b610a0f5760405162461bcd60e51b8152600401610a0690613406565b60405180910390fd5b5f805460c9805467ffffffffffffffff1916905560cd82905560ce82905561ffff191660ff83169081176101001761ff0019169091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a150565b5f54610100900460ff1615808015610a9957505f54600160ff909116105b80610ab25750303b158015610ab257505f5460ff166001145b610ace5760405162461bcd60e51b8152600401610a0690613406565b5f805460ff191660011790558015610aef575f805461ff0019166101001790555b610af88261244b565b8015610b3d575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b60408051610160810182525f8082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e0820183905261010082018390526101208201839052610140820152610ba760c0840160a08501612e8d565b6001600160a01b038116610bce5760405163538ba4f960e01b815260040160405180910390fd5b610bdf610100850160e08601612e8d565b6001600160a01b038116610c065760405163538ba4f960e01b815260040160405180910390fd5b610c1660e0860160c08701613281565b6001600160401b0381161580610c34575046816001600160401b0316145b15610c5257604051631c6c777560e31b815260040160405180910390fd5b610c66609754610100900460ff1660021490565b15610c845760405163bae6e2a960e01b815260040160405180910390fd5b6002610c9260975460ff1690565b60ff1603610cb35760405163dfc60d8560e01b815260040160405180910390fd5b610cbd6002611faf565b610ccd60608701604088016133d6565b63ffffffff165f03610d1557610ce96040870160208801613281565b6001600160401b031615610d105760405163c9f5178760e01b815260040160405180910390fd5b610d3d565b610d1e866124a9565b5f03610d3d576040516308c2ad5360e01b815260040160405180910390fd5b5f610d5161052a60e0890160c08a01613281565b50905080610d7257604051631c6c777560e31b815260040160405180910390fd5b34610d836040890160208a01613281565b610d9b906001600160401b03166101208a0135613468565b14610db957604051634ac2abdf60e11b815260040160405180910390fd5b610dc2876133cb565b60c98054919650600160401b9091046001600160401b0316906008610de68361347b565b82546101009290920a6001600160401b03818102199093169183160217909155908116865233606087015246166080860152610e2185611e63565b9550857fe33fd33b4f45b95b1c196242240c5b5233129d724b578f95b66ce8d8aae9351786604051610e5391906134a5565b60405180910390a260405163019b28af60e61b8152600481018790527f00000000000000000000000076337500000000000000000000000000000000056001600160a01b0316906366ca2bc0906024016020604051808303815f875af1158015610ebf573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ee391906133ef565b5050610eef6001611faf565b505050915091565b5f5f610f0d609754610100900460ff1660021490565b15610f2b5760405163bae6e2a960e01b815260040160405180910390fd5b6002610f3960975460ff1690565b60ff1603610f5a5760405163dfc60d8560e01b815260040160405180910390fd5b610f646002611faf565b5f5a905046610f7960e0880160c08901613281565b6001600160401b031614610fa057604051631c6c777560e31b815260040160405180910390fd5b610fb060a0870160808801613281565b6001600160401b03161580610fdc575046610fd160a0880160808901613281565b6001600160401b0316145b15610ffa57604051631c6c777560e31b815260040160405180910390fd5b604080516080810182525f80825260208201819052918101829052606081019190915261102e610100880160e08901612e8d565b6001600160a01b031633148015606083015261109d5761105460608801604089016133d6565b63ffffffff165f03611079576040516372b6e1c360e11b815260040160405180910390fd5b62030d4085111561109d57604051631e3b03c960e01b815260040160405180910390fd5b5f6110aa6105f1896133cb565b90506110b6815f611fc5565b63ffffffff861660208301526110fe7f0000000000000000000000007633750000000000000000000000000000000005826110f760a08c0160808d01613281565b8a8a6124fc565b63ffffffff1660408084019190915261113d90611120908a0160208b01613281565b611138906001600160401b03166101208b0135613468565b612017565b61115a576040516335856fbd60e21b815260040160405180910390fd5b5f611185897f00000000000000000000000076337500000000000000000000000000000000056120dc565b1561119e575060029450600193506101208801356111e5565b5f83606001516111ae575a6111b7565b6111b78a6124a9565b90506111c98a84838760600151612210565b156111da57600296505f95506111e3565b60019650600295505b505b6111f560408a0160208b01613281565b6001600160401b0316156113535761121360408a0160208b01613281565b611226906001600160401b031682613468565b90508260600151801561124d575061124460608a0160408b016133d6565b63ffffffff1615155b1561135357604083015163ffffffff16614e20025a61128a6112736101408d018d6134b7565b6020601f909101819004026101a00160041b919050565b63ffffffff9081168701919091036201d4c00181168086525f9183916112b3918391906125a716565b0390505f6112c760608d0160408e016133d6565b63ffffffff166112dd60408e0160208f01613281565b6001600160401b03168302816112f5576112f5613500565b0490505f48830290505f6113398e60200160208101906113159190613281565b6001600160401b0316848410156113315784840160011c611333565b845b906125bc565b9586900395905061134d33826188b86125d0565b50505050505b61137b816188b861136b6101008d0160e08e01612e8d565b6001600160a01b031691906125d0565b6113858287612334565b817f8580f507761043ecdd2bdca084d6fb0109150b3d9842d854d34e3dea6d69387d8a856040516113b7929190613698565b60405180910390a2505050506113cd6001611faf565b935093915050565b5f60026113e460975460ff1690565b60ff1614905090565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000000011630036114355760405162461bcd60e51b8152600401610a06906136ec565b7f00000000000000000000000007633750000000000000000000000000000000016001600160a01b0316611467612613565b6001600160a01b03161461148d5760405162461bcd60e51b8152600401610a0690613738565b6114968161262e565b604080515f808252602082019092526114b191839190612636565b50565b6114c8609754610100900460ff1660021490565b6114e55760405163bae6e2a960e01b815260040160405180910390fd5b6114ed6127a0565b6115016097805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a161153e335f6127b9565b565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000000011630036115885760405162461bcd60e51b8152600401610a06906136ec565b7f00000000000000000000000007633750000000000000000000000000000000016001600160a01b03166115ba612613565b6001600160a01b0316146115e05760405162461bcd60e51b8152600401610a0690613738565b6115e98261262e565b610b3d82826001612636565b5f306001600160a01b037f000000000000000000000000076337500000000000000000000000000000000116146116945760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610a06565b505f5160206139525f395f51905f525b90565b5f466116b960a0860160808701613281565b6001600160401b0316146116ce57505f61171b565b6117187f00000000000000000000000076337500000000000000000000000000000000056117016106586105f1886133cb565b61171160e0880160c08901613281565b8686612866565b90505b9392505050565b5f4661173460a0840160808501613281565b6001600160401b03161461174957505f919050565b6001600160a01b037f0000000000000000000000007633750000000000000000000000000000000005166332676bc6306117856105f1866133cb565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401602060405180830381865afa1580156117cc573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117f09190613784565b92915050565b6117fe6123f1565b61153e5f6128e8565b5f6117f0620c35006101a06020601f8601819004020160041b61379f565b611839609754610100900460ff1660021490565b156118575760405163bae6e2a960e01b815260040160405180910390fd5b61185f6127a0565b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a161153e3360016127b9565b5f6118b5612613565b905090565b5f5f6118d1836562726964676560d01b6001612939565b6001600160a01b038116151594909350915050565b6118f660e0820160c08301613281565b46816001600160401b03161461191f57604051631c6c777560e31b815260040160405180910390fd5b61192f60a0830160808401613281565b6001600160401b038116158061194d575046816001600160401b0316145b1561196b57604051631c6c777560e31b815260040160405180910390fd5b61197f609754610100900460ff1660021490565b1561199d5760405163bae6e2a960e01b815260040160405180910390fd5b60026119ab60975460ff1690565b60ff16036119cc5760405163dfc60d8560e01b815260040160405180910390fd5b6119d66002611faf565b6119e7610100840160e08501612e8d565b6001600160a01b0316336001600160a01b031614611a18576040516372b6e1c360e11b815260040160405180910390fd5b5f611a256105f1856133cb565b9050611a32816001611fc5565b611a3d816003612334565b60405163019b28af60e61b81526003821860048201527f00000000000000000000000076337500000000000000000000000000000000056001600160a01b0316906366ca2bc0906024016020604051808303815f875af1158015611aa3573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611ac791906133ef565b5050611ad36001611faf565b505050565b611ae860a0840160808501613281565b46816001600160401b031614611b1157604051631c6c777560e31b815260040160405180910390fd5b611b2160e0850160c08601613281565b6001600160401b0381161580611b3f575046816001600160401b0316145b15611b5d57604051631c6c777560e31b815260040160405180910390fd5b611b71609754610100900460ff1660021490565b15611b8f5760405163bae6e2a960e01b815260040160405180910390fd5b6002611b9d60975460ff1690565b60ff1603611bbe5760405163dfc60d8560e01b815260040160405180910390fd5b611bc86002611faf565b5f611bd56105f1876133cb565b9050611be1815f611fc5565b604051631933b5e360e11b8152306004820152602481018290527f00000000000000000000000076337500000000000000000000000000000000056001600160a01b0316906332676bc690604401602060405180830381865afa158015611c4a573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c6e9190613784565b611c8b5760405163ab035ad560e01b815260040160405180910390fd5b611cca7f000000000000000000000000763375000000000000000000000000000000000560038318611cc360e08a0160c08b01613281565b88886124fc565b50611cd6816004612334565b611ce4866101200135612017565b611d01576040516335856fbd60e21b815260040160405180910390fd5b611d2a62bc399d60e11b611d1b6080890160608a01612e8d565b6001600160a01b0316906129db565b15611dce57611d498130611d4460a08a0160808b01613281565b612aab565b611d596080870160608801612e8d565b6001600160a01b0316630178733a87610120013588846040518463ffffffff1660e01b8152600401611d8c9291906137bb565b5f604051808303818588803b158015611da3575f5ffd5b505af1158015611db5573d5f5f3e3d5ffd5b5050505050611dc95f195f1b5f1980612aab565b611dea565b611dea6101208701356188b861136b60c08a0160a08b01612e8d565b50611df56001611faf565b5050505050565b5f46611e0e60e0860160c08701613281565b6001600160401b031614611e2357505f61171b565b6117187f0000000000000000000000007633750000000000000000000000000000000005611e536105f1876133cb565b61171160a0880160808901613281565b5f81604051602001611e7591906137dc565b604051602081830303815290604052805190602001209050919050565b604080516060810182525f8082526020820181905291810191909152611f09604080516060810182525f8082526020820181905291810191909152506040805160608101825260cb54815260cc546001600160a01b0381166020830152600160a01b90046001600160401b03169181019190915290565b80519091501580611f1b575080515f19145b156116a457604051635ceed17360e01b815260040160405180910390fd5b611f416123f1565b6001600160a01b038116611fa65760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610a06565b6114b1816128e8565b6097805460ff191660ff92909216919091179055565b806004811115611fd757611fd76130e1565b5f83815260ca602052604090205460ff166004811115611ff957611ff96130e1565b14610b3d576040516319d893ad60e21b815260040160405180910390fd5b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031661204e57506001919050565b6040516315c638fb60e31b81525f6004820152602481018390527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063ae31c7d8906044015f604051808303815f87803b1580156120b3575f5ffd5b505af19250505080156120c4575060015b6120cf57505f919050565b506001919050565b919050565b5f806120f061012085016101008601612e8d565b6001600160a01b031603612106575060016117f0565b3061211961012085016101008601612e8d565b6001600160a01b03160361212f575060016117f0565b6001600160a01b03821661214b61012085016101008601612e8d565b6001600160a01b031603612161575060016117f0565b60046121716101408501856134b7565b9050101580156121a85750637f07c94760e01b6121926101408501856134b7565b61219b91613812565b6001600160e01b03191614155b801561171b575061171b6121c461012085016101008601612e8d565b6001600160a01b03163b151590565b5f6001600160a01b0385166121fb57604051634c67134d60e11b815260040160405180910390fd5b5f5f835160208501878988f195945050505050565b5f306122226080870160608801612e8d565b6001600160a01b0316036122385761223861384a565b61012085013515801561225857506122546101408601866134b7565b1590505b156122655750600161232c565b825f0361227357505f61232c565b612297846122876080880160608901612e8d565b611d4460a0890160808a01613281565b5f6122aa61012087016101008801612e8d565b90506101208601355f6122c16101408901896134b7565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92018290525084519495509384935083925090506020850186888cf194505a9050851561231b5761231b8188612b00565b6123275f198080612aab565b505050505b949350505050565b806004811115612346576123466130e1565b5f83815260ca602052604090205460ff166004811115612368576123686130e1565b03612386576040516319d893ad60e21b815260040160405180910390fd5b5f82815260ca60205260409020805482919060ff191660018360048111156123b0576123b06130e1565b0217905550817f6c51882bc2ed67617f77a1e9b9a25d2caad8448647ecb093b357a603b2575634826040516123e5919061314b565b60405180910390a25050565b6033546001600160a01b0316331461153e5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610a06565b5f54610100900460ff166124715760405162461bcd60e51b8152600401610a069061385e565b612479612b14565b6124976001600160a01b0382161561249157816128e8565b336128e8565b506097805461ff001916610100179055565b5f806124c36124bc6101408501856134b7565b9050611807565b63ffffffff169050806124f46124df60608601604087016133d6565b63ffffffff16836125a790919063ffffffff16565b039392505050565b5f856001600160a01b031663910af6ed85612521876562726964676560d01b5f612939565b8887876040518663ffffffff1660e01b81526004016125449594939291906138a9565b6020604051808303815f875af192505050801561257e575060408051601f3d908101601f1916820190925261257b918101906133ef565b60015b61259b576040516314504c7360e31b815260040160405180910390fd5b90505b95945050505050565b5f8183116125b5578161171b565b5090919050565b5f8183116125ca578261171b565b50919050565b815f036125dc57505050565b6125f683838360405180602001604052805f8152506121d3565b611ad357604051634c67134d60e11b815260040160405180910390fd5b5f5160206139525f395f51905f52546001600160a01b031690565b6114b16123f1565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff161561266957611ad383612b3a565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156126c3575060408051601f3d908101601f191682019092526126c0918101906133ef565b60015b6127265760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610a06565b5f5160206139525f395f51905f5281146127945760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610a06565b50611ad3838383612bd5565b60405163a87dd7cf60e01b815260040160405180910390fd5b6033546001600160a01b038381169116148061280157506127ec6d636861696e5f7761746368646f6760901b6001612bf9565b6001600160a01b0316826001600160a01b0316145b1561280a575050565b808015612844575061282f6e6272696467655f7761746368646f6760881b6001612bf9565b6001600160a01b0316826001600160a01b0316145b1561284d575050565b6040516395383ea160e01b815260040160405180910390fd5b5f856001600160a01b031663ce9d08208561288b876562726964676560d01b5f612939565b8887876040518663ffffffff1660e01b81526004016128ae9594939291906138a9565b5f6040518083038186803b1580156128c4575f5ffd5b505afa9250505080156128d5575060015b6128e057505f61259e565b50600161259e565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f7f0000000000000000000000007633750000000000000000000000000000000006604051633632b1fb60e11b81526001600160401b03861660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa1580156129b7573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061171891906138ea565b6040516001600160e01b0319821660248201525f90819081906001600160a01b0386169060440160408051601f198184030181529181526020820180516001600160e01b03166301ffc9a760e01b17905251612a379190613905565b5f60405180830381855afa9150503d805f8114612a6f576040519150601f19603f3d011682016040523d82523d5f602084013e612a74565b606091505b5091509150818015612a87575080516020145b15612aa35780806020019051810190612aa09190613784565b92505b505092915050565b604080516060810182528481526001600160a01b03909316602084018190526001600160401b03909216920182905260cb9290925560cc8054600160a01b9092026001600160e01b0319909216909217179055565b612b0b603f82613920565b821015610b3d57fe5b5f54610100900460ff1661153e5760405162461bcd60e51b8152600401610a069061385e565b6001600160a01b0381163b612ba75760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610a06565b5f5160206139525f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b612bde83612c92565b5f82511180612bea5750805b15611ad3576109bc8383612cd1565b5f7f0000000000000000000000007633750000000000000000000000000000000006604051633632b1fb60e11b81524660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa158015612c6e573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061171b91906138ea565b612c9b81612b3a565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606061171b83836040518060600160405280602781526020016139726027913960605f5f856001600160a01b031685604051612d0d9190613905565b5f60405180830381855af49150503d805f8114612d45576040519150601f19603f3d011682016040523d82523d5f602084013e612d4a565b606091505b5091509150612d5b86838387612d65565b9695505050505050565b60608315612dd35782515f03612dcc576001600160a01b0385163b612dcc5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610a06565b508161232c565b61232c8383815115612de85781518083602001fd5b8060405162461bcd60e51b8152600401610a06919061393f565b5f61016082840312156125ca575f5ffd5b80151581146114b1575f5ffd5b5f5f60408385031215612e31575f5ffd5b82356001600160401b03811115612e46575f5ffd5b612e5285828601612e02565b9250506020830135612e6381612e13565b809150509250929050565b6001600160a01b03811681146114b1575f5ffd5b80356120d781612e6e565b5f60208284031215612e9d575f5ffd5b813561171b81612e6e565b5f60208284031215612eb8575f5ffd5b81356001600160401b03811115612ecd575f5ffd5b61232c84828501612e02565b5f5b83811015612ef3578181015183820152602001612edb565b50505f910152565b5f8151808452612f12816020860160208601612ed9565b601f01601f19169290920160200192915050565b80516001600160401b031682525f6020820151612f4e60208501826001600160401b03169052565b506040820151612f66604085018263ffffffff169052565b506060820151612f8160608501826001600160a01b03169052565b506080820151612f9c60808501826001600160401b03169052565b5060a0820151612fb760a08501826001600160a01b03169052565b5060c0820151612fd260c08501826001600160401b03169052565b5060e0820151612fed60e08501826001600160a01b03169052565b5061010082015161300a6101008501826001600160a01b03169052565b5061012082015161012084015261014082015161016061014085015261232c610160850182612efb565b828152604060208201525f6117186040830184612f26565b5f5f5f6040848603121561305e575f5ffd5b83356001600160401b03811115613073575f5ffd5b61307f86828701612e02565b93505060208401356001600160401b0381111561309a575f5ffd5b8401601f810186136130aa575f5ffd5b80356001600160401b038111156130bf575f5ffd5b8660208284010111156130d0575f5ffd5b939660209190910195509293505050565b634e487b7160e01b5f52602160045260245ffd5b60058110613105576131056130e1565b9052565b6040810161311782856130f5565b60048310613127576131276130e1565b8260208301529392505050565b5f60208284031215613144575f5ffd5b5035919050565b602081016117f082846130f5565b634e487b7160e01b5f52604160045260245ffd5b60405161016081016001600160401b038111828210171561319057613190613159565b60405290565b5f82601f8301126131a5575f5ffd5b81356001600160401b038111156131be576131be613159565b604051601f8201601f19908116603f011681016001600160401b03811182821017156131ec576131ec613159565b604052818152838201602001851015613203575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f5f60408385031215613230575f5ffd5b823561323b81612e6e565b915060208301356001600160401b03811115613255575f5ffd5b61326185828601613196565b9150509250929050565b80356001600160401b03811681146120d7575f5ffd5b5f60208284031215613291575f5ffd5b61171b8261326b565b803563ffffffff811681146120d7575f5ffd5b5f61016082840312156132be575f5ffd5b6132c661316d565b90506132d18261326b565b81526132df6020830161326b565b60208201526132f06040830161329a565b604082015261330160608301612e82565b60608201526133126080830161326b565b608082015261332360a08301612e82565b60a082015261333460c0830161326b565b60c082015261334560e08301612e82565b60e08201526133576101008301612e82565b61010082015261012082810135908201526101408201356001600160401b03811115613381575f5ffd5b61338d84828501613196565b6101408301525092915050565b5f602082840312156133aa575f5ffd5b81356001600160401b038111156133bf575f5ffd5b61232c848285016132ad565b5f6117f036836132ad565b5f602082840312156133e6575f5ffd5b61171b8261329a565b5f602082840312156133ff575f5ffd5b5051919050565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b634e487b7160e01b5f52601160045260245ffd5b808201808211156117f0576117f0613454565b5f6001600160401b0382166001600160401b03810361349c5761349c613454565b60010192915050565b602081525f61171b6020830184612f26565b5f5f8335601e198436030181126134cc575f5ffd5b8301803591506001600160401b038211156134e5575f5ffd5b6020019150368190038213156134f9575f5ffd5b9250929050565b634e487b7160e01b5f52601260045260245ffd5b5f5f8335601e19843603018112613529575f5ffd5b83016020810192503590506001600160401b03811115613547575f5ffd5b8036038213156134f9575f5ffd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b6135978261358a8361326b565b6001600160401b03169052565b5f6135a46020830161326b565b6001600160401b031660208401526135be6040830161329a565b63ffffffff1660408401526135d560608301612e82565b6001600160a01b031660608401526135ef6080830161326b565b6001600160401b0316608084015261360960a08301612e82565b6001600160a01b031660a084015261362360c0830161326b565b6001600160401b031660c084015261363d60e08301612e82565b6001600160a01b031660e08401526136586101008301612e82565b6001600160a01b03166101008401526101208281013590840152613680610140830183613514565b610160610140860152612aa061016086018284613555565b60a081525f6136aa60a083018561357d565b905063ffffffff835116602083015263ffffffff602084015116604083015263ffffffff60408401511660608301526060830151151560808301529392505050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b5f60208284031215613794575f5ffd5b815161171b81612e13565b63ffffffff81811683821601908111156117f0576117f0613454565b604081525f6137cd604083018561357d565b90508260208301529392505050565b60408152600d60408201526c5441494b4f5f4d45535341474560981b6060820152608060208201525f61171b6080830184612f26565b80356001600160e01b03198116906004841015613843576001600160e01b0319600485900360031b81901b82161691505b5092915050565b634e487b7160e01b5f52600160045260245ffd5b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6001600160401b038616815260018060a01b0385166020820152836040820152608060608201525f6138df608083018486613555565b979650505050505050565b5f602082840312156138fa575f5ffd5b815161171b81612e6e565b5f8251613916818460208701612ed9565b9190910192915050565b5f8261393a57634e487b7160e01b5f52601260045260245ffd5b500490565b602081525f61171b6020830184612efb56fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d27f3c7bd8b6afdaa765107161bd5cb6fc4e614237f88562cc75c635512a7a3464736f6c634300081b0033", "balance": "0x0" }, "0x7633750000000000000000000000000000000001": { @@ -105,18 +107,17 @@ "0x0000000000000000000000000000000000000000000000000000000000000097": "0x0000000000000000000000000000000000000000000000000000000000000101", "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000003bc256069ff9af461f3e04494a3ece3f62f183fc", - "0x0000000000000000000000000000000000000000000000000000000000000065": "0x0000000000000000000000007633750000000000000000000000000000000006", "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0763375000000000000000000000000000000001" }, - "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea26469706673582212205339f45a82df35ebd66aff7c8a93c20457e46388cef179bebf6908c53edcb0ec64736f6c634300081b0033", - "balance": "0x033b2e31c81e3cefb21f0000" + "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033", + "balance": "0x033b2dfb9278161c43400000" }, "0x0763375000000000000000000000000000000002": { "contractName": "ERC20VaultImpl", "storage": { "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000003bc256069ff9af461f3e04494a3ece3f62f183fc" }, - "code": "0x608060405260043610610195575f3560e01c80635c975abb116100e75780639aa8605c11610087578063d56ad7ac11610062578063d56ad7ac14610491578063e07baba6146104a7578063f09a4016146104e4578063f2fde38b14610503575f5ffd5b80639aa8605c14610422578063a86f9d9e14610452578063b84d9ffe14610471575f5ffd5b80637f07c947116100c25780637f07c947146103ca5780638456cb59146103dd5780638abf6077146103f15780638da5cb5b14610405575f5ffd5b80635c975abb1461035757806367090ccf14610377578063715018a6146103b6575f5ffd5b80633075db56116101525780633eb6b8cf1161012d5780633eb6b8cf146102fd5780633f4ba83a1461031c5780634f1ef2861461033057806352d1902d14610343575f5ffd5b80633075db56146102ab5780633659cfe6146102bf5780633ab76e9f146102de575f5ffd5b80630178733a1461019957806301ffc9a7146101ae578063066fe7b4146101e257806306fdde03146102265780630e7eeb79146102465780630ecd8be914610274575b5f5ffd5b6101ac6101a7366004613282565b610522565b005b3480156101b9575f5ffd5b506101cd6101c83660046132c8565b6106b0565b60405190151581526020015b60405180910390f35b3480156101ed575f5ffd5b506102186101fc366004613303565b60fe60209081525f928352604080842090915290825290205481565b6040519081526020016101d9565b348015610231575f5ffd5b506a195c98cc8c17dd985d5b1d60aa1b610218565b348015610251575f5ffd5b506101cd610260366004613331565b60fd6020525f908152604090205460ff1681565b34801561027f575f5ffd5b5061029361028e36600461334c565b610700565b6040516001600160a01b0390911681526020016101d9565b3480156102b6575f5ffd5b506101cd610e04565b3480156102ca575f5ffd5b506101ac6102d9366004613331565b610e1c565b3480156102e9575f5ffd5b50606554610293906001600160a01b031681565b348015610308575f5ffd5b506102936103173660046133b4565b610eec565b348015610327575f5ffd5b506101ac610f02565b6101ac61033e3660046134fc565b610f16565b34801561034e575f5ffd5b50610218610fcb565b348015610362575f5ffd5b506101cd609754610100900460ff1660021490565b348015610382575f5ffd5b50610293610391366004613303565b60fc60209081525f92835260408084209091529082529020546001600160a01b031681565b3480156103c1575f5ffd5b506101ac61107c565b6101ac6103d8366004613548565b61108d565b3480156103e8575f5ffd5b506101ac6111dd565b3480156103fc575f5ffd5b506102936111f8565b348015610410575f5ffd5b506033546001600160a01b0316610293565b34801561042d575f5ffd5b5061044161043c366004613331565b611206565b6040516101d9959493929190613601565b34801561045d575f5ffd5b5061029361046c36600461365a565b61135a565b61048461047f36600461367d565b611366565b6040516101d99190613696565b34801561049c575f5ffd5b506102186276a70081565b3480156104b2575f5ffd5b506097546104cc906201000090046001600160401b031681565b6040516001600160401b0390911681526020016101d9565b3480156104ef575f5ffd5b506101ac6104fe366004613786565b6117a8565b34801561050e575f5ffd5b506101ac61051d366004613331565b6118b7565b610536609754610100900460ff1660021490565b156105545760405163bae6e2a960e01b815260040160405180910390fd5b600261056260975460ff1690565b60ff16036105835760405163dfc60d8560e01b815260040160405180910390fd5b61058d600261192d565b610595611943565b505f6105a56101408401846137a2565b6105b39160049082906137eb565b8101906105c09190613812565b90505f5f828060200190518101906105d891906138be565b9350505091505f6105fc838760a00160208101906105f69190613331565b84611a39565b905061062661012087013561061760c0890160a08a01613331565b6001600160a01b031690611ae1565b61063660c0870160a08801613331565b6001600160a01b0316857f3dea0f5955b148debf6212261e03bd80eaf8534bee43780452d16637dcc22dd585602001518486604051610696939291906001600160a01b039384168152919092166020820152604081019190915260600190565b60405180910390a3505050506106ac600161192d565b5050565b5f6001600160e01b0319821662bc399d60e11b14806106df57506001600160e01b03198216637f07c94760e01b145b806106fa57506001600160e01b031982166301ffc9a760e01b145b92915050565b5f610709611aec565b600261071760975460ff1690565b60ff16036107385760405163dfc60d8560e01b815260040160405180910390fd5b610742600261192d565b6001600160a01b038216158061077757506001600160a01b038281165f90815260fb6020526040902054600160401b90041615155b8061078a57506001600160a01b0382163b155b156107a85760405163dc63f98760e01b815260040160405180910390fd5b5f6107b96040850160208601613331565b6001600160a01b031614806107e25750466107d760208501856139b2565b6001600160401b0316145b1561080057604051638257f7f560e01b815260040160405180910390fd5b6001600160a01b0382165f90815260fd602052604090205460ff1615610839576040516375c42fc160e01b815260040160405180910390fd5b5f60fe8161084a60208701876139b2565b6001600160401b031681526020019081526020015f205f8560200160208101906108749190613331565b6001600160a01b0316815260208101919091526040015f2054905061089c6276a700826139e1565b4210156108bc5760405163231d35fb60e11b815260040160405180910390fd5b60fc5f6108cc60208701876139b2565b6001600160401b031681526020019081526020015f205f8560200160208101906108f69190613331565b6001600160a01b03908116825260208201929092526040015f20541691508115610c59576001600160a01b038281165f90815260fb60209081526040808320815160a08101835281546001600160401b0381168252600160401b810490961693810193909352600160e01b90940460ff169082015260018301805492939192606084019190610984906139f4565b80601f01602080910402602001604051908101604052809291908181526020018280546109b0906139f4565b80156109fb5780601f106109d2576101008083540402835291602001916109fb565b820191905f5260205f20905b8154815290600101906020018083116109de57829003601f168201915b50505050508152602001600282018054610a14906139f4565b80601f0160208091040260200160405190810160405280929190818152602001828054610a40906139f4565b8015610a8b5780601f10610a6257610100808354040283529160200191610a8b565b820191905f5260205f20905b815481529060010190602001808311610a6e57829003601f168201915b505050505081525050905080604051602001610aa79190613a8b565b6040516020818303038152906040528051906020012085604051602001610ace9190613b11565b6040516020818303038152906040528051906020012014610b0257604051632f9d1d7b60e11b815260040160405180910390fd5b6001600160a01b0383165f90815260fb6020526040812080546001600160e81b031916815590610b35600183018261322a565b610b42600283015f61322a565b50506001600160a01b0383165f81815260fd60205260409020805460ff19166001179055610b779063b8f2e0c560e01b611b46565b8015610b985750610b986001600160a01b03851663b8f2e0c560e01b611b46565b15610c575760405163b8f2e0c560e01b81526001600160a01b0385811660048301525f602483015284169063b8f2e0c5906044015f604051808303815f87803b158015610be3575f5ffd5b505af1158015610bf5573d5f5f3e3d5ffd5b505060405163b8f2e0c560e01b81526001600160a01b038681166004830152600160248301528716925063b8f2e0c591506044015f604051808303815f87803b158015610c40575f5ffd5b505af1158015610c52573d5f5f3e3d5ffd5b505050505b505b6001600160a01b0383165f90815260fb602052604090208490610c7c8282613cb4565b5083905060fc5f610c9060208801886139b2565b6001600160401b031681526020019081526020015f205f866020016020810190610cba9190613331565b6001600160a01b03166001600160a01b031681526020019081526020015f205f6101000a8154816001600160a01b0302191690836001600160a01b031602179055504260fe5f865f016020810190610d1291906139b2565b6001600160401b031681526020019081526020015f205f866020016020810190610d3c9190613331565b6001600160a01b03166001600160a01b031681526020019081526020015f2081905550836020016020810190610d729190613331565b6001600160a01b0316610d8860208601866139b2565b6001600160401b03167f031d68e1805917560c34a5f55a7dd91bef98f911190ed02cdbb53caedae6c39d8486610dc160608a018a6137a2565b610dce60808c018c6137a2565b610dde60608e0160408f01613d72565b604051610df19796959493929190613d8d565b60405180910390a3506106fa600161192d565b5f6002610e1360975460ff1690565b60ff1614905090565b6001600160a01b037f0000000000000000000000000763375000000000000000000000000000000002163003610e6d5760405162461bcd60e51b8152600401610e6490613de4565b60405180910390fd5b7f00000000000000000000000007633750000000000000000000000000000000026001600160a01b0316610e9f611bd3565b6001600160a01b031614610ec55760405162461bcd60e51b8152600401610e6490613e30565b610ece81611bee565b604080515f80825260208201909252610ee991839190611bf6565b50565b5f610ef8848484611d60565b90505b9392505050565b610f0a611db2565b610f14335f611e42565b565b6001600160a01b037f0000000000000000000000000763375000000000000000000000000000000002163003610f5e5760405162461bcd60e51b8152600401610e6490613de4565b7f00000000000000000000000007633750000000000000000000000000000000026001600160a01b0316610f90611bd3565b6001600160a01b031614610fb65760405162461bcd60e51b8152600401610e6490613e30565b610fbf82611bee565b6106ac82826001611bf6565b5f306001600160a01b037f0000000000000000000000000763375000000000000000000000000000000002161461106a5760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610e64565b505f5160206149105f395f51905f5290565b611084611aec565b610f145f611e4a565b6110a1609754610100900460ff1660021490565b156110bf5760405163bae6e2a960e01b815260040160405180910390fd5b60026110cd60975460ff1690565b60ff16036110ee5760405163dfc60d8560e01b815260040160405180910390fd5b6110f8600261192d565b5f80808061110885870187613e87565b93509350935093505f611119611e9b565b905061112483611fba565b5f611130868585611a39565b90506111456001600160a01b03851634611ae1565b836001600160a01b0316856001600160a01b0316835f01517f75a051823424fc80e92556c41cb0ad977ae1dcb09c68a9c38acab86b11a69f8985604001518a6020015186896040516111c594939291906001600160401b039490941684526001600160a01b03928316602085015291166040830152606082015260800190565b60405180910390a45050505050506106ac600161192d565b6111e5611ff6565b6111ed61200f565b610f14336001611e42565b5f611201611bd3565b905090565b60fb6020525f9081526040902080546001820180546001600160401b03831693600160401b84046001600160a01b031693600160e01b900460ff1692909161124d906139f4565b80601f0160208091040260200160405190810160405280929190818152602001828054611279906139f4565b80156112c45780601f1061129b576101008083540402835291602001916112c4565b820191905f5260205f20905b8154815290600101906020018083116112a757829003601f168201915b5050505050908060020180546112d9906139f4565b80601f0160208091040260200160405190810160405280929190818152602001828054611305906139f4565b80156113505780601f1061132757610100808354040283529160200191611350565b820191905f5260205f20905b81548152906001019060200180831161133357829003601f168201915b5050505050905085565b5f610efb468484611d60565b60408051610160810182525f8082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e0820183905261010082018390526101208201929092526101408101919091526113d4609754610100900460ff1660021490565b156113f25760405163bae6e2a960e01b815260040160405180910390fd5b600261140060975460ff1690565b60ff16036114215760405163dfc60d8560e01b815260040160405180910390fd5b61142b600261192d565b8160c001355f0361144f57604051634299323b60e11b815260040160405180910390fd5b5f61146060a0840160808501613331565b6001600160a01b031603611487576040516303f8a7d360e01b815260040160405180910390fd5b60fd5f61149a60a0850160808601613331565b6001600160a01b0316815260208101919091526040015f205460ff16156114d4576040516375c42fc160e01b815260040160405180910390fd5b6114e460808301606084016139b2565b6001600160401b031634101561150d57604051630178ce0b60e31b815260040160405180910390fd5b5f5f5f61151985612080565b6040805161016081019091525f8082529396509194509250602081016115456080890160608a016139b2565b6001600160401b0316815260200161156360c0890160a08a01613f8c565b63ffffffff1681525f60208083018290526040830191909152336060830152608090910190611594908901896139b2565b6001600160401b031681526020015f6001600160a01b03168860200160208101906115bf9190613331565b6001600160a01b0316036115d357336115e3565b6115e36040890160208a01613331565b6001600160a01b0316815260209081019061161a90611604908a018a6139b2565b6a195c98cc8c17dd985d5b1d60aa1b5b5f610eec565b6001600160a01b031681526020016116386080890160608a016139b2565b61164b906001600160401b031634613fa7565b815260200185905290505f6116696562726964676560d01b8261135a565b6001600160a01b0316631bdb003734846040518363ffffffff1660e01b81526004016116959190613696565b5f6040518083038185885af11580156116b0573d5f5f3e3d5ffd5b50505050506040513d5f823e601f3d908101601f191682016040526116d89190810190613fc5565b965090506116ec6060880160408901613331565b6001600160a01b03168660a001516001600160a01b0316827f256f5c87f6ab8d238ac244067613227eb6e2cd65299121135d4f778e8581e03d875f01518b5f01602081019061173b91906139b2565b89602001518d60800160208101906117539190613331565b604080516001600160401b0395861681529390941660208401526001600160a01b03918216838501521660608201526080810189905290519081900360a00190a450505050506117a3600161192d565b919050565b5f54610100900460ff16158080156117c657505f54600160ff909116105b806117df5750303b1580156117df57505f5460ff166001145b6118425760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610e64565b5f805460ff191660011790558015611863575f805461ff0019166101001790555b61186d8383612519565b80156118b2575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050565b6118bf611aec565b6001600160a01b0381166119245760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610e64565b610ee981611e4a565b6097805460ff191660ff92909216919091179055565b604080516060810182525f80825260208201819052918101919091526562726964676560d01b61197481600161135a565b6001600160a01b0316336001600160a01b0316146119a557604051630d85cccf60e11b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa1580156119e1573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a0591906140e5565b60208101519092506001600160a01b03163314611a3557604051632583296b60e01b815260040160405180910390fd5b5090565b5f46845f01516001600160401b031603611a6c57506020830151611a676001600160a01b0382168484612553565b611ad7565b611a75846125b6565b6040516340c10f1960e01b81526001600160a01b03858116600483015260248201859052919250908216906340c10f19906044015f604051808303815f87803b158015611ac0575f5ffd5b505af1158015611ad2573d5f5f3e3d5ffd5b505050505b610efb81836125f7565b6106ac82825a612686565b6033546001600160a01b03163314610f145760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610e64565b5f6001600160a01b0383163b611b5d57505f6106fa565b6040516301ffc9a760e01b81526001600160e01b0319831660048201526001600160a01b038416906301ffc9a790602401602060405180830381865afa925050508015611bc7575060408051601f3d908101601f19168201909252611bc49181019061414d565b60015b156106fa579392505050565b5f5160206149105f395f51905f52546001600160a01b031690565b610ee9611aec565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff1615611c29576118b2836126c9565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611c83575060408051601f3d908101601f19168201909252611c8091810190614168565b60015b611ce65760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610e64565b5f5160206149105f395f51905f528114611d545760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610e64565b506118b2838383612764565b5f611d6b848461278e565b905081158015611d8257506001600160a01b038116155b15610efb57604051632b0d65db60e01b81526001600160401b038516600482015260248101849052604401610e64565b611dc6609754610100900460ff1660021490565b611de35760405163bae6e2a960e01b815260040160405180910390fd5b6097805461010069ffffffffffffffffff001990911662010000426001600160401b031602171790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa906020015b60405180910390a1565b6106ac611aec565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b604080516060810182525f80825260208201819052918101919091526562726964676560d01b611ecc81600161135a565b6001600160a01b0316336001600160a01b031614611efd57604051630d85cccf60e11b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa158015611f39573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611f5d91906140e5565b91505f611f7d83604001516116146a195c98cc8c17dd985d5b1d60aa1b90565b9050806001600160a01b031683602001516001600160a01b031614611fb557604051632583296b60e01b815260040160405180910390fd5b505090565b6001600160a01b0381161580611fd857506001600160a01b03811630145b15610ee957604051635b50f3f960e01b815260040160405180910390fd5b60405163a87dd7cf60e01b815260040160405180910390fd5b612023609754610100900460ff1660021490565b156120415760405163bae6e2a960e01b815260040160405180910390fd5b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a25890602001611e38565b6040805160a0810182525f8082526020820181905291810191909152606081810181905260808201819052905f8060fb816120c160a0880160808901613331565b6001600160a01b03908116825260208201929092526040015f208054909250600160401b900416156122f7576040805160a08101825282546001600160401b0381168252600160401b81046001600160a01b03166020830152600160e01b900460ff169181019190915260018201805483916060840191612141906139f4565b80601f016020809104026020016040519081016040528092919081815260200182805461216d906139f4565b80156121b85780601f1061218f576101008083540402835291602001916121b8565b820191905f5260205f20905b81548152906001019060200180831161219b57829003601f168201915b505050505081526020016002820180546121d1906139f4565b80601f01602080910402602001604051908101604052809291908181526020018280546121fd906139f4565b80156122485780601f1061221f57610100808354040283529160200191612248565b820191905f5260205f20905b81548152906001019060200180831161222b57829003601f168201915b505050505081525050925061228133308760c001358860800160208101906122709190613331565b6001600160a01b0316929190612838565b61229160a0860160808701613331565b604051630852cd8d60e31b815260c087013560048201526001600160a01b0391909116906342966c68906024015f604051808303815f87803b1580156122d5575f5ffd5b505af11580156122e7573d5f5f3e3d5ffd5b505050508460c001359150612499565b6040518060a00160405280466001600160401b031681526020018660800160208101906123249190613331565b6001600160a01b0316815260200161234a61234560a0890160808a01613331565b612870565b60ff16815260200161236a61236560a0890160808a01613331565b612924565b815260200161238761238260a0890160808a01613331565b6129ce565b905292505f61239c60a0870160808801613331565b6040516370a0823160e01b81523060048201529091505f906001600160a01b038316906370a0823190602401602060405180830381865afa1580156123e3573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906124079190614168565b90506124226001600160a01b038316333060c08b0135612838565b6040516370a0823160e01b815230600482015281906001600160a01b038416906370a0823190602401602060405180830381865afa158015612466573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061248a9190614168565b6124949190613fa7565b935050505b30637f07c94784336124b160608a0160408b01613331565b866040516020016124c5949392919061417f565b60408051601f19818403018152908290526124e2916024016141b4565b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050509350509193909250565b806001600160a01b0381166125415760405163538ba4f960e01b815260040160405180910390fd5b61254a83612a14565b6118b282612a72565b6040516001600160a01b0383166024820152604481018290526118b290849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152612ae2565b80516001600160401b03165f90815260fc60209081526040808320828501516001600160a01b03908116855292529091205416806117a3576106fa82612bb5565b5f6126136c38bab7ba30afb6b0b730b3b2b960991b600161135a565b90506001600160a01b038116156118b2576040516315c638fb60e31b81526001600160a01b0384811660048301526024820184905282169063ae31c7d8906044015f604051808303815f87803b15801561266b575f5ffd5b505af115801561267d573d5f5f3e3d5ffd5b50505050505050565b815f0361269257505050565b6126ac83838360405180602001604052805f815250612e00565b6118b257604051634c67134d60e11b815260040160405180910390fd5b6001600160a01b0381163b6127365760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610e64565b5f5160206149105f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b61276d83612e3d565b5f825111806127795750805b156118b2576127888383612e7c565b50505050565b6065545f906001600160a01b0316806127ba57604051638ed88b2560e01b815260040160405180910390fd5b604051630a3dc4f360e21b81526001600160401b0385166004820152602481018490526001600160a01b038216906328f713cc90604401602060405180830381865afa15801561280c573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061283091906141c6565b949350505050565b6040516001600160a01b03808516602483015283166044820152606481018290526127889085906323b872dd60e01b9060840161257f565b60408051600481526024810182526020810180516001600160e01b031663313ce56760e01b17905290515f91829182916001600160a01b038616916128b591906141e1565b5f60405180830381855afa9150503d805f81146128ed576040519150601f19603f3d011682016040523d82523d5f602084013e6128f2565b606091505b5091509150818015612905575080516020145b612910576012612830565b8080602001905181019061283091906141fc565b60408051600481526024810182526020810180516001600160e01b03166395d89b4160e01b17905290516060915f9182916001600160a01b0386169161296a91906141e1565b5f60405180830381855afa9150503d805f81146129a2576040519150601f19603f3d011682016040523d82523d5f602084013e6129a7565b606091505b5091509150816129c55760405180602001604052805f815250612830565b61283081612ea1565b60408051600481526024810182526020810180516001600160e01b03166306fdde0360e01b17905290516060915f9182916001600160a01b0386169161296a91906141e1565b5f54610100900460ff16612a3a5760405162461bcd60e51b8152600401610e6490614217565b612a4261300e565b612a606001600160a01b03821615612a5a5781611e4a565b33611e4a565b506097805461ff001916610100179055565b5f54610100900460ff16612a985760405162461bcd60e51b8152600401610e6490614217565b6001600160401b03461115612ac05760405163a12e8fa960e01b815260040160405180910390fd5b606580546001600160a01b0319166001600160a01b0392909216919091179055565b5f612b36826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166130349092919063ffffffff16565b905080515f1480612b56575080806020019051810190612b56919061414d565b6118b25760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610e64565b5f5f612bc96033546001600160a01b031690565b60655460208501518551604080880151606089015160808a01519251612bff97966001600160a01b031695949390602401614262565b60408051601f19818403018152918152602080830180516001600160e01b031663bb86ef9360e01b1790528581015191519293505f92612c5892910160609190911b6bffffffffffffffffffffffff1916815260140190565b60405160208183030381529060405280519060200120905080612c8b6c0627269646765645f657263323609c1b5f61135a565b83604051612c9890613261565b612ca39291906142cd565b8190604051809103905ff5905080158015612cc0573d5f5f3e3d5ffd5b506001600160a01b038082165f90815260fb602090815260409182902088518154928a0151938a015160ff16600160e01b0260ff60e01b1994909516600160401b026001600160e01b03199093166001600160401b03909116179190911791909116919091178155606086015191945085916001820190612d4190826142f0565b5060808201516002820190612d5690826142f0565b505084516001600160401b039081165f90815260fc60209081526040808320828a0180516001600160a01b039081168652919093529281902080546001600160a01b0319168985169081179091559151895160608b015160808c0151848d01519451959850929095169516937fb6b427556e8cb0ebf9175da4bc48c64c4f56e44cfaf8c3ab5ebf8e2ea130907993612df193919291906143aa565b60405180910390a45050919050565b5f6001600160a01b038516612e2857604051634c67134d60e11b815260040160405180910390fd5b5f5f835160208501878988f195945050505050565b612e46816126c9565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060610efb838360405180606001604052806027815260200161493060279139613042565b60606040825110612ec057818060200190518101906106fa91906143e2565b8151602003612ffb575f5b60208160ff16108015612f005750828160ff1681518110612eee57612eee614413565b01602001516001600160f81b03191615155b15612f175780612f0f81614427565b915050612ecb565b5f8160ff166001600160401b03811115612f3357612f336133f3565b6040519080825280601f01601f191660200182016040528015612f5d576020820181803683370190505b5090505f91505b60208260ff16108015612f995750838260ff1681518110612f8757612f87614413565b01602001516001600160f81b03191615155b15610efb57838260ff1681518110612fb357612fb3614413565b602001015160f81c60f81b818360ff1681518110612fd357612fd3614413565b60200101906001600160f81b03191690815f1a90535081612ff381614427565b925050612f64565b505060408051602081019091525f815290565b5f54610100900460ff16610f145760405162461bcd60e51b8152600401610e6490614217565b6060610ef884845f856130b6565b60605f5f856001600160a01b03168560405161305e91906141e1565b5f60405180830381855af49150503d805f8114613096576040519150601f19603f3d011682016040523d82523d5f602084013e61309b565b606091505b50915091506130ac8683838761318d565b9695505050505050565b6060824710156131175760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610e64565b5f5f866001600160a01b0316858760405161313291906141e1565b5f6040518083038185875af1925050503d805f811461316c576040519150601f19603f3d011682016040523d82523d5f602084013e613171565b606091505b50915091506131828783838761318d565b979650505050505050565b606083156131fb5782515f036131f4576001600160a01b0385163b6131f45760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610e64565b5081612830565b61283083838151156132105781518083602001fd5b8060405162461bcd60e51b8152600401610e6491906141b4565b508054613236906139f4565b5f825580601f10613245575050565b601f0160209004905f5260205f2090810190610ee9919061326e565b6104ca8061444683390190565b5b80821115611a35575f815560010161326f565b5f5f60408385031215613293575f5ffd5b82356001600160401b038111156132a8575f5ffd5b830161016081860312156132ba575f5ffd5b946020939093013593505050565b5f602082840312156132d8575f5ffd5b81356001600160e01b031981168114610efb575f5ffd5b6001600160a01b0381168114610ee9575f5ffd5b5f5f60408385031215613314575f5ffd5b823591506020830135613326816132ef565b809150509250929050565b5f60208284031215613341575f5ffd5b8135610efb816132ef565b5f5f6040838503121561335d575f5ffd5b82356001600160401b03811115613372575f5ffd5b830160a08186031215613383575f5ffd5b91506020830135613326816132ef565b6001600160401b0381168114610ee9575f5ffd5b8015158114610ee9575f5ffd5b5f5f5f606084860312156133c6575f5ffd5b83356133d181613393565b92506020840135915060408401356133e8816133a7565b809150509250925092565b634e487b7160e01b5f52604160045260245ffd5b60405160a081016001600160401b0381118282101715613429576134296133f3565b60405290565b60405161016081016001600160401b0381118282101715613429576134296133f3565b604051601f8201601f191681016001600160401b038111828210171561347a5761347a6133f3565b604052919050565b5f6001600160401b0382111561349a5761349a6133f3565b50601f01601f191660200190565b5f82601f8301126134b7575f5ffd5b8135602083015f6134cf6134ca84613482565b613452565b90508281528583830111156134e2575f5ffd5b828260208301375f92810160200192909252509392505050565b5f5f6040838503121561350d575f5ffd5b8235613518816132ef565b915060208301356001600160401b03811115613532575f5ffd5b61353e858286016134a8565b9150509250929050565b5f5f60208385031215613559575f5ffd5b82356001600160401b0381111561356e575f5ffd5b8301601f8101851361357e575f5ffd5b80356001600160401b03811115613593575f5ffd5b8560208284010111156135a4575f5ffd5b6020919091019590945092505050565b5f5b838110156135ce5781810151838201526020016135b6565b50505f910152565b5f81518084526135ed8160208601602086016135b4565b601f01601f19169290920160200192915050565b6001600160401b03861681526001600160a01b038516602082015260ff8416604082015260a0606082018190525f9061363c908301856135d6565b828103608084015261364e81856135d6565b98975050505050505050565b5f5f6040838503121561366b575f5ffd5b823591506020830135613326816133a7565b5f60e082840312801561368e575f5ffd5b509092915050565b602081526136b06020820183516001600160401b03169052565b5f60208301516136cb60408401826001600160401b03169052565b50604083015163ffffffff811660608401525060608301516001600160a01b03811660808401525060808301516001600160401b03811660a08401525060a08301516001600160a01b03811660c08401525060c08301516001600160401b03811660e08401525060e08301516001600160a01b038116610100840152506101008301516001600160a01b03811661012084015250610120830151610140830152610140830151610160808401526128306101808401826135d6565b5f5f60408385031215613797575f5ffd5b8235613383816132ef565b5f5f8335601e198436030181126137b7575f5ffd5b8301803591506001600160401b038211156137d0575f5ffd5b6020019150368190038213156137e4575f5ffd5b9250929050565b5f5f858511156137f9575f5ffd5b83861115613805575f5ffd5b5050820193919092039150565b5f60208284031215613822575f5ffd5b81356001600160401b03811115613837575f5ffd5b612830848285016134a8565b80516117a381613393565b80516117a3816132ef565b60ff81168114610ee9575f5ffd5b80516117a381613859565b5f82601f830112613881575f5ffd5b8151602083015f6138946134ca84613482565b90508281528583830111156138a7575f5ffd5b6138b58360208301846135b4565b95945050505050565b5f5f5f5f608085870312156138d1575f5ffd5b84516001600160401b038111156138e6575f5ffd5b850160a081880312156138f7575f5ffd5b6138ff613407565b815161390a81613393565b8152602082015161391a816132ef565b602082015261392b60408301613867565b604082015260608201516001600160401b03811115613948575f5ffd5b61395489828501613872565b60608301525060808201516001600160401b03811115613972575f5ffd5b61397e89828501613872565b608083015250945061399490506020860161384e565b92506139a26040860161384e565b6060959095015193969295505050565b5f602082840312156139c2575f5ffd5b8135610efb81613393565b634e487b7160e01b5f52601160045260245ffd5b808201808211156106fa576106fa6139cd565b600181811c90821680613a0857607f821691505b602082108103613a2657634e487b7160e01b5f52602260045260245ffd5b50919050565b6001600160401b03815116825260018060a01b03602082015116602083015260ff60408201511660408301525f606082015160a06060850152613a7260a08501826135d6565b9050608083015184820360808601526138b582826135d6565b602081525f610efb6020830184613a2c565b80356117a381613859565b5f5f8335601e19843603018112613abd575f5ffd5b83016020810192503590506001600160401b03811115613adb575f5ffd5b8036038213156137e4575f5ffd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b602081525f8235613b2181613393565b6001600160401b0381166020840152506020830135613b3f816132ef565b6001600160a01b0316604083810191909152830135613b5d81613859565b60ff8116606084015250613b746060840184613aa8565b60a06080850152613b8960c085018284613ae9565b915050613b996080850185613aa8565b848303601f190160a08601526130ac838284613ae9565b601f8211156118b257805f5260205f20601f840160051c81016020851015613bd55750805b601f840160051c820191505b81811015613bf4575f8155600101613be1565b5050505050565b6001600160401b03831115613c1257613c126133f3565b613c2683613c2083546139f4565b83613bb0565b5f601f841160018114613c57575f8515613c405750838201355b5f19600387901b1c1916600186901b178355613bf4565b5f83815260208120601f198716915b82811015613c865786850135825560209485019460019092019101613c66565b5086821015613ca2575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b8135613cbf81613393565b6001600160401b03811690508154816001600160401b031982161783556020840135613cea816132ef565b6001600160e01b031991909116909117604091821b68010000000000000000600160e01b03161782555f90830135613d2181613859565b825460ff60e01b191660e09190911b60ff60e01b1617825550613d4760608301836137a2565b613d55818360018601613bfb565b5050613d6460808301836137a2565b612788818360028601613bfb565b5f60208284031215613d82575f5ffd5b8135610efb81613859565b6001600160a01b0388811682528716602082015260a0604082018190525f90613db99083018789613ae9565b8281036060840152613dcc818688613ae9565b91505060ff8316608083015298975050505050505050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b80356117a3816132ef565b5f5f5f5f60808587031215613e9a575f5ffd5b84356001600160401b03811115613eaf575f5ffd5b850160a08188031215613ec0575f5ffd5b613ec8613407565b8135613ed381613393565b81526020820135613ee3816132ef565b6020820152613ef460408301613a9d565b604082015260608201356001600160401b03811115613f11575f5ffd5b613f1d898285016134a8565b60608301525060808201356001600160401b03811115613f3b575f5ffd5b613f47898285016134a8565b6080830152509450613f5d905060208601613e7c565b9250613f6b60408601613e7c565b9396929550929360600135925050565b63ffffffff81168114610ee9575f5ffd5b5f60208284031215613f9c575f5ffd5b8135610efb81613f7b565b818103818111156106fa576106fa6139cd565b80516117a381613f7b565b5f5f60408385031215613fd6575f5ffd5b825160208401519092506001600160401b03811115613ff3575f5ffd5b83016101608186031215614005575f5ffd5b61400d61342f565b61401682613843565b815261402460208301613843565b602082015261403560408301613fba565b60408201526140466060830161384e565b606082015261405760808301613843565b608082015261406860a0830161384e565b60a082015261407960c08301613843565b60c082015261408a60e0830161384e565b60e082015261409c610100830161384e565b61010082015261012082810151908201526101408201516001600160401b038111156140c6575f5ffd5b6140d287828501613872565b6101408301525080925050509250929050565b5f60608284031280156140f6575f5ffd5b50604051606081016001600160401b0381118282101715614119576141196133f3565b60405282518152602083015161412e816132ef565b6020820152604083015161414181613393565b60408201529392505050565b5f6020828403121561415d575f5ffd5b8151610efb816133a7565b5f60208284031215614178575f5ffd5b5051919050565b608081525f6141916080830187613a2c565b6001600160a01b0395861660208401529390941660408201526060015292915050565b602081525f610efb60208301846135d6565b5f602082840312156141d6575f5ffd5b8151610efb816132ef565b5f82516141f28184602087016135b4565b9190910192915050565b5f6020828403121561420c575f5ffd5b8151610efb81613859565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6001600160a01b0388811682528781166020830152861660408201526001600160401b038516606082015260ff8416608082015260e060a082018190525f906142ad908301856135d6565b82810360c08401526142bf81856135d6565b9a9950505050505050505050565b6001600160a01b03831681526040602082018190525f90610ef8908301846135d6565b81516001600160401b03811115614309576143096133f3565b61431d8161431784546139f4565b84613bb0565b6020601f82116001811461434f575f83156143385750848201515b5f19600385901b1c1916600184901b178455613bf4565b5f84815260208120601f198516915b8281101561437e578785015182556020948501946001909201910161435e565b508482101561439b57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b606081525f6143bc60608301866135d6565b82810360208401526143ce81866135d6565b91505060ff83166040830152949350505050565b5f602082840312156143f2575f5ffd5b81516001600160401b03811115614407575f5ffd5b61283084828501613872565b634e487b7160e01b5f52603260045260245ffd5b5f60ff821660ff810361443c5761443c6139cd565b6001019291505056fe60806040526040516104ca3803806104ca833981016040819052610022916102d2565b61002d82825f610034565b50506103ed565b61003d8361005f565b5f825111806100495750805b1561005a57610058838361009e565b505b505050565b610068816100ca565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606100c383836040518060600160405280602781526020016104a36027913961017d565b9392505050565b6001600160a01b0381163b61013c5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f5f856001600160a01b03168560405161019991906103a0565b5f60405180830381855af49150503d805f81146101d1576040519150601f19603f3d011682016040523d82523d5f602084013e6101d6565b606091505b5090925090506101e8868383876101f2565b9695505050505050565b606083156102605782515f03610259576001600160a01b0385163b6102595760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610133565b508161026a565b61026a8383610272565b949350505050565b8151156102825781518083602001fd5b8060405162461bcd60e51b815260040161013391906103bb565b634e487b7160e01b5f52604160045260245ffd5b5f5b838110156102ca5781810151838201526020016102b2565b50505f910152565b5f5f604083850312156102e3575f5ffd5b82516001600160a01b03811681146102f9575f5ffd5b60208401519092506001600160401b03811115610314575f5ffd5b8301601f81018513610324575f5ffd5b80516001600160401b0381111561033d5761033d61029c565b604051601f8201601f19908116603f011681016001600160401b038111828210171561036b5761036b61029c565b604052818152828201602001871015610382575f5ffd5b6103938260208301602086016102b0565b8093505050509250929050565b5f82516103b18184602087016102b0565b9190910192915050565b602081525f82518060208401526103d98160408501602087016102b0565b601f01601f19169190910160400192915050565b60aa806103f95f395ff3fe608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea26469706673582212205339f45a82df35ebd66aff7c8a93c20457e46388cef179bebf6908c53edcb0ec64736f6c634300081b0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220eb870cff8087fa27657b4efe8fad5cdc943bf24b50599d04ca860824af536cfe64736f6c634300081b0033", + "code": "0x608060405260043610610195575f3560e01c80634f1ef286116100e75780638456cb59116100875780639aa8605c116100625780639aa8605c1461049b578063a90018a1146104cb578063d56ad7ac146104eb578063f2fde38b14610501575f5ffd5b80638456cb59146104565780638abf60771461046a5780638da5cb5b1461047e575f5ffd5b806367090ccf116100c257806367090ccf146103d15780636f39014414610410578063715018a61461042f5780637f07c94714610443575f5ffd5b80634f1ef2861461038a57806352d1902d1461039d5780635c975abb146103b1575f5ffd5b80630e7eeb791161015257806319ab453c1161012d57806319ab453c146103245780633075db56146103435780633659cfe6146103575780633f4ba83a14610376575f5ffd5b80630e7eeb79146102c45780630ecd8be9146102f25780630ed4342014610311575f5ffd5b80630178733a1461019957806301ffc9a7146101ae578063040944ab146101e257806304f3bcec1461022e578063066fe7b41461026057806306fdde03146102a4575b5f5ffd5b6101ac6101a73660046137f0565b610520565b005b3480156101b9575f5ffd5b506101cd6101c8366004613836565b6106e9565b60405190151581526020015b60405180910390f35b3480156101ed575f5ffd5b506102166101fc36600461385d565b60ff6020525f90815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020016101d9565b348015610239575f5ffd5b507f0000000000000000000000007633750000000000000000000000000000000006610216565b34801561026b575f5ffd5b5061029661027a366004613888565b60fe60209081525f928352604080842090915290825290205481565b6040519081526020016101d9565b3480156102af575f5ffd5b506a195c98cc8c17dd985d5b1d60aa1b610296565b3480156102cf575f5ffd5b506101cd6102de3660046138b6565b60fd6020525f908152604090205460ff1681565b3480156102fd575f5ffd5b5061021661030c3660046138d1565b610739565b6101ac61031f3660046139de565b610e3d565b34801561032f575f5ffd5b506101ac61033e3660046138b6565b611127565b34801561034e575f5ffd5b506101cd611238565b348015610362575f5ffd5b506101ac6103713660046138b6565b611250565b348015610381575f5ffd5b506101ac611314565b6101ac610398366004613ae9565b6113a0565b3480156103a8575f5ffd5b50610296611455565b3480156103bc575f5ffd5b506101cd609754610100900460ff1660021490565b3480156103dc575f5ffd5b506102166103eb366004613888565b60fc60209081525f92835260408084209091529082529020546001600160a01b031681565b34801561041b575f5ffd5b5061029661042a366004613b35565b611506565b34801561043a575f5ffd5b506101ac61155c565b6101ac610451366004613b7a565b61156d565b348015610461575f5ffd5b506101ac611780565b348015610475575f5ffd5b50610216611807565b348015610489575f5ffd5b506033546001600160a01b0316610216565b3480156104a6575f5ffd5b506104ba6104b53660046138b6565b611815565b6040516101d9959493929190613c33565b6104de6104d9366004613c8c565b611969565b6040516101d99190613ca6565b3480156104f6575f5ffd5b506102966276a70081565b34801561050c575f5ffd5b506101ac61051b3660046138b6565b611e1d565b610534609754610100900460ff1660021490565b156105525760405163bae6e2a960e01b815260040160405180910390fd5b600261056060975460ff1690565b60ff16036105815760405163dfc60d8560e01b815260040160405180910390fd5b61058b6002611e93565b610593611ea9565b505f6105a3610140840184613d96565b6105b1916004908290613ddf565b8101906105be9190613e06565b90505f5f5f838060200190518101906105d79190613eb2565b5094509450505092505f81836105ed9190613fd0565b90505f61060a8561060460c08b0160a08c016138b6565b84611f9f565b602086015190915061065d906001600160a01b03161561062f5788610120013561063e565b61063e836101208b0135613fe3565b61064e60c08b0160a08c016138b6565b6001600160a01b03169061206a565b61066d60c0890160a08a016138b6565b6001600160a01b0316877f3dea0f5955b148debf6212261e03bd80eaf8534bee43780452d16637dcc22dd5876020015184886040516106cd939291906001600160a01b039384168152919092166020820152604081019190915260600190565b60405180910390a35050505050506106e56001611e93565b5050565b5f6001600160e01b0319821662bc399d60e11b148061071857506001600160e01b03198216637f07c94760e01b145b8061073357506001600160e01b031982166301ffc9a760e01b145b92915050565b5f610742612075565b600261075060975460ff1690565b60ff16036107715760405163dfc60d8560e01b815260040160405180910390fd5b61077b6002611e93565b6001600160a01b03821615806107b057506001600160a01b038281165f90815260fb6020526040902054600160401b90041615155b806107c357506001600160a01b0382163b155b156107e15760405163dc63f98760e01b815260040160405180910390fd5b5f6107f260408501602086016138b6565b6001600160a01b0316148061081b5750466108106020850185613ff6565b6001600160401b0316145b1561083957604051638257f7f560e01b815260040160405180910390fd5b6001600160a01b0382165f90815260fd602052604090205460ff1615610872576040516375c42fc160e01b815260040160405180910390fd5b5f60fe816108836020870187613ff6565b6001600160401b031681526020019081526020015f205f8560200160208101906108ad91906138b6565b6001600160a01b0316815260208101919091526040015f205490506108d56276a70082613fd0565b4210156108f55760405163231d35fb60e11b815260040160405180910390fd5b60fc5f6109056020870187613ff6565b6001600160401b031681526020019081526020015f205f85602001602081019061092f91906138b6565b6001600160a01b03908116825260208201929092526040015f20541691508115610c92576001600160a01b038281165f90815260fb60209081526040808320815160a08101835281546001600160401b0381168252600160401b810490961693810193909352600160e01b90940460ff1690820152600183018054929391926060840191906109bd90614011565b80601f01602080910402602001604051908101604052809291908181526020018280546109e990614011565b8015610a345780601f10610a0b57610100808354040283529160200191610a34565b820191905f5260205f20905b815481529060010190602001808311610a1757829003601f168201915b50505050508152602001600282018054610a4d90614011565b80601f0160208091040260200160405190810160405280929190818152602001828054610a7990614011565b8015610ac45780601f10610a9b57610100808354040283529160200191610ac4565b820191905f5260205f20905b815481529060010190602001808311610aa757829003601f168201915b505050505081525050905080604051602001610ae091906140a8565b6040516020818303038152906040528051906020012085604051602001610b07919061412e565b6040516020818303038152906040528051906020012014610b3b57604051632f9d1d7b60e11b815260040160405180910390fd5b6001600160a01b0383165f90815260fb6020526040812080546001600160e81b031916815590610b6e6001830182613798565b610b7b600283015f613798565b50506001600160a01b0383165f81815260fd60205260409020805460ff19166001179055610bb09063b8f2e0c560e01b6120cf565b8015610bd15750610bd16001600160a01b03851663b8f2e0c560e01b6120cf565b15610c905760405163b8f2e0c560e01b81526001600160a01b0385811660048301525f602483015284169063b8f2e0c5906044015f604051808303815f87803b158015610c1c575f5ffd5b505af1158015610c2e573d5f5f3e3d5ffd5b505060405163b8f2e0c560e01b81526001600160a01b038681166004830152600160248301528716925063b8f2e0c591506044015f604051808303815f87803b158015610c79575f5ffd5b505af1158015610c8b573d5f5f3e3d5ffd5b505050505b505b6001600160a01b0383165f90815260fb602052604090208490610cb582826142d1565b5083905060fc5f610cc96020880188613ff6565b6001600160401b031681526020019081526020015f205f866020016020810190610cf391906138b6565b6001600160a01b03166001600160a01b031681526020019081526020015f205f6101000a8154816001600160a01b0302191690836001600160a01b031602179055504260fe5f865f016020810190610d4b9190613ff6565b6001600160401b031681526020019081526020015f205f866020016020810190610d7591906138b6565b6001600160a01b03166001600160a01b031681526020019081526020015f2081905550836020016020810190610dab91906138b6565b6001600160a01b0316610dc16020860186613ff6565b6001600160401b03167f031d68e1805917560c34a5f55a7dd91bef98f911190ed02cdbb53caedae6c39d8486610dfa60608a018a613d96565b610e0760808c018c613d96565b610e1760608e0160408f0161438f565b604051610e2a97969594939291906143aa565b60405180910390a3506107336001611e93565b6002610e4b60975460ff1690565b60ff1603610e6c5760405163dfc60d8560e01b815260040160405180910390fd5b610e766002611e93565b610e8a609754610100900460ff1660021490565b15610ea85760405163bae6e2a960e01b815260040160405180910390fd5b60a081015115610fe7575f610ec5647461696b6f60d81b5f61219f565b9050806001600160a01b031663a4b235546040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f03573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f279190614401565b610f4457604051631874710f60e01b815260040160405180910390fd5b608082015160405163888775d960e01b81526001600160401b0390911660048201525f906001600160a01b0383169063888775d99060240161014060405180830381865afa158015610f98573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610fbc919061444d565b5160a08401519091508114610fe457604051630dc3ea9f60e31b815260040160405180910390fd5b50505b5f611003825f0151836020015184604001518560600151611506565b5f81815260ff60205260409020549091506001600160a01b03161561103b57604051638dd940f760e01b815260040160405180910390fd5b5f81815260ff6020908152604090912080546001600160a01b031916331790558201516001600160a01b03166110b8578160600151341461108f57604051634299323b60e11b815260040160405180910390fd5b6110b3826060015183604001516001600160a01b031661206a90919063ffffffff16565b6110e4565b6110e4338360400151846060015185602001516001600160a01b0316612238909392919063ffffffff16565b60405133815281907f4e13900a0e52240bc42a70a941392f3766f6789416493003d0e9e400b0ef32ae9060200160405180910390a2506111246001611e93565b50565b5f54610100900460ff161580801561114557505f54600160ff909116105b8061115e5750303b15801561115e57505f5460ff166001145b6111c65760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b5f805460ff1916600117905580156111e7575f805461ff0019166101001790555b6111f0826122a9565b80156106e5575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15050565b5f600261124760975460ff1690565b60ff1614905090565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000000021630036112985760405162461bcd60e51b81526004016111bd90614510565b7f00000000000000000000000007633750000000000000000000000000000000026001600160a01b03166112ca612307565b6001600160a01b0316146112f05760405162461bcd60e51b81526004016111bd9061455c565b6112f981612322565b604080515f808252602082019092526111249183919061232a565b611328609754610100900460ff1660021490565b6113455760405163bae6e2a960e01b815260040160405180910390fd5b61134d612499565b6113616097805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a161139e335f6124b2565b565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000000021630036113e85760405162461bcd60e51b81526004016111bd90614510565b7f00000000000000000000000007633750000000000000000000000000000000026001600160a01b031661141a612307565b6001600160a01b0316146114405760405162461bcd60e51b81526004016111bd9061455c565b61144982612322565b6106e58282600161232a565b5f306001600160a01b037f000000000000000000000000076337500000000000000000000000000000000216146114f45760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c000000000000000060648201526084016111bd565b505f5160206150455f395f51905f5290565b6040805160208082018790526bffffffffffffffffffffffff19606087811b82168486015286901b166054830152606880830185905283518084039091018152608890920190925280519101205b949350505050565b611564612075565b61139e5f6124ba565b611581609754610100900460ff1660021490565b1561159f5760405163bae6e2a960e01b815260040160405180910390fd5b60026115ad60975460ff1690565b60ff16036115ce5760405163dfc60d8560e01b815260040160405180910390fd5b6115d86002611e93565b5f80808080806115ea878901896145b3565b9550955095509550955095505f6115ff61250b565b905061160a8561262a565b845f841561164f57505f83815260ff60205260409020546001600160a01b0316801561164f575f84815260ff6020526040902080546001600160a01b03191690559050805b5f8061165b8789613fd0565b90506116688b8583611f9f565b91505f6116bc5f6001600160a01b03168d602001516001600160a01b031614611691573461169b565b61169b8334613fe3565b5a60408051602081019091525f81526001600160a01b038e16929190612666565b90506001600160a01b0384166116ea57806116ea57604051632cc319bb60e01b815260040160405180910390fd5b5050835160408086015160208d81015183516001600160a01b0388811682526001600160401b03909416928101929092528216818401528482166060820152608081018b905260a081018a905291518b821693918d16927f153364fe598dfe35e31cd640831e4a90a9effca5f12d8e8ccf2fcb2e14d35bb8919081900360c00190a4505050505050505050506106e56001611e93565b611794609754610100900460ff1660021490565b156117b25760405163bae6e2a960e01b815260040160405180910390fd5b6117ba612499565b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a161139e3360016124b2565b5f611810612307565b905090565b60fb6020525f9081526040902080546001820180546001600160401b03831693600160401b84046001600160a01b031693600160e01b900460ff1692909161185c90614011565b80601f016020809104026020016040519081016040528092919081815260200182805461188890614011565b80156118d35780601f106118aa576101008083540402835291602001916118d3565b820191905f5260205f20905b8154815290600101906020018083116118b657829003601f168201915b5050505050908060020180546118e890614011565b80601f016020809104026020016040519081016040528092919081815260200182805461191490614011565b801561195f5780601f106119365761010080835404028352916020019161195f565b820191905f5260205f20905b81548152906001019060200180831161194257829003601f168201915b5050505050905085565b60408051610160810182525f8082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e0820183905261010082018390526101208201929092526101408101919091526119d7609754610100900460ff1660021490565b156119f55760405163bae6e2a960e01b815260040160405180910390fd5b6002611a0360975460ff1690565b60ff1603611a245760405163dfc60d8560e01b815260040160405180910390fd5b611a2e6002611e93565b8160c001355f03611a5257604051634299323b60e11b815260040160405180910390fd5b5f80611a6460a08501608086016138b6565b6001600160a01b031614611a78575f611a8a565b611a8a60e084013560c0850135613fd0565b905080611a9d6080850160608601613ff6565b6001600160401b0316611ab09190613fd0565b341015611ad057604051630a97785560e21b815260040160405180910390fd5b5f611ae160a08501608086016138b6565b6001600160a01b031614158015611b23575060fd5f611b0660a08601608087016138b6565b6001600160a01b0316815260208101919091526040015f205460ff165b15611b41576040516375c42fc160e01b815260040160405180910390fd5b611b66611b5460608501604086016138b6565b611b616020860186613ff6565b6126a3565b505f611b7b6562726964676560d01b5f61219f565b90505f5f5f5f611b8b8588612701565b93509350935093505f6040518061016001604052805f6001600160401b03168152602001896060016020810190611bc29190613ff6565b6001600160401b03168152602001611be060c08b0160a08c016146cb565b63ffffffff1681525f60208083018290526040830191909152336060830152608090910190611c11908b018b613ff6565b6001600160401b031681526020015f6001600160a01b03168a6020016020810190611c3c91906138b6565b6001600160a01b031603611c505733611c60565b611c6060408b0160208c016138b6565b6001600160a01b03168152602090810190611c9790611c81908c018c613ff6565b6a195c98cc8c17dd985d5b1d60aa1b5b5f612bdf565b6001600160a01b03168152602001611cb560808b0160608c01613ff6565b611cc8906001600160401b031634613fe3565b81526020018681525090505f866001600160a01b0316631bdb003734846040518363ffffffff1660e01b8152600401611d019190613ca6565b5f6040518083038185885af1158015611d1c573d5f5f3e3d5ffd5b50505050506040513d5f823e601f3d908101601f19168201604052611d4491908101906146f1565b98509050611d5860608a0160408b016138b6565b6001600160a01b03168860a001516001600160a01b0316827f5f338013bf4edc4a223ee7b435dd1e5ba722013c222cfbbf3c66372fbf07f295885f01518d5f016020810190611da79190613ff6565b8a602001518f6080016020810190611dbf91906138b6565b604080516001600160401b0395861681529390941660208401526001600160a01b0391821683850152166060820152608081018a905260a0810189905290519081900360c00190a450505050505050611e186001611e93565b919050565b611e25612075565b6001600160a01b038116611e8a5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016111bd565b611124816124ba565b6097805460ff191660ff92909216919091179055565b604080516060810182525f80825260208201819052918101919091526562726964676560d01b611eda81600161219f565b6001600160a01b0316336001600160a01b031614611f0b576040516395383ea160e01b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa158015611f47573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611f6b9190614811565b60208101519092506001600160a01b03163314611f9b57604051632583296b60e01b815260040160405180910390fd5b5090565b60208301515f906001600160a01b0316611fcb57611fc66001600160a01b0384168361206a565b612063565b46845f01516001600160401b031603611ff857506020830151611fc66001600160a01b0382168484612c81565b61200184612cb1565b6040516340c10f1960e01b81526001600160a01b03858116600483015260248201859052919250908216906340c10f19906044015f604051808303815f87803b15801561204c575f5ffd5b505af115801561205e573d5f5f3e3d5ffd5b505050505b9392505050565b6106e582825a612cf2565b6033546001600160a01b0316331461139e5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016111bd565b6040516001600160e01b0319821660248201525f90819081906001600160a01b0386169060440160408051601f198184030181529181526020820180516001600160e01b03166301ffc9a760e01b1790525161212b9190614879565b5f60405180830381855afa9150503d805f8114612163576040519150601f19603f3d011682016040523d82523d5f602084013e612168565b606091505b509150915081801561217b575080516020145b1561219757808060200190518101906121949190614401565b92505b505092915050565b5f7f0000000000000000000000007633750000000000000000000000000000000006604051633632b1fb60e11b81524660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa158015612214573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906120639190614894565b6040516001600160a01b03808516602483015283166044820152606481018290526122a39085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152612d35565b50505050565b5f54610100900460ff166122cf5760405162461bcd60e51b81526004016111bd906148af565b6122d7612e08565b6122f56001600160a01b038216156122ef57816124ba565b336124ba565b506097805461ff001916610100179055565b5f5160206150455f395f51905f52546001600160a01b031690565b611124612075565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156123625761235d83612e2e565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156123bc575060408051601f3d908101601f191682019092526123b9918101906148fa565b60015b61241f5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b60648201526084016111bd565b5f5160206150455f395f51905f52811461248d5760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b60648201526084016111bd565b5061235d838383612ec9565b60405163a87dd7cf60e01b815260040160405180910390fd5b6106e5612075565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b604080516060810182525f80825260208201819052918101919091526562726964676560d01b61253c81600161219f565b6001600160a01b0316336001600160a01b03161461256d576040516395383ea160e01b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa1580156125a9573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906125cd9190614811565b91505f6125ed8360400151611c916a195c98cc8c17dd985d5b1d60aa1b90565b9050806001600160a01b031683602001516001600160a01b03161461262557604051632583296b60e01b815260040160405180910390fd5b505090565b6001600160a01b038116158061264857506001600160a01b03811630145b1561112457604051635b50f3f960e01b815260040160405180910390fd5b5f6001600160a01b03851661268e57604051634c67134d60e11b815260040160405180910390fd5b5f5f835160208501878988f195945050505050565b6001600160a01b03821615806126e357506126ce816a195c98cc8c17dd985d5b1d60aa1b6001612bdf565b6001600160a01b0316826001600160a01b0316145b156106e557604051635b50f3f960e01b815260040160405180910390fd5b6040805160a0810182525f8082526020820181905291810191909152606081810181905260808201819052905f80808061274160a08801608089016138b6565b6001600160a01b031603612762578560c0013592508560e001359150612abd565b5f60fb8161277660a08a0160808b016138b6565b6001600160a01b03908116825260208201929092526040015f2054600160401b900416146129ea5760fb5f6127b160a0890160808a016138b6565b6001600160a01b03908116825260208083019390935260409182015f20825160a08101845281546001600160401b0381168252600160401b810490931694810194909452600160e01b90910460ff169183019190915260018101805460608401919061281c90614011565b80601f016020809104026020016040519081016040528092919081815260200182805461284890614011565b80156128935780601f1061286a57610100808354040283529160200191612893565b820191905f5260205f20905b81548152906001019060200180831161287657829003601f168201915b505050505081526020016002820180546128ac90614011565b80601f01602080910402602001604051908101604052809291908181526020018280546128d890614011565b80156129235780601f106128fa57610100808354040283529160200191612923565b820191905f5260205f20905b81548152906001019060200180831161290657829003601f168201915b50505050508152505093505f8660e001358760c001356129439190613fd0565b905061296c33308361295b60a08c0160808d016138b6565b6001600160a01b0316929190612238565b61297c60a08801608089016138b6565b6001600160a01b03166342966c68826040518263ffffffff1660e01b81526004016129a991815260200190565b5f604051808303815f87803b1580156129c0575f5ffd5b505af11580156129d2573d5f5f3e3d5ffd5b505050508660c0013593508660e00135925050612abd565b6040518060a00160405280466001600160401b03168152602001876080016020810190612a1791906138b6565b6001600160a01b03168152602001612a3d612a3860a08a0160808b016138b6565b612eed565b60ff168152602001612a5d612a5860a08a0160808b016138b6565b612fa1565b8152602001612a7a612a7560a08a0160808b016138b6565b61304b565b90529350612a9b612a9160a08801608089016138b6565b8760c00135613091565b9250612aba612ab060a08801608089016138b6565b8760e00135613091565b91505b60e086013515612b59575f876001600160a01b031663eefbf17e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b04573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612b289190614911565b6001600160401b03169050612b55818660200151896040016020810190612b4f91906138b6565b87611506565b9150505b30637f07c9478533612b7160608b0160408c016138b6565b878787604051602001612b899695949392919061492c565b60408051601f1981840301815290829052612ba691602401614973565b604051602081830303815290604052915060e01b6020820180516001600160e01b03838183161783525050505094505092959194509250565b5f7f0000000000000000000000007633750000000000000000000000000000000006604051633632b1fb60e11b81526001600160401b03861660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa158015612c5d573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115549190614894565b6040516001600160a01b03831660248201526044810182905261235d90849063a9059cbb60e01b9060640161226c565b80516001600160401b03165f90815260fc60209081526040808320828501516001600160a01b0390811685529252909120541680611e185761073382613193565b815f03612cfe57505050565b612d1883838360405180602001604052805f815250612666565b61235d57604051634c67134d60e11b815260040160405180910390fd5b5f612d89826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166133d19092919063ffffffff16565b905080515f1480612da9575080806020019051810190612da99190614401565b61235d5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016111bd565b5f54610100900460ff1661139e5760405162461bcd60e51b81526004016111bd906148af565b6001600160a01b0381163b612e9b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016111bd565b5f5160206150455f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b612ed2836133df565b5f82511180612ede5750805b1561235d576122a3838361341e565b60408051600481526024810182526020810180516001600160e01b031663313ce56760e01b17905290515f91829182916001600160a01b03861691612f329190614879565b5f60405180830381855afa9150503d805f8114612f6a576040519150601f19603f3d011682016040523d82523d5f602084013e612f6f565b606091505b5091509150818015612f82575080516020145b612f8d576012611554565b808060200190518101906115549190614985565b60408051600481526024810182526020810180516001600160e01b03166395d89b4160e01b17905290516060915f9182916001600160a01b03861691612fe79190614879565b5f60405180830381855afa9150503d805f811461301f576040519150601f19603f3d011682016040523d82523d5f602084013e613024565b606091505b5091509150816130425760405180602001604052805f815250611554565b61155481613443565b60408051600481526024810182526020810180516001600160e01b03166306fdde0360e01b17905290516060915f9182916001600160a01b03861691612fe79190614879565b5f815f036130a057505f610733565b6040516370a0823160e01b815230600482015283905f906001600160a01b038316906370a0823190602401602060405180830381865afa1580156130e6573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061310a91906148fa565b90506131216001600160a01b038316333087612238565b6040516370a0823160e01b815230600482015281906001600160a01b038416906370a0823190602401602060405180830381865afa158015613165573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061318991906148fa565b6121949190613fe3565b5f5f6131a76033546001600160a01b031690565b602084015184516040808701516060880151608089015192516131d096959493906024016149a0565b60408051601f19818403018152918152602080830180516001600160e01b0316636c0db62b60e01b1790528581015191519293505f9261322992910160609190911b6bffffffffffffffffffffffff1916815260140190565b6040516020818303038152906040528051906020012090508061325c6c0627269646765645f657263323609c1b5f61219f565b83604051613269906137cf565b613274929190614a02565b8190604051809103905ff5905080158015613291573d5f5f3e3d5ffd5b506001600160a01b038082165f90815260fb602090815260409182902088518154928a0151938a015160ff16600160e01b0260ff60e01b1994909516600160401b026001600160e01b03199093166001600160401b039091161791909117919091169190911781556060860151919450859160018201906133129082614a25565b50608082015160028201906133279082614a25565b505084516001600160401b039081165f90815260fc60209081526040808320828a0180516001600160a01b039081168652919093529281902080546001600160a01b0319168985169081179091559151895160608b015160808c0151848d01519451959850929095169516937fb6b427556e8cb0ebf9175da4bc48c64c4f56e44cfaf8c3ab5ebf8e2ea1309079936133c29391929190614adf565b60405180910390a45050919050565b606061155484845f856135b0565b6133e881612e2e565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060612063838360405180606001604052806027815260200161506560279139613687565b6060604082511061346257818060200190518101906107339190614b17565b815160200361359d575f5b60208160ff161080156134a25750828160ff168151811061349057613490614b48565b01602001516001600160f81b03191615155b156134b957806134b181614b5c565b91505061346d565b5f8160ff166001600160401b038111156134d5576134d5613918565b6040519080825280601f01601f1916602001820160405280156134ff576020820181803683370190505b5090505f91505b60208260ff1610801561353b5750838260ff168151811061352957613529614b48565b01602001516001600160f81b03191615155b1561206357838260ff168151811061355557613555614b48565b602001015160f81c60f81b818360ff168151811061357557613575614b48565b60200101906001600160f81b03191690815f1a9053508161359581614b5c565b925050613506565b505060408051602081019091525f815290565b6060824710156136115760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016111bd565b5f5f866001600160a01b0316858760405161362c9190614879565b5f6040518083038185875af1925050503d805f8114613666576040519150601f19603f3d011682016040523d82523d5f602084013e61366b565b606091505b509150915061367c878383876136fb565b979650505050505050565b60605f5f856001600160a01b0316856040516136a39190614879565b5f60405180830381855af49150503d805f81146136db576040519150601f19603f3d011682016040523d82523d5f602084013e6136e0565b606091505b50915091506136f1868383876136fb565b9695505050505050565b606083156137695782515f03613762576001600160a01b0385163b6137625760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016111bd565b5081611554565b611554838381511561377e5781518083602001fd5b8060405162461bcd60e51b81526004016111bd9190614973565b5080546137a490614011565b5f825580601f106137b3575050565b601f0160209004905f5260205f209081019061112491906137dc565b6104ca80614b7b83390190565b5b80821115611f9b575f81556001016137dd565b5f5f60408385031215613801575f5ffd5b82356001600160401b03811115613816575f5ffd5b83016101608186031215613828575f5ffd5b946020939093013593505050565b5f60208284031215613846575f5ffd5b81356001600160e01b031981168114612063575f5ffd5b5f6020828403121561386d575f5ffd5b5035919050565b6001600160a01b0381168114611124575f5ffd5b5f5f60408385031215613899575f5ffd5b8235915060208301356138ab81613874565b809150509250929050565b5f602082840312156138c6575f5ffd5b813561206381613874565b5f5f604083850312156138e2575f5ffd5b82356001600160401b038111156138f7575f5ffd5b830160a08186031215613908575f5ffd5b915060208301356138ab81613874565b634e487b7160e01b5f52604160045260245ffd5b60405160a081016001600160401b038111828210171561394e5761394e613918565b60405290565b60405161014081016001600160401b038111828210171561394e5761394e613918565b60405161016081016001600160401b038111828210171561394e5761394e613918565b604051601f8201601f191681016001600160401b03811182821017156139c2576139c2613918565b604052919050565b6001600160401b0381168114611124575f5ffd5b5f60c08284031280156139ef575f5ffd5b5060405160c081016001600160401b0381118282101715613a1257613a12613918565b604052823581526020830135613a2781613874565b60208201526040830135613a3a81613874565b6040820152606083810135908201526080830135613a57816139ca565b608082015260a0928301359281019290925250919050565b5f6001600160401b03821115613a8757613a87613918565b50601f01601f191660200190565b5f82601f830112613aa4575f5ffd5b8135602083015f613abc613ab784613a6f565b61399a565b9050828152858383011115613acf575f5ffd5b828260208301375f92810160200192909252509392505050565b5f5f60408385031215613afa575f5ffd5b8235613b0581613874565b915060208301356001600160401b03811115613b1f575f5ffd5b613b2b85828601613a95565b9150509250929050565b5f5f5f5f60808587031215613b48575f5ffd5b843593506020850135613b5a81613874565b92506040850135613b6a81613874565b9396929550929360600135925050565b5f5f60208385031215613b8b575f5ffd5b82356001600160401b03811115613ba0575f5ffd5b8301601f81018513613bb0575f5ffd5b80356001600160401b03811115613bc5575f5ffd5b856020828401011115613bd6575f5ffd5b6020919091019590945092505050565b5f5b83811015613c00578181015183820152602001613be8565b50505f910152565b5f8151808452613c1f816020860160208601613be6565b601f01601f19169290920160200192915050565b6001600160401b03861681526001600160a01b038516602082015260ff8416604082015260a0606082018190525f90613c6e90830185613c08565b8281036080840152613c808185613c08565b98975050505050505050565b5f610100828403128015613c9e575f5ffd5b509092915050565b60208152613cc06020820183516001600160401b03169052565b5f6020830151613cdb60408401826001600160401b03169052565b50604083015163ffffffff811660608401525060608301516001600160a01b03811660808401525060808301516001600160401b03811660a08401525060a08301516001600160a01b03811660c08401525060c08301516001600160401b03811660e08401525060e08301516001600160a01b038116610100840152506101008301516001600160a01b0381166101208401525061012083015161014083015261014083015161016080840152611554610180840182613c08565b5f5f8335601e19843603018112613dab575f5ffd5b8301803591506001600160401b03821115613dc4575f5ffd5b602001915036819003821315613dd8575f5ffd5b9250929050565b5f5f85851115613ded575f5ffd5b83861115613df9575f5ffd5b5050820193919092039150565b5f60208284031215613e16575f5ffd5b81356001600160401b03811115613e2b575f5ffd5b61155484828501613a95565b8051611e18816139ca565b8051611e1881613874565b60ff81168114611124575f5ffd5b8051611e1881613e4d565b5f82601f830112613e75575f5ffd5b8151602083015f613e88613ab784613a6f565b9050828152858383011115613e9b575f5ffd5b613ea9836020830184613be6565b95945050505050565b5f5f5f5f5f5f60c08789031215613ec7575f5ffd5b86516001600160401b03811115613edc575f5ffd5b870160a0818a031215613eed575f5ffd5b613ef561392c565b8151613f00816139ca565b81526020820151613f1081613874565b6020820152613f2160408301613e5b565b604082015260608201516001600160401b03811115613f3e575f5ffd5b613f4a8b828501613e66565b60608301525060808201516001600160401b03811115613f68575f5ffd5b613f748b828501613e66565b6080830152509650613f8a905060208801613e42565b9450613f9860408801613e42565b6060880151608089015160a090990151979a96995090979096909590945092505050565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561073357610733613fbc565b8181038181111561073357610733613fbc565b5f60208284031215614006575f5ffd5b8135612063816139ca565b600181811c9082168061402557607f821691505b60208210810361404357634e487b7160e01b5f52602260045260245ffd5b50919050565b6001600160401b03815116825260018060a01b03602082015116602083015260ff60408201511660408301525f606082015160a0606085015261408f60a0850182613c08565b9050608083015184820360808601526121948282613c08565b602081525f6120636020830184614049565b8035611e1881613e4d565b5f5f8335601e198436030181126140da575f5ffd5b83016020810192503590506001600160401b038111156140f8575f5ffd5b803603821315613dd8575f5ffd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b602081525f823561413e816139ca565b6001600160401b038116602084015250602083013561415c81613874565b6001600160a01b031660408381019190915283013561417a81613e4d565b60ff811660608401525061419160608401846140c5565b60a060808501526141a660c085018284614106565b9150506141b660808501856140c5565b848303601f190160a08601526136f1838284614106565b601f82111561235d57805f5260205f20601f840160051c810160208510156141f25750805b601f840160051c820191505b81811015614211575f81556001016141fe565b5050505050565b6001600160401b0383111561422f5761422f613918565b6142438361423d8354614011565b836141cd565b5f601f841160018114614274575f851561425d5750838201355b5f19600387901b1c1916600186901b178355614211565b5f83815260208120601f198716915b828110156142a35786850135825560209485019460019092019101614283565b50868210156142bf575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b81356142dc816139ca565b6001600160401b03811690508154816001600160401b03198216178355602084013561430781613874565b6001600160e01b031991909116909117604091821b68010000000000000000600160e01b03161782555f9083013561433e81613e4d565b825460ff60e01b191660e09190911b60ff60e01b16178255506143646060830183613d96565b614372818360018601614218565b50506143816080830183613d96565b6122a3818360028601614218565b5f6020828403121561439f575f5ffd5b813561206381613e4d565b6001600160a01b0388811682528716602082015260a0604082018190525f906143d69083018789614106565b82810360608401526143e9818688614106565b91505060ff8316608083015298975050505050505050565b5f60208284031215614411575f5ffd5b81518015158114612063575f5ffd5b80516bffffffffffffffffffffffff81168114611e18575f5ffd5b805162ffffff81168114611e18575f5ffd5b5f61014082840312801561445f575f5ffd5b50614468613954565b8251815261447860208401613e37565b602082015261448960408401614420565b604082015261449a60608401614420565b60608201526144ab60808401613e37565b60808201526144bc60a08401613e37565b60a08201526144cd60c08401613e37565b60c08201526144de60e0840161443b565b60e08201526144f06101008401613e5b565b610100820152614503610120840161443b565b6101208201529392505050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b8035611e1881613874565b5f5f5f5f5f5f60c087890312156145c8575f5ffd5b86356001600160401b038111156145dd575f5ffd5b870160a0818a0312156145ee575f5ffd5b6145f661392c565b8135614601816139ca565b8152602082013561461181613874565b6020820152614622604083016140ba565b604082015260608201356001600160401b0381111561463f575f5ffd5b61464b8b828501613a95565b60608301525060808201356001600160401b03811115614669575f5ffd5b6146758b828501613a95565b608083015250965061468b9050602088016145a8565b9450614699604088016145a8565b959894975094956060810135955060808101359460a0909101359350915050565b63ffffffff81168114611124575f5ffd5b5f602082840312156146db575f5ffd5b8135612063816146ba565b8051611e18816146ba565b5f5f60408385031215614702575f5ffd5b825160208401519092506001600160401b0381111561471f575f5ffd5b83016101608186031215614731575f5ffd5b614739613977565b61474282613e37565b815261475060208301613e37565b6020820152614761604083016146e6565b604082015261477260608301613e42565b606082015261478360808301613e37565b608082015261479460a08301613e42565b60a08201526147a560c08301613e37565b60c08201526147b660e08301613e42565b60e08201526147c86101008301613e42565b61010082015261012082810151908201526101408201516001600160401b038111156147f2575f5ffd5b6147fe87828501613e66565b6101408301525080925050509250929050565b5f6060828403128015614822575f5ffd5b50604051606081016001600160401b038111828210171561484557614845613918565b60405282518152602083015161485a81613874565b6020820152604083015161486d816139ca565b60408201529392505050565b5f825161488a818460208701613be6565b9190910192915050565b5f602082840312156148a4575f5ffd5b815161206381613874565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f6020828403121561490a575f5ffd5b5051919050565b5f60208284031215614921575f5ffd5b8151612063816139ca565b60c081525f61493e60c0830189614049565b6001600160a01b0397881660208401529590961660408201526060810193909352608083019190915260a09091015292915050565b602081525f6120636020830184613c08565b5f60208284031215614995575f5ffd5b815161206381613e4d565b6001600160a01b038781168252861660208201526001600160401b038516604082015260ff8416606082015260c0608082018190525f906149e390830185613c08565b82810360a08401526149f58185613c08565b9998505050505050505050565b6001600160a01b03831681526040602082018190525f9061155490830184613c08565b81516001600160401b03811115614a3e57614a3e613918565b614a5281614a4c8454614011565b846141cd565b6020601f821160018114614a84575f8315614a6d5750848201515b5f19600385901b1c1916600184901b178455614211565b5f84815260208120601f198516915b82811015614ab35787850151825560209485019460019092019101614a93565b5084821015614ad057868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b606081525f614af16060830186613c08565b8281036020840152614b038186613c08565b91505060ff83166040830152949350505050565b5f60208284031215614b27575f5ffd5b81516001600160401b03811115614b3c575f5ffd5b61155484828501613e66565b634e487b7160e01b5f52603260045260245ffd5b5f60ff821660ff8103614b7157614b71613fbc565b6001019291505056fe60806040526040516104ca3803806104ca833981016040819052610022916102d2565b61002d82825f610034565b50506103ed565b61003d8361005f565b5f825111806100495750805b1561005a57610058838361009e565b505b505050565b610068816100ca565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606100c383836040518060600160405280602781526020016104a36027913961017d565b9392505050565b6001600160a01b0381163b61013c5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f5f856001600160a01b03168560405161019991906103a0565b5f60405180830381855af49150503d805f81146101d1576040519150601f19603f3d011682016040523d82523d5f602084013e6101d6565b606091505b5090925090506101e8868383876101f2565b9695505050505050565b606083156102605782515f03610259576001600160a01b0385163b6102595760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610133565b508161026a565b61026a8383610272565b949350505050565b8151156102825781518083602001fd5b8060405162461bcd60e51b815260040161013391906103bb565b634e487b7160e01b5f52604160045260245ffd5b5f5b838110156102ca5781810151838201526020016102b2565b50505f910152565b5f5f604083850312156102e3575f5ffd5b82516001600160a01b03811681146102f9575f5ffd5b60208401519092506001600160401b03811115610314575f5ffd5b8301601f81018513610324575f5ffd5b80516001600160401b0381111561033d5761033d61029c565b604051601f8201601f19908116603f011681016001600160401b038111828210171561036b5761036b61029c565b604052818152828201602001871015610382575f5ffd5b6103938260208301602086016102b0565b8093505050509250929050565b5f82516103b18184602087016102b0565b9190910192915050565b602081525f82518060208401526103d98160408501602087016102b0565b601f01601f19169190910160400192915050565b60aa806103f95f395ff3fe608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220f5985bbf890df2a2cf0f79dd1a9a8e0269da66500bc0f70b3560ee587406619f64736f6c634300081b0033", "balance": "0x0" }, "0x7633750000000000000000000000000000000002": { @@ -125,10 +126,9 @@ "0x0000000000000000000000000000000000000000000000000000000000000097": "0x0000000000000000000000000000000000000000000000000000000000000101", "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000003bc256069ff9af461f3e04494a3ece3f62f183fc", - "0x0000000000000000000000000000000000000000000000000000000000000065": "0x0000000000000000000000007633750000000000000000000000000000000006", "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0763375000000000000000000000000000000002" }, - "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea26469706673582212205339f45a82df35ebd66aff7c8a93c20457e46388cef179bebf6908c53edcb0ec64736f6c634300081b0033", + "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033", "balance": "0x0" }, "0x0763375000000000000000000000000000000003": { @@ -136,7 +136,7 @@ "storage": { "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000003bc256069ff9af461f3e04494a3ece3f62f183fc" }, - "code": "0x608060405260043610610147575f3560e01c80635c975abb116100b35780638da5cb5b1161006d5780638da5cb5b146103955780639aa8605c146103b2578063a86f9d9e146103e1578063e07baba614610400578063f09a40161461043d578063f2fde38b1461045c575f5ffd5b80635c975abb146102e757806367090ccf14610307578063715018a6146103465780637f07c9471461035a5780638456cb591461036d5780638abf607714610381575f5ffd5b80633659cfe6116101045780633659cfe6146102375780633ab76e9f146102565780633eb6b8cf1461028d5780633f4ba83a146102ac5780634f1ef286146102c057806352d1902d146102d3575f5ffd5b80630178733a1461014b57806301ffc9a71461016057806306fdde0314610194578063150b7a02146101bf5780631f59a830146102035780633075db5614610223575b5f5ffd5b61015e610159366004612815565b61047b565b005b34801561016b575f5ffd5b5061017f61017a36600461285b565b61062e565b60405190151581526020015b60405180910390f35b34801561019f575f5ffd5b506b195c98cdcc8c57dd985d5b1d60a21b5b60405190815260200161018b565b3480156101ca575f5ffd5b506101ea6101d93660046128e5565b630a85bd0160e11b95945050505050565b6040516001600160e01b0319909116815260200161018b565b610216610211366004612952565b61067e565b60405161018b91906129d6565b34801561022e575f5ffd5b5061017f610b04565b348015610242575f5ffd5b5061015e610251366004612ac6565b610b1c565b348015610261575f5ffd5b50606554610275906001600160a01b031681565b6040516001600160a01b03909116815260200161018b565b348015610298575f5ffd5b506102756102a7366004612b0d565b610bec565b3480156102b7575f5ffd5b5061015e610c02565b61015e6102ce366004612c78565b610c16565b3480156102de575f5ffd5b506101b1610ccb565b3480156102f2575f5ffd5b5061017f609754610100900460ff1660021490565b348015610312575f5ffd5b50610275610321366004612cc4565b60fc60209081525f92835260408084209091529082529020546001600160a01b031681565b348015610351575f5ffd5b5061015e610d7c565b61015e610368366004612cf2565b610d8d565b348015610378575f5ffd5b5061015e610ef6565b34801561038c575f5ffd5b50610275610f11565b3480156103a0575f5ffd5b506033546001600160a01b0316610275565b3480156103bd575f5ffd5b506103d16103cc366004612ac6565b610f1f565b60405161018b9493929190612d30565b3480156103ec575f5ffd5b506102756103fb366004612d7f565b611069565b34801561040b575f5ffd5b50609754610425906201000090046001600160401b031681565b6040516001600160401b03909116815260200161018b565b348015610448575f5ffd5b5061015e610457366004612da2565b611075565b348015610467575f5ffd5b5061015e610476366004612ac6565b611184565b61048f609754610100900460ff1660021490565b156104ad5760405163bae6e2a960e01b815260040160405180910390fd5b60026104bb60975460ff1690565b60ff16036104dc5760405163dfc60d8560e01b815260040160405180910390fd5b6104e660026111fa565b6104ee611210565b505f6104fe610140840184612dce565b61050c916004908290612e10565b8101906105199190612e37565b90505f5f828060200190518101906105319190612f51565b9350505091505f610555838760a001602081019061054f9190612ac6565b84611306565b905061057f61012087013561057060c0890160a08a01612ac6565b6001600160a01b03169061147d565b61058f60c0870160a08801612ac6565b6001600160a01b0316857fe48bef18455e47bca14864ab6e82dffa29df148b051c09de95aec44ecf13598c8560200151848687516001600160401b038111156105da576105da612b4c565b604051908082528060200260200182016040528015610603578160200160208202803683370190505b506040516106149493929190613090565b60405180910390a35050505061062a60016111fa565b5050565b5f6001600160e01b0319821662bc399d60e11b148061065d57506001600160e01b03198216637f07c94760e01b145b8061067857506001600160e01b031982166301ffc9a760e01b145b92915050565b60408051610160810182525f8082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e0820183905261010082018390526101208201929092526101408101919091526106ec609754610100900460ff1660021490565b1561070a5760405163bae6e2a960e01b815260040160405180910390fd5b61071382613144565b8060e00151518160c00151511461073d5760405163196e8a4160e31b815260040160405180910390fd5b60808101516001600160a01b0316610768576040516303f8a7d360e01b815260040160405180910390fd5b600261077660975460ff1690565b60ff16036107975760405163dfc60d8560e01b815260040160405180910390fd5b6107a160026111fa565b6107b16080840160608501613217565b6001600160401b03163410156107da57604051630178ce0b60e31b815260040160405180910390fd5b5f5b6107e960c0850185613232565b905081101561083d576107ff60e0850185613232565b8281811061080f5761080f613277565b905060200201355f1461083557604051634299323b60e11b815260040160405180910390fd5b6001016107dc565b506108686380ac58cd60e01b61085960a0860160808701612ac6565b6001600160a01b031690611488565b61088557604051633ee915f560e11b815260040160405180910390fd5b5f5f61089085611515565b6040805161016081019091525f808252929450909250602081016108ba6080890160608a01613217565b6001600160401b031681526020016108d860c0890160a08a0161328b565b63ffffffff1681525f6020808301829052604083019190915233606083015260809091019061090990890189613217565b6001600160401b031681526020015f6001600160a01b03168860200160208101906109349190612ac6565b6001600160a01b0316036109485733610958565b6109586040890160208a01612ac6565b6001600160a01b0316815260209081019061099090610979908a018a613217565b6b195c98cdcc8c57dd985d5b1d60a21b5b5f610bec565b6001600160a01b031681526020016109ae6080890160608a01613217565b6109c1906001600160401b0316346132ba565b815260200184905290505f6109df6562726964676560d01b82611069565b6001600160a01b0316631bdb003734846040518363ffffffff1660e01b8152600401610a0b91906129d6565b5f6040518083038185885af1158015610a26573d5f5f3e3d5ffd5b50505050506040513d5f823e601f3d908101601f19168201604052610a4e91908101906132d8565b96509050610a626060880160408901612ac6565b6001600160a01b03168660a001516001600160a01b0316827fabbf62a1459339f9ac59136d313a5ccd83d2706cc6d4c04d90642520169144dc8960c0015187602001518c6080016020810190610ab89190612ac6565b610ac560c08f018f613232565b8f8060e00190610ad59190613232565b604051610ae89796959493929190613428565b60405180910390a450505050610afe60016111fa565b50919050565b5f6002610b1360975460ff1690565b60ff1614905090565b6001600160a01b037f0000000000000000000000000763375000000000000000000000000000000003163003610b6d5760405162461bcd60e51b8152600401610b6490613484565b60405180910390fd5b7f00000000000000000000000007633750000000000000000000000000000000036001600160a01b0316610b9f6119f6565b6001600160a01b031614610bc55760405162461bcd60e51b8152600401610b64906134d0565b610bce81611a11565b604080515f80825260208201909252610be991839190611a19565b50565b5f610bf8848484611b83565b90505b9392505050565b610c0a611bd5565b610c14335f611c65565b565b6001600160a01b037f0000000000000000000000000763375000000000000000000000000000000003163003610c5e5760405162461bcd60e51b8152600401610b6490613484565b7f00000000000000000000000007633750000000000000000000000000000000036001600160a01b0316610c906119f6565b6001600160a01b031614610cb65760405162461bcd60e51b8152600401610b64906134d0565b610cbf82611a11565b61062a82826001611a19565b5f306001600160a01b037f00000000000000000000000007633750000000000000000000000000000000031614610d6a5760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610b64565b505f516020613f245f395f51905f5290565b610d84611c69565b610c145f611cc3565b610da1609754610100900460ff1660021490565b15610dbf5760405163bae6e2a960e01b815260040160405180910390fd5b6002610dcd60975460ff1690565b60ff1603610dee5760405163dfc60d8560e01b815260040160405180910390fd5b610df860026111fa565b5f808080610e088587018761351c565b93509350935093505f610e19611d14565b9050610e2483611e34565b5f610e30868585611306565b9050610e456001600160a01b0385163461147d565b836001600160a01b0316856001600160a01b0316835f01517f895f73e418d1bbbad2a311d085fad00e5d98a960e9f2afa4b942071d39bec43a85604001518a6020015186898a516001600160401b03811115610ea357610ea3612b4c565b604051908082528060200260200182016040528015610ecc578160200160208202803683370190505b50604051610ede959493929190613615565b60405180910390a450505050505061062a60016111fa565b610efe611e70565b610f06611e89565b610c14336001611c65565b5f610f1a6119f6565b905090565b60fb6020525f9081526040902080546001820180546001600160401b03831693600160401b9093046001600160a01b0316929190610f5c9061366d565b80601f0160208091040260200160405190810160405280929190818152602001828054610f889061366d565b8015610fd35780601f10610faa57610100808354040283529160200191610fd3565b820191905f5260205f20905b815481529060010190602001808311610fb657829003601f168201915b505050505090806002018054610fe89061366d565b80601f01602080910402602001604051908101604052809291908181526020018280546110149061366d565b801561105f5780601f106110365761010080835404028352916020019161105f565b820191905f5260205f20905b81548152906001019060200180831161104257829003601f168201915b5050505050905084565b5f610bfb468484611b83565b5f54610100900460ff161580801561109357505f54600160ff909116105b806110ac5750303b1580156110ac57505f5460ff166001145b61110f5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610b64565b5f805460ff191660011790558015611130575f805461ff0019166101001790555b61113a8383611efa565b801561117f575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050565b61118c611c69565b6001600160a01b0381166111f15760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610b64565b610be981611cc3565b6097805460ff191660ff92909216919091179055565b604080516060810182525f80825260208201819052918101919091526562726964676560d01b611241816001611069565b6001600160a01b0316336001600160a01b03161461127257604051630d85cccf60e11b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa1580156112ae573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112d2919061369f565b60208101519092506001600160a01b0316331461130257604051632583296b60e01b815260040160405180910390fd5b5090565b5f46845f01516001600160401b0316036113cc575060208301515f5b82518110156113c657816001600160a01b03166342842e0e308686858151811061134e5761134e613277565b60209081029190910101516040516001600160e01b031960e086901b1681526001600160a01b03938416600482015292909116602483015260448201526064015f604051808303815f87803b1580156113a5575f5ffd5b505af11580156113b7573d5f5f3e3d5ffd5b50505050806001019050611322565b50610bfb565b6113d584611f34565b90505f5b825181101561147557816001600160a01b03166340c10f198585848151811061140457611404613277565b60200260200101516040518363ffffffff1660e01b815260040161143d9291906001600160a01b03929092168252602082015260400190565b5f604051808303815f87803b158015611454575f5ffd5b505af1158015611466573d5f5f3e3d5ffd5b505050508060010190506113d9565b509392505050565b61062a82825a611f7a565b5f6001600160a01b0383163b61149f57505f610678565b6040516301ffc9a760e01b81526001600160e01b0319831660048201526001600160a01b038416906301ffc9a790602401602060405180830381865afa925050508015611509575060408051601f3d908101601f1916820190925261150691810190613707565b60015b15610678579392505050565b604080516080810182525f808252602082015260609181018290528082018290525f60fb8161154a60a0870160808801612ac6565b6001600160a01b03908116825260208201929092526040015f208054909250600160401b9004161561182b576040805160808101825282546001600160401b0381168252600160401b90046001600160a01b031660208201526001830180549192849290840191906115bb9061366d565b80601f01602080910402602001604051908101604052809291908181526020018280546115e79061366d565b80156116325780601f1061160957610100808354040283529160200191611632565b820191905f5260205f20905b81548152906001019060200180831161161557829003601f168201915b5050505050815260200160028201805461164b9061366d565b80601f01602080910402602001604051908101604052809291908181526020018280546116779061366d565b80156116c25780601f10611699576101008083540402835291602001916116c2565b820191905f5260205f20905b8154815290600101906020018083116116a557829003601f168201915b50505050508152505091505f5b6116dc60c0860186613232565b9050811015611825576116f560a0860160808701612ac6565b6001600160a01b03166342842e0e333061171260c08a018a613232565b8681811061172257611722613277565b6040516001600160e01b031960e088901b1681526001600160a01b039586166004820152949093166024850152506020909102013560448201526064015f604051808303815f87803b158015611776575f5ffd5b505af1158015611788573d5f5f3e3d5ffd5b5061179d9250505060a0860160808701612ac6565b6001600160a01b03166342966c686117b860c0880188613232565b848181106117c8576117c8613277565b905060200201356040518263ffffffff1660e01b81526004016117ed91815260200190565b5f604051808303815f87803b158015611804575f5ffd5b505af1158015611816573d5f5f3e3d5ffd5b505050508060010190506116cf565b5061196b565b6040518060800160405280466001600160401b031681526020018560800160208101906118589190612ac6565b6001600160a01b0316815260200161187e61187960a0880160808901612ac6565b611fbd565b815260200161189b61189660a0880160808901612ac6565b61206f565b905291505f5b6118ae60c0860186613232565b9050811015611969576118c760a0860160808701612ac6565b6001600160a01b03166342842e0e33306118e460c08a018a613232565b868181106118f4576118f4613277565b6040516001600160e01b031960e088901b1681526001600160a01b039586166004820152949093166024850152506020909102013560448201526064015f604051808303815f87803b158015611948575f5ffd5b505af115801561195a573d5f5f3e3d5ffd5b505050508060010190506118a1565b505b5030637f07c94782336119846060880160408901612ac6565b61199160c0890189613232565b6040516020016119a5959493929190613722565b60408051601f19818403018152908290526119c2916024016137b3565b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050509150915091565b5f516020613f245f395f51905f52546001600160a01b031690565b610be9611c69565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff1615611a4c5761117f836120b5565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611aa6575060408051601f3d908101601f19168201909252611aa3918101906137c5565b60015b611b095760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610b64565b5f516020613f245f395f51905f528114611b775760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610b64565b5061117f838383612150565b5f611b8e848461217a565b905081158015611ba557506001600160a01b038116155b15610bfb57604051632b0d65db60e01b81526001600160401b038516600482015260248101849052604401610b64565b611be9609754610100900460ff1660021490565b611c065760405163bae6e2a960e01b815260040160405180910390fd5b6097805461010069ffffffffffffffffff001990911662010000426001600160401b031602171790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa906020015b60405180910390a1565b61062a5b6033546001600160a01b03163314610c145760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610b64565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b604080516060810182525f80825260208201819052918101919091526562726964676560d01b611d45816001611069565b6001600160a01b0316336001600160a01b031614611d7657604051630d85cccf60e11b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa158015611db2573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611dd6919061369f565b91505f611df7836040015161098a6b195c98cdcc8c57dd985d5b1d60a21b90565b9050806001600160a01b031683602001516001600160a01b031614611e2f57604051632583296b60e01b815260040160405180910390fd5b505090565b6001600160a01b0381161580611e5257506001600160a01b03811630145b15610be957604051635b50f3f960e01b815260040160405180910390fd5b60405163a87dd7cf60e01b815260040160405180910390fd5b611e9d609754610100900460ff1660021490565b15611ebb5760405163bae6e2a960e01b815260040160405180910390fd5b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a25890602001611c5b565b806001600160a01b038116611f225760405163538ba4f960e01b815260040160405180910390fd5b611f2b8361221c565b61117f8261227a565b80516001600160401b03165f90815260fc60209081526040808320828501516001600160a01b0390811685529252909120541680611f7557610678826122ea565b919050565b815f03611f8657505050565b611fa083838360405180602001604052805f8152506124c3565b61117f57604051634c67134d60e11b815260040160405180910390fd5b60408051600481526024810182526020810180516001600160e01b03166395d89b4160e01b17905290516060915f9182916001600160a01b0386169161200391906137dc565b5f60405180830381855afa9150503d805f811461203b576040519150601f19603f3d011682016040523d82523d5f602084013e612040565b606091505b50915091508161205e5760405180602001604052805f815250612067565b61206781612500565b949350505050565b60408051600481526024810182526020810180516001600160e01b03166306fdde0360e01b17905290516060915f9182916001600160a01b0386169161200391906137dc565b6001600160a01b0381163b6121225760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610b64565b5f516020613f245f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b6121598361266d565b5f825111806121655750805b1561117f5761217483836126ac565b50505050565b6065545f906001600160a01b0316806121a657604051638ed88b2560e01b815260040160405180910390fd5b604051630a3dc4f360e21b81526001600160401b0385166004820152602481018490526001600160a01b038216906328f713cc90604401602060405180830381865afa1580156121f8573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061206791906137f7565b5f54610100900460ff166122425760405162461bcd60e51b8152600401610b6490613812565b61224a6126d1565b6122686001600160a01b038216156122625781611cc3565b33611cc3565b506097805461ff001916610100179055565b5f54610100900460ff166122a05760405162461bcd60e51b8152600401610b6490613812565b6001600160401b034611156122c85760405163a12e8fa960e01b815260040160405180910390fd5b606580546001600160a01b0319166001600160a01b0392909216919091179055565b5f5f6122fe6033546001600160a01b031690565b606554602085015185516040808801516060890151915161232f96956001600160a01b03169493929060240161385d565b60408051601f198184030181529190526020810180516001600160e01b03166377c6257360e11b17905290506123766d627269646765645f65726337323160901b5f611069565b8160405161238390612808565b61238e9291906138be565b604051809103905ff0801580156123a7573d5f5f3e3d5ffd5b506001600160a01b038082165f90815260fb60209081526040918290208751815492890151909416600160401b026001600160e01b03199092166001600160401b03909416939093171782558501519193508491600182019061240a908261392c565b506060820151600282019061241f908261392c565b505083516001600160401b039081165f90815260fc6020908152604080832082890180516001600160a01b039081168652919093529281902080546001600160a01b03191688851690811790915591518851828a015160608b01519351949750919094169493909316927f44977f2d30fe1e3aee2c1476f2f95aaacaf34e44b9359c403da01fcc93fd751b926124b592906139e6565b60405180910390a450919050565b5f6001600160a01b0385166124eb57604051634c67134d60e11b815260040160405180910390fd5b5f5f835160208501878988f195945050505050565b6060604082511061251f57818060200190518101906106789190613a0a565b815160200361265a575f5b60208160ff1610801561255f5750828160ff168151811061254d5761254d613277565b01602001516001600160f81b03191615155b15612576578061256e81613a3b565b91505061252a565b5f8160ff166001600160401b0381111561259257612592612b4c565b6040519080825280601f01601f1916602001820160405280156125bc576020820181803683370190505b5090505f91505b60208260ff161080156125f85750838260ff16815181106125e6576125e6613277565b01602001516001600160f81b03191615155b15610bfb57838260ff168151811061261257612612613277565b602001015160f81c60f81b818360ff168151811061263257612632613277565b60200101906001600160f81b03191690815f1a9053508161265281613a3b565b9250506125c3565b505060408051602081019091525f815290565b612676816120b5565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060610bfb8383604051806060016040528060278152602001613f44602791396126f7565b5f54610100900460ff16610c145760405162461bcd60e51b8152600401610b6490613812565b60605f5f856001600160a01b03168560405161271391906137dc565b5f60405180830381855af49150503d805f811461274b576040519150601f19603f3d011682016040523d82523d5f602084013e612750565b606091505b50915091506127618683838761276b565b9695505050505050565b606083156127d95782515f036127d2576001600160a01b0385163b6127d25760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610b64565b5081612067565b61206783838151156127ee5781518083602001fd5b8060405162461bcd60e51b8152600401610b6491906137b3565b6104ca80613a5a83390190565b5f5f60408385031215612826575f5ffd5b82356001600160401b0381111561283b575f5ffd5b8301610160818603121561284d575f5ffd5b946020939093013593505050565b5f6020828403121561286b575f5ffd5b81356001600160e01b031981168114610bfb575f5ffd5b6001600160a01b0381168114610be9575f5ffd5b8035611f7581612882565b5f5f83601f8401126128b1575f5ffd5b5081356001600160401b038111156128c7575f5ffd5b6020830191508360208285010111156128de575f5ffd5b9250929050565b5f5f5f5f5f608086880312156128f9575f5ffd5b853561290481612882565b9450602086013561291481612882565b93506040860135925060608601356001600160401b03811115612935575f5ffd5b612941888289016128a1565b969995985093965092949392505050565b5f60208284031215612962575f5ffd5b81356001600160401b03811115612977575f5ffd5b82016101008185031215610bfb575f5ffd5b5f5b838110156129a357818101518382015260200161298b565b50505f910152565b5f81518084526129c2816020860160208601612989565b601f01601f19169290920160200192915050565b602081526129f06020820183516001600160401b03169052565b5f6020830151612a0b60408401826001600160401b03169052565b50604083015163ffffffff811660608401525060608301516001600160a01b03811660808401525060808301516001600160401b03811660a08401525060a08301516001600160a01b03811660c08401525060c08301516001600160401b03811660e08401525060e08301516001600160a01b038116610100840152506101008301516001600160a01b03811661012084015250610120830151610140830152610140830151610160808401526120676101808401826129ab565b5f60208284031215612ad6575f5ffd5b8135610bfb81612882565b6001600160401b0381168114610be9575f5ffd5b8035611f7581612ae1565b8015158114610be9575f5ffd5b5f5f5f60608486031215612b1f575f5ffd5b8335612b2a81612ae1565b9250602084013591506040840135612b4181612b00565b809150509250925092565b634e487b7160e01b5f52604160045260245ffd5b604051608081016001600160401b0381118282101715612b8257612b82612b4c565b60405290565b60405161010081016001600160401b0381118282101715612b8257612b82612b4c565b60405161016081016001600160401b0381118282101715612b8257612b82612b4c565b604051601f8201601f191681016001600160401b0381118282101715612bf657612bf6612b4c565b604052919050565b5f6001600160401b03821115612c1657612c16612b4c565b50601f01601f191660200190565b5f82601f830112612c33575f5ffd5b8135602083015f612c4b612c4684612bfe565b612bce565b9050828152858383011115612c5e575f5ffd5b828260208301375f92810160200192909252509392505050565b5f5f60408385031215612c89575f5ffd5b8235612c9481612882565b915060208301356001600160401b03811115612cae575f5ffd5b612cba85828601612c24565b9150509250929050565b5f5f60408385031215612cd5575f5ffd5b823591506020830135612ce781612882565b809150509250929050565b5f5f60208385031215612d03575f5ffd5b82356001600160401b03811115612d18575f5ffd5b612d24858286016128a1565b90969095509350505050565b6001600160401b03851681526001600160a01b03841660208201526080604082018190525f90612d62908301856129ab565b8281036060840152612d7481856129ab565b979650505050505050565b5f5f60408385031215612d90575f5ffd5b823591506020830135612ce781612b00565b5f5f60408385031215612db3575f5ffd5b8235612dbe81612882565b91506020830135612ce781612882565b5f5f8335601e19843603018112612de3575f5ffd5b8301803591506001600160401b03821115612dfc575f5ffd5b6020019150368190038213156128de575f5ffd5b5f5f85851115612e1e575f5ffd5b83861115612e2a575f5ffd5b5050820193919092039150565b5f60208284031215612e47575f5ffd5b81356001600160401b03811115612e5c575f5ffd5b61206784828501612c24565b8051611f7581612ae1565b8051611f7581612882565b5f82601f830112612e8d575f5ffd5b8151602083015f612ea0612c4684612bfe565b9050828152858383011115612eb3575f5ffd5b612ec1836020830184612989565b95945050505050565b5f6001600160401b03821115612ee257612ee2612b4c565b5060051b60200190565b5f82601f830112612efb575f5ffd5b8151612f09612c4682612eca565b8082825260208201915060208360051b860101925085831115612f2a575f5ffd5b602085015b83811015612f47578051835260209283019201612f2f565b5095945050505050565b5f5f5f5f60808587031215612f64575f5ffd5b84516001600160401b03811115612f79575f5ffd5b850160808188031215612f8a575f5ffd5b612f92612b60565b8151612f9d81612ae1565b81526020820151612fad81612882565b602082015260408201516001600160401b03811115612fca575f5ffd5b612fd689828501612e7e565b60408301525060608201516001600160401b03811115612ff4575f5ffd5b61300089828501612e7e565b6060830152509450613016905060208601612e73565b925061302460408601612e73565b915060608501516001600160401b0381111561303e575f5ffd5b61304a87828801612eec565b91505092959194509250565b5f8151808452602084019350602083015f5b82811015613086578151865260209586019590910190600101613068565b5093949350505050565b6001600160a01b038581168252841660208201526080604082018190525f906130bb90830185613056565b8281036060840152612d748185613056565b63ffffffff81168114610be9575f5ffd5b8035611f75816130cd565b5f82601f8301126130f8575f5ffd5b8135613106612c4682612eca565b8082825260208201915060208360051b860101925085831115613127575f5ffd5b602085015b83811015612f4757803583526020928301920161312c565b5f6101008236031215613155575f5ffd5b61315d612b88565b61316683612af5565b815261317460208401612896565b602082015261318560408401612896565b604082015261319660608401612af5565b60608201526131a760808401612896565b60808201526131b860a084016130de565b60a082015260c08301356001600160401b038111156131d5575f5ffd5b6131e1368286016130e9565b60c08301525060e08301356001600160401b038111156131ff575f5ffd5b61320b368286016130e9565b60e08301525092915050565b5f60208284031215613227575f5ffd5b8135610bfb81612ae1565b5f5f8335601e19843603018112613247575f5ffd5b8301803591506001600160401b03821115613260575f5ffd5b6020019150600581901b36038213156128de575f5ffd5b634e487b7160e01b5f52603260045260245ffd5b5f6020828403121561329b575f5ffd5b8135610bfb816130cd565b634e487b7160e01b5f52601160045260245ffd5b81810381811115610678576106786132a6565b8051611f75816130cd565b5f5f604083850312156132e9575f5ffd5b825160208401519092506001600160401b03811115613306575f5ffd5b83016101608186031215613318575f5ffd5b613320612bab565b61332982612e68565b815261333760208301612e68565b6020820152613348604083016132cd565b604082015261335960608301612e73565b606082015261336a60808301612e68565b608082015261337b60a08301612e73565b60a082015261338c60c08301612e68565b60c082015261339d60e08301612e73565b60e08201526133af6101008301612e73565b61010082015261012082810151908201526101408201516001600160401b038111156133d9575f5ffd5b6133e587828501612e7e565b6101408301525080925050509250929050565b8183525f6001600160fb1b0383111561340f575f5ffd5b8260051b80836020870137939093016020019392505050565b6001600160401b03881681526001600160a01b0387811660208301528616604082015260a0606082018190525f9061346390830186886133f8565b82810360808401526134768185876133f8565b9a9950505050505050505050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b5f5f5f5f6080858703121561352f575f5ffd5b84356001600160401b03811115613544575f5ffd5b850160808188031215613555575f5ffd5b61355d612b60565b813561356881612ae1565b8152602082013561357881612882565b602082015260408201356001600160401b03811115613595575f5ffd5b6135a189828501612c24565b60408301525060608201356001600160401b038111156135bf575f5ffd5b6135cb89828501612c24565b60608301525094506135e1905060208601612896565b92506135ef60408601612896565b915060608501356001600160401b03811115613609575f5ffd5b61304a878288016130e9565b6001600160401b03861681526001600160a01b0385811660208301528416604082015260a0606082018190525f9061364f90830185613056565b82810360808401526136618185613056565b98975050505050505050565b600181811c9082168061368157607f821691505b602082108103610afe57634e487b7160e01b5f52602260045260245ffd5b5f60608284031280156136b0575f5ffd5b50604051606081016001600160401b03811182821017156136d3576136d3612b4c565b6040528251815260208301516136e881612882565b602082015260408301516136fb81612ae1565b60408201529392505050565b5f60208284031215613717575f5ffd5b8151610bfb81612b00565b608081526001600160401b03865116608082015260018060a01b0360208701511660a08201525f6040870151608060c08401526137636101008401826129ab565b90506060880151607f198483030160e085015261378082826129ab565b6001600160a01b0389811660208701528816604086015291506137a09050565b82810360608401526136618185876133f8565b602081525f610bfb60208301846129ab565b5f602082840312156137d5575f5ffd5b5051919050565b5f82516137ed818460208701612989565b9190910192915050565b5f60208284031215613807575f5ffd5b8151610bfb81612882565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6001600160a01b0387811682528681166020830152851660408201526001600160401b038416606082015260c0608082018190525f9061389f908301856129ab565b82810360a08401526138b181856129ab565b9998505050505050505050565b6001600160a01b03831681526040602082018190525f90610bf8908301846129ab565b601f82111561117f57805f5260205f20601f840160051c810160208510156139065750805b601f840160051c820191505b81811015613925575f8155600101613912565b5050505050565b81516001600160401b0381111561394557613945612b4c565b61395981613953845461366d565b846138e1565b6020601f82116001811461398b575f83156139745750848201515b5f19600385901b1c1916600184901b178455613925565b5f84815260208120601f198516915b828110156139ba578785015182556020948501946001909201910161399a565b50848210156139d757868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b604081525f6139f860408301856129ab565b8281036020840152612ec181856129ab565b5f60208284031215613a1a575f5ffd5b81516001600160401b03811115613a2f575f5ffd5b61206784828501612e7e565b5f60ff821660ff8103613a5057613a506132a6565b6001019291505056fe60806040526040516104ca3803806104ca833981016040819052610022916102d2565b61002d82825f610034565b50506103ed565b61003d8361005f565b5f825111806100495750805b1561005a57610058838361009e565b505b505050565b610068816100ca565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606100c383836040518060600160405280602781526020016104a36027913961017d565b9392505050565b6001600160a01b0381163b61013c5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f5f856001600160a01b03168560405161019991906103a0565b5f60405180830381855af49150503d805f81146101d1576040519150601f19603f3d011682016040523d82523d5f602084013e6101d6565b606091505b5090925090506101e8868383876101f2565b9695505050505050565b606083156102605782515f03610259576001600160a01b0385163b6102595760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610133565b508161026a565b61026a8383610272565b949350505050565b8151156102825781518083602001fd5b8060405162461bcd60e51b815260040161013391906103bb565b634e487b7160e01b5f52604160045260245ffd5b5f5b838110156102ca5781810151838201526020016102b2565b50505f910152565b5f5f604083850312156102e3575f5ffd5b82516001600160a01b03811681146102f9575f5ffd5b60208401519092506001600160401b03811115610314575f5ffd5b8301601f81018513610324575f5ffd5b80516001600160401b0381111561033d5761033d61029c565b604051601f8201601f19908116603f011681016001600160401b038111828210171561036b5761036b61029c565b604052818152828201602001871015610382575f5ffd5b6103938260208301602086016102b0565b8093505050509250929050565b5f82516103b18184602087016102b0565b9190910192915050565b602081525f82518060208401526103d98160408501602087016102b0565b601f01601f19169190910160400192915050565b60aa806103f95f395ff3fe608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea26469706673582212205339f45a82df35ebd66aff7c8a93c20457e46388cef179bebf6908c53edcb0ec64736f6c634300081b0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122013c8137a65b3211a2c4d57dbdcf9c0d3405cf8f6e3d7e09b5ae9fb86f20b65de64736f6c634300081b0033", + "code": "0x608060405260043610610126575f3560e01c80634f1ef286116100a85780637f07c9471161006d5780637f07c947146103485780638456cb591461035b5780638abf60771461036f5780638da5cb5b146103835780639aa8605c146103a0578063f2fde38b146103cf575f5ffd5b80634f1ef286146102ae57806352d1902d146102c15780635c975abb146102d557806367090ccf146102f5578063715018a614610334575f5ffd5b806319ab453c116100ee57806319ab453c146102285780631f59a830146102475780633075db56146102675780633659cfe61461027b5780633f4ba83a1461029a575f5ffd5b80630178733a1461012a57806301ffc9a71461013f57806304f3bcec1461017357806306fdde03146101b9578063150b7a02146101e4575b5f5ffd5b61013d6101383660046127a2565b6103ee565b005b34801561014a575f5ffd5b5061015e6101593660046127e8565b6105a1565b60405190151581526020015b60405180910390f35b34801561017e575f5ffd5b507f00000000000000000000000076337500000000000000000000000000000000065b6040516001600160a01b03909116815260200161016a565b3480156101c4575f5ffd5b506b195c98cdcc8c57dd985d5b1d60a21b5b60405190815260200161016a565b3480156101ef575f5ffd5b5061020f6101fe366004612872565b630a85bd0160e11b95945050505050565b6040516001600160e01b0319909116815260200161016a565b348015610233575f5ffd5b5061013d6102423660046128df565b6105f1565b61025a6102553660046128fa565b610702565b60405161016a919061297e565b348015610272575f5ffd5b5061015e610bb3565b348015610286575f5ffd5b5061013d6102953660046128df565b610bcb565b3480156102a5575f5ffd5b5061013d610c92565b61013d6102bc366004612b9a565b610d1e565b3480156102cc575f5ffd5b506101d6610dd3565b3480156102e0575f5ffd5b5061015e609754610100900460ff1660021490565b348015610300575f5ffd5b506101a161030f366004612be6565b60fc60209081525f92835260408084209091529082529020546001600160a01b031681565b34801561033f575f5ffd5b5061013d610e84565b61013d610356366004612c14565b610e95565b348015610366575f5ffd5b5061013d610ffe565b34801561037a575f5ffd5b506101a1611085565b34801561038e575f5ffd5b506033546001600160a01b03166101a1565b3480156103ab575f5ffd5b506103bf6103ba3660046128df565b611093565b60405161016a9493929190612c52565b3480156103da575f5ffd5b5061013d6103e93660046128df565b6111dd565b610402609754610100900460ff1660021490565b156104205760405163bae6e2a960e01b815260040160405180910390fd5b600261042e60975460ff1690565b60ff160361044f5760405163dfc60d8560e01b815260040160405180910390fd5b6104596002611253565b610461611269565b505f610471610140840184612ca1565b61047f916004908290612ce3565b81019061048c9190612d0a565b90505f5f828060200190518101906104a49190612e38565b9350505091505f6104c8838760a00160208101906104c291906128df565b8461135f565b90506104f26101208701356104e360c0890160a08a016128df565b6001600160a01b0316906114db565b61050260c0870160a088016128df565b6001600160a01b0316857fe48bef18455e47bca14864ab6e82dffa29df148b051c09de95aec44ecf13598c8560200151848687516001600160401b0381111561054d5761054d612a6e565b604051908082528060200260200182016040528015610576578160200160208202803683370190505b506040516105879493929190612f77565b60405180910390a35050505061059d6001611253565b5050565b5f6001600160e01b0319821662bc399d60e11b14806105d057506001600160e01b03198216637f07c94760e01b145b806105eb57506001600160e01b031982166301ffc9a760e01b145b92915050565b5f54610100900460ff161580801561060f57505f54600160ff909116105b806106285750303b15801561062857505f5460ff166001145b6106905760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b5f805460ff1916600117905580156106b1575f805461ff0019166101001790555b6106ba826114e6565b801561059d575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15050565b60408051610160810182525f8082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e082018390526101008201839052610120820192909252610140810191909152610770609754610100900460ff1660021490565b1561078e5760405163bae6e2a960e01b815260040160405180910390fd5b61079782613036565b8060e00151518160c0015151146107c15760405163196e8a4160e31b815260040160405180910390fd5b60808101516001600160a01b03166107ec576040516303f8a7d360e01b815260040160405180910390fd5b60026107fa60975460ff1690565b60ff160361081b5760405163dfc60d8560e01b815260040160405180910390fd5b6108256002611253565b6108356080840160608501613109565b6001600160401b031634101561085e57604051630178ce0b60e31b815260040160405180910390fd5b5f61086c60c0850185613124565b905090505f5b818110156108c55761088760e0860186613124565b8281811061089757610897613169565b905060200201355f146108bd57604051634299323b60e11b815260040160405180910390fd5b600101610872565b506108f290506380ac58cd60e01b6108e360a08601608087016128df565b6001600160a01b031690611544565b61090f57604051633ee915f560e11b815260040160405180910390fd5b61093461092260608501604086016128df565b61092f6020860186613109565b611614565b5f5f61093f85611673565b6040805161016081019091525f808252929450909250602081016109696080890160608a01613109565b6001600160401b0316815260200161098760c0890160a08a0161317d565b63ffffffff1681525f602080830182905260408301919091523360608301526080909101906109b890890189613109565b6001600160401b031681526020015f6001600160a01b03168860200160208101906109e391906128df565b6001600160a01b0316036109f75733610a07565b610a076040890160208a016128df565b6001600160a01b03168152602090810190610a3f90610a28908a018a613109565b6b195c98cdcc8c57dd985d5b1d60a21b5b5f611b4b565b6001600160a01b03168152602001610a5d6080890160608a01613109565b610a70906001600160401b0316346131ac565b815260200184905290505f610a8e6562726964676560d01b82611bf5565b6001600160a01b0316631bdb003734846040518363ffffffff1660e01b8152600401610aba919061297e565b5f6040518083038185885af1158015610ad5573d5f5f3e3d5ffd5b50505050506040513d5f823e601f3d908101601f19168201604052610afd91908101906131ca565b96509050610b1160608801604089016128df565b6001600160a01b03168660a001516001600160a01b0316827fabbf62a1459339f9ac59136d313a5ccd83d2706cc6d4c04d90642520169144dc8960c0015187602001518c6080016020810190610b6791906128df565b610b7460c08f018f613124565b8f8060e00190610b849190613124565b604051610b97979695949392919061331a565b60405180910390a450505050610bad6001611253565b50919050565b5f6002610bc260975460ff1690565b60ff1614905090565b6001600160a01b037f0000000000000000000000000763375000000000000000000000000000000003163003610c135760405162461bcd60e51b815260040161068790613376565b7f00000000000000000000000007633750000000000000000000000000000000036001600160a01b0316610c45611c95565b6001600160a01b031614610c6b5760405162461bcd60e51b8152600401610687906133c2565b610c7481611cb0565b604080515f80825260208201909252610c8f91839190611cb8565b50565b610ca6609754610100900460ff1660021490565b610cc35760405163bae6e2a960e01b815260040160405180910390fd5b610ccb611e27565b610cdf6097805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610d1c335f611e40565b565b6001600160a01b037f0000000000000000000000000763375000000000000000000000000000000003163003610d665760405162461bcd60e51b815260040161068790613376565b7f00000000000000000000000007633750000000000000000000000000000000036001600160a01b0316610d98611c95565b6001600160a01b031614610dbe5760405162461bcd60e51b8152600401610687906133c2565b610dc782611cb0565b61059d82826001611cb8565b5f306001600160a01b037f00000000000000000000000007633750000000000000000000000000000000031614610e725760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610687565b505f516020613e055f395f51905f5290565b610e8c611e44565b610d1c5f611e9e565b610ea9609754610100900460ff1660021490565b15610ec75760405163bae6e2a960e01b815260040160405180910390fd5b6002610ed560975460ff1690565b60ff1603610ef65760405163dfc60d8560e01b815260040160405180910390fd5b610f006002611253565b5f808080610f108587018761340e565b93509350935093505f610f21611eef565b9050610f2c8361200f565b5f610f3886858561135f565b9050610f4d6001600160a01b038516346114db565b836001600160a01b0316856001600160a01b0316835f01517f895f73e418d1bbbad2a311d085fad00e5d98a960e9f2afa4b942071d39bec43a85604001518a6020015186898a516001600160401b03811115610fab57610fab612a6e565b604051908082528060200260200182016040528015610fd4578160200160208202803683370190505b50604051610fe6959493929190613507565b60405180910390a450505050505061059d6001611253565b611012609754610100900460ff1660021490565b156110305760405163bae6e2a960e01b815260040160405180910390fd5b611038611e27565b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a1610d1c336001611e40565b5f61108e611c95565b905090565b60fb6020525f9081526040902080546001820180546001600160401b03831693600160401b9093046001600160a01b03169291906110d09061355f565b80601f01602080910402602001604051908101604052809291908181526020018280546110fc9061355f565b80156111475780601f1061111e57610100808354040283529160200191611147565b820191905f5260205f20905b81548152906001019060200180831161112a57829003601f168201915b50505050509080600201805461115c9061355f565b80601f01602080910402602001604051908101604052809291908181526020018280546111889061355f565b80156111d35780601f106111aa576101008083540402835291602001916111d3565b820191905f5260205f20905b8154815290600101906020018083116111b657829003601f168201915b5050505050905084565b6111e5611e44565b6001600160a01b03811661124a5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610687565b610c8f81611e9e565b6097805460ff191660ff92909216919091179055565b604080516060810182525f80825260208201819052918101919091526562726964676560d01b61129a816001611bf5565b6001600160a01b0316336001600160a01b0316146112cb576040516395383ea160e01b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa158015611307573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061132b9190613591565b60208101519092506001600160a01b0316331461135b57604051632583296b60e01b815260040160405180910390fd5b5090565b805183515f9190466001600160401b039091160361142957846020015191505f5b8181101561142357826001600160a01b03166342842e0e30878785815181106113ab576113ab613169565b60209081029190910101516040516001600160e01b031960e086901b1681526001600160a01b03938416600482015292909116602483015260448201526064015f604051808303815f87803b158015611402575f5ffd5b505af1158015611414573d5f5f3e3d5ffd5b50505050806001019050611380565b506114d3565b6114328561204b565b91505f5b818110156114d157826001600160a01b03166340c10f198686848151811061146057611460613169565b60200260200101516040518363ffffffff1660e01b81526004016114999291906001600160a01b03929092168252602082015260400190565b5f604051808303815f87803b1580156114b0575f5ffd5b505af11580156114c2573d5f5f3e3d5ffd5b50505050806001019050611436565b505b509392505050565b61059d82825a612091565b5f54610100900460ff1661150c5760405162461bcd60e51b8152600401610687906135f9565b6115146120d4565b6115326001600160a01b0382161561152c5781611e9e565b33611e9e565b506097805461ff001916610100179055565b6040516001600160e01b0319821660248201525f90819081906001600160a01b0386169060440160408051601f198184030181529181526020820180516001600160e01b03166301ffc9a760e01b179052516115a09190613644565b5f60405180830381855afa9150503d805f81146115d8576040519150601f19603f3d011682016040523d82523d5f602084013e6115dd565b606091505b50915091508180156115f0575080516020145b1561160c5780806020019051810190611609919061365f565b92505b505092915050565b6001600160a01b03821615806116555750611640816b195c98cdcc8c57dd985d5b1d60a21b6001611b4b565b6001600160a01b0316826001600160a01b0316145b1561059d57604051635b50f3f960e01b815260040160405180910390fd5b604080516080810182525f808252602082015260609181018290528082018290525f6116a260c0850185613124565b91505f905060fb816116ba60a08801608089016128df565b6001600160a01b03908116825260208201929092526040015f208054909250600160401b9004161561198d576040805160808101825282546001600160401b0381168252600160401b90046001600160a01b0316602082015260018301805491928492908401919061172b9061355f565b80601f01602080910402602001604051908101604052809291908181526020018280546117579061355f565b80156117a25780601f10611779576101008083540402835291602001916117a2565b820191905f5260205f20905b81548152906001019060200180831161178557829003601f168201915b505050505081526020016002820180546117bb9061355f565b80601f01602080910402602001604051908101604052809291908181526020018280546117e79061355f565b80156118325780601f1061180957610100808354040283529160200191611832565b820191905f5260205f20905b81548152906001019060200180831161181557829003601f168201915b50505050508152505092505f5b828110156119875761185760a08701608088016128df565b6001600160a01b03166342842e0e333061187460c08b018b613124565b8681811061188457611884613169565b6040516001600160e01b031960e088901b1681526001600160a01b039586166004820152949093166024850152506020909102013560448201526064015f604051808303815f87803b1580156118d8575f5ffd5b505af11580156118ea573d5f5f3e3d5ffd5b506118ff9250505060a08701608088016128df565b6001600160a01b03166342966c6861191a60c0890189613124565b8481811061192a5761192a613169565b905060200201356040518263ffffffff1660e01b815260040161194f91815260200190565b5f604051808303815f87803b158015611966575f5ffd5b505af1158015611978573d5f5f3e3d5ffd5b5050505080600101905061183f565b50611abf565b6040518060800160405280466001600160401b031681526020018660800160208101906119ba91906128df565b6001600160a01b031681526020016119e06119db60a0890160808a016128df565b6120fa565b81526020016119fd6119f860a0890160808a016128df565b6121a4565b905292505f5b82811015611abd57611a1b60a08701608088016128df565b6001600160a01b03166342842e0e3330611a3860c08b018b613124565b86818110611a4857611a48613169565b6040516001600160e01b031960e088901b1681526001600160a01b039586166004820152949093166024850152506020909102013560448201526064015f604051808303815f87803b158015611a9c575f5ffd5b505af1158015611aae573d5f5f3e3d5ffd5b50505050806001019050611a03565b505b5030637f07c9478333611ad86060890160408a016128df565b611ae560c08a018a613124565b604051602001611af995949392919061367e565b60408051601f1981840301815290829052611b169160240161370f565b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050925050915091565b5f7f0000000000000000000000007633750000000000000000000000000000000006604051633632b1fb60e11b81526001600160401b03861660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa158015611bc9573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611bed9190613721565b949350505050565b5f7f0000000000000000000000007633750000000000000000000000000000000006604051633632b1fb60e11b81524660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa158015611c6a573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c8e9190613721565b9392505050565b5f516020613e055f395f51905f52546001600160a01b031690565b610c8f611e44565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff1615611cf057611ceb836121ea565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611d4a575060408051601f3d908101601f19168201909252611d479181019061373c565b60015b611dad5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610687565b5f516020613e055f395f51905f528114611e1b5760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610687565b50611ceb838383612285565b60405163a87dd7cf60e01b815260040160405180910390fd5b61059d5b6033546001600160a01b03163314610d1c5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610687565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b604080516060810182525f80825260208201819052918101919091526562726964676560d01b611f20816001611bf5565b6001600160a01b0316336001600160a01b031614611f51576040516395383ea160e01b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa158015611f8d573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611fb19190613591565b91505f611fd28360400151610a396b195c98cdcc8c57dd985d5b1d60a21b90565b9050806001600160a01b031683602001516001600160a01b03161461200a57604051632583296b60e01b815260040160405180910390fd5b505090565b6001600160a01b038116158061202d57506001600160a01b03811630145b15610c8f57604051635b50f3f960e01b815260040160405180910390fd5b80516001600160401b03165f90815260fc60209081526040808320828501516001600160a01b039081168552925290912054168061208c576105eb826122af565b919050565b815f0361209d57505050565b6120b783838360405180602001604052805f81525061247b565b611ceb57604051634c67134d60e11b815260040160405180910390fd5b5f54610100900460ff16610d1c5760405162461bcd60e51b8152600401610687906135f9565b60408051600481526024810182526020810180516001600160e01b03166395d89b4160e01b17905290516060915f9182916001600160a01b038616916121409190613644565b5f60405180830381855afa9150503d805f8114612178576040519150601f19603f3d011682016040523d82523d5f602084013e61217d565b606091505b50915091508161219b5760405180602001604052805f815250611bed565b611bed816124b8565b60408051600481526024810182526020810180516001600160e01b03166306fdde0360e01b17905290516060915f9182916001600160a01b038616916121409190613644565b6001600160a01b0381163b6122575760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610687565b5f516020613e055f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b61228e83612625565b5f8251118061229a5750805b15611ceb576122a98383612664565b50505050565b5f5f6122c36033546001600160a01b031690565b60208401518451604080870151606088015191516122e79594939290602401613753565b60408051601f198184030181529190526020810180516001600160e01b031663689ccd8d60e11b179052905061232e6d627269646765645f65726337323160901b5f611bf5565b8160405161233b90612795565b61234692919061379f565b604051809103905ff08015801561235f573d5f5f3e3d5ffd5b506001600160a01b038082165f90815260fb60209081526040918290208751815492890151909416600160401b026001600160e01b03199092166001600160401b0390941693909317178255850151919350849160018201906123c2908261380d565b50606082015160028201906123d7908261380d565b505083516001600160401b039081165f90815260fc6020908152604080832082890180516001600160a01b039081168652919093529281902080546001600160a01b03191688851690811790915591518851828a015160608b01519351949750919094169493909316927f44977f2d30fe1e3aee2c1476f2f95aaacaf34e44b9359c403da01fcc93fd751b9261246d92906138c7565b60405180910390a450919050565b5f6001600160a01b0385166124a357604051634c67134d60e11b815260040160405180910390fd5b5f5f835160208501878988f195945050505050565b606060408251106124d757818060200190518101906105eb91906138eb565b8151602003612612575f5b60208160ff161080156125175750828160ff168151811061250557612505613169565b01602001516001600160f81b03191615155b1561252e57806125268161391c565b9150506124e2565b5f8160ff166001600160401b0381111561254a5761254a612a6e565b6040519080825280601f01601f191660200182016040528015612574576020820181803683370190505b5090505f91505b60208260ff161080156125b05750838260ff168151811061259e5761259e613169565b01602001516001600160f81b03191615155b15611c8e57838260ff16815181106125ca576125ca613169565b602001015160f81c60f81b818360ff16815181106125ea576125ea613169565b60200101906001600160f81b03191690815f1a9053508161260a8161391c565b92505061257b565b505060408051602081019091525f815290565b61262e816121ea565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060611c8e8383604051806060016040528060278152602001613e256027913960605f5f856001600160a01b0316856040516126a09190613644565b5f60405180830381855af49150503d805f81146126d8576040519150601f19603f3d011682016040523d82523d5f602084013e6126dd565b606091505b50915091506126ee868383876126f8565b9695505050505050565b606083156127665782515f0361275f576001600160a01b0385163b61275f5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610687565b5081611bed565b611bed838381511561277b5781518083602001fd5b8060405162461bcd60e51b8152600401610687919061370f565b6104ca8061393b83390190565b5f5f604083850312156127b3575f5ffd5b82356001600160401b038111156127c8575f5ffd5b830161016081860312156127da575f5ffd5b946020939093013593505050565b5f602082840312156127f8575f5ffd5b81356001600160e01b031981168114611c8e575f5ffd5b6001600160a01b0381168114610c8f575f5ffd5b803561208c8161280f565b5f5f83601f84011261283e575f5ffd5b5081356001600160401b03811115612854575f5ffd5b60208301915083602082850101111561286b575f5ffd5b9250929050565b5f5f5f5f5f60808688031215612886575f5ffd5b85356128918161280f565b945060208601356128a18161280f565b93506040860135925060608601356001600160401b038111156128c2575f5ffd5b6128ce8882890161282e565b969995985093965092949392505050565b5f602082840312156128ef575f5ffd5b8135611c8e8161280f565b5f6020828403121561290a575f5ffd5b81356001600160401b0381111561291f575f5ffd5b82016101008185031215611c8e575f5ffd5b5f5b8381101561294b578181015183820152602001612933565b50505f910152565b5f815180845261296a816020860160208601612931565b601f01601f19169290920160200192915050565b602081526129986020820183516001600160401b03169052565b5f60208301516129b360408401826001600160401b03169052565b50604083015163ffffffff811660608401525060608301516001600160a01b03811660808401525060808301516001600160401b03811660a08401525060a08301516001600160a01b03811660c08401525060c08301516001600160401b03811660e08401525060e08301516001600160a01b038116610100840152506101008301516001600160a01b0381166101208401525061012083015161014083015261014083015161016080840152611bed610180840182612953565b634e487b7160e01b5f52604160045260245ffd5b604051608081016001600160401b0381118282101715612aa457612aa4612a6e565b60405290565b60405161010081016001600160401b0381118282101715612aa457612aa4612a6e565b60405161016081016001600160401b0381118282101715612aa457612aa4612a6e565b604051601f8201601f191681016001600160401b0381118282101715612b1857612b18612a6e565b604052919050565b5f6001600160401b03821115612b3857612b38612a6e565b50601f01601f191660200190565b5f82601f830112612b55575f5ffd5b8135602083015f612b6d612b6884612b20565b612af0565b9050828152858383011115612b80575f5ffd5b828260208301375f92810160200192909252509392505050565b5f5f60408385031215612bab575f5ffd5b8235612bb68161280f565b915060208301356001600160401b03811115612bd0575f5ffd5b612bdc85828601612b46565b9150509250929050565b5f5f60408385031215612bf7575f5ffd5b823591506020830135612c098161280f565b809150509250929050565b5f5f60208385031215612c25575f5ffd5b82356001600160401b03811115612c3a575f5ffd5b612c468582860161282e565b90969095509350505050565b6001600160401b03851681526001600160a01b03841660208201526080604082018190525f90612c8490830185612953565b8281036060840152612c968185612953565b979650505050505050565b5f5f8335601e19843603018112612cb6575f5ffd5b8301803591506001600160401b03821115612ccf575f5ffd5b60200191503681900382131561286b575f5ffd5b5f5f85851115612cf1575f5ffd5b83861115612cfd575f5ffd5b5050820193919092039150565b5f60208284031215612d1a575f5ffd5b81356001600160401b03811115612d2f575f5ffd5b611bed84828501612b46565b6001600160401b0381168114610c8f575f5ffd5b805161208c81612d3b565b805161208c8161280f565b5f82601f830112612d74575f5ffd5b8151602083015f612d87612b6884612b20565b9050828152858383011115612d9a575f5ffd5b612da8836020830184612931565b95945050505050565b5f6001600160401b03821115612dc957612dc9612a6e565b5060051b60200190565b5f82601f830112612de2575f5ffd5b8151612df0612b6882612db1565b8082825260208201915060208360051b860101925085831115612e11575f5ffd5b602085015b83811015612e2e578051835260209283019201612e16565b5095945050505050565b5f5f5f5f60808587031215612e4b575f5ffd5b84516001600160401b03811115612e60575f5ffd5b850160808188031215612e71575f5ffd5b612e79612a82565b8151612e8481612d3b565b81526020820151612e948161280f565b602082015260408201516001600160401b03811115612eb1575f5ffd5b612ebd89828501612d65565b60408301525060608201516001600160401b03811115612edb575f5ffd5b612ee789828501612d65565b6060830152509450612efd905060208601612d5a565b9250612f0b60408601612d5a565b915060608501516001600160401b03811115612f25575f5ffd5b612f3187828801612dd3565b91505092959194509250565b5f8151808452602084019350602083015f5b82811015612f6d578151865260209586019590910190600101612f4f565b5093949350505050565b6001600160a01b038581168252841660208201526080604082018190525f90612fa290830185612f3d565b8281036060840152612c968185612f3d565b803561208c81612d3b565b63ffffffff81168114610c8f575f5ffd5b803561208c81612fbf565b5f82601f830112612fea575f5ffd5b8135612ff8612b6882612db1565b8082825260208201915060208360051b860101925085831115613019575f5ffd5b602085015b83811015612e2e57803583526020928301920161301e565b5f6101008236031215613047575f5ffd5b61304f612aaa565b61305883612fb4565b815261306660208401612823565b602082015261307760408401612823565b604082015261308860608401612fb4565b606082015261309960808401612823565b60808201526130aa60a08401612fd0565b60a082015260c08301356001600160401b038111156130c7575f5ffd5b6130d336828601612fdb565b60c08301525060e08301356001600160401b038111156130f1575f5ffd5b6130fd36828601612fdb565b60e08301525092915050565b5f60208284031215613119575f5ffd5b8135611c8e81612d3b565b5f5f8335601e19843603018112613139575f5ffd5b8301803591506001600160401b03821115613152575f5ffd5b6020019150600581901b360382131561286b575f5ffd5b634e487b7160e01b5f52603260045260245ffd5b5f6020828403121561318d575f5ffd5b8135611c8e81612fbf565b634e487b7160e01b5f52601160045260245ffd5b818103818111156105eb576105eb613198565b805161208c81612fbf565b5f5f604083850312156131db575f5ffd5b825160208401519092506001600160401b038111156131f8575f5ffd5b8301610160818603121561320a575f5ffd5b613212612acd565b61321b82612d4f565b815261322960208301612d4f565b602082015261323a604083016131bf565b604082015261324b60608301612d5a565b606082015261325c60808301612d4f565b608082015261326d60a08301612d5a565b60a082015261327e60c08301612d4f565b60c082015261328f60e08301612d5a565b60e08201526132a16101008301612d5a565b61010082015261012082810151908201526101408201516001600160401b038111156132cb575f5ffd5b6132d787828501612d65565b6101408301525080925050509250929050565b8183525f6001600160fb1b03831115613301575f5ffd5b8260051b80836020870137939093016020019392505050565b6001600160401b03881681526001600160a01b0387811660208301528616604082015260a0606082018190525f9061335590830186886132ea565b82810360808401526133688185876132ea565b9a9950505050505050505050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b5f5f5f5f60808587031215613421575f5ffd5b84356001600160401b03811115613436575f5ffd5b850160808188031215613447575f5ffd5b61344f612a82565b813561345a81612d3b565b8152602082013561346a8161280f565b602082015260408201356001600160401b03811115613487575f5ffd5b61349389828501612b46565b60408301525060608201356001600160401b038111156134b1575f5ffd5b6134bd89828501612b46565b60608301525094506134d3905060208601612823565b92506134e160408601612823565b915060608501356001600160401b038111156134fb575f5ffd5b612f3187828801612fdb565b6001600160401b03861681526001600160a01b0385811660208301528416604082015260a0606082018190525f9061354190830185612f3d565b82810360808401526135538185612f3d565b98975050505050505050565b600181811c9082168061357357607f821691505b602082108103610bad57634e487b7160e01b5f52602260045260245ffd5b5f60608284031280156135a2575f5ffd5b50604051606081016001600160401b03811182821017156135c5576135c5612a6e565b6040528251815260208301516135da8161280f565b602082015260408301516135ed81612d3b565b60408201529392505050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f8251613655818460208701612931565b9190910192915050565b5f6020828403121561366f575f5ffd5b81518015158114611c8e575f5ffd5b608081526001600160401b03865116608082015260018060a01b0360208701511660a08201525f6040870151608060c08401526136bf610100840182612953565b90506060880151607f198483030160e08501526136dc8282612953565b6001600160a01b0389811660208701528816604086015291506136fc9050565b82810360608401526135538185876132ea565b602081525f611c8e6020830184612953565b5f60208284031215613731575f5ffd5b8151611c8e8161280f565b5f6020828403121561374c575f5ffd5b5051919050565b6001600160a01b038681168252851660208201526001600160401b038416604082015260a0606082018190525f9061378d90830185612953565b82810360808401526135538185612953565b6001600160a01b03831681526040602082018190525f90611bed90830184612953565b601f821115611ceb57805f5260205f20601f840160051c810160208510156137e75750805b601f840160051c820191505b81811015613806575f81556001016137f3565b5050505050565b81516001600160401b0381111561382657613826612a6e565b61383a81613834845461355f565b846137c2565b6020601f82116001811461386c575f83156138555750848201515b5f19600385901b1c1916600184901b178455613806565b5f84815260208120601f198516915b8281101561389b578785015182556020948501946001909201910161387b565b50848210156138b857868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b604081525f6138d96040830185612953565b8281036020840152612da88185612953565b5f602082840312156138fb575f5ffd5b81516001600160401b03811115613910575f5ffd5b611bed84828501612d65565b5f60ff821660ff810361393157613931613198565b6001019291505056fe60806040526040516104ca3803806104ca833981016040819052610022916102d2565b61002d82825f610034565b50506103ed565b61003d8361005f565b5f825111806100495750805b1561005a57610058838361009e565b505b505050565b610068816100ca565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606100c383836040518060600160405280602781526020016104a36027913961017d565b9392505050565b6001600160a01b0381163b61013c5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f5f856001600160a01b03168560405161019991906103a0565b5f60405180830381855af49150503d805f81146101d1576040519150601f19603f3d011682016040523d82523d5f602084013e6101d6565b606091505b5090925090506101e8868383876101f2565b9695505050505050565b606083156102605782515f03610259576001600160a01b0385163b6102595760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610133565b508161026a565b61026a8383610272565b949350505050565b8151156102825781518083602001fd5b8060405162461bcd60e51b815260040161013391906103bb565b634e487b7160e01b5f52604160045260245ffd5b5f5b838110156102ca5781810151838201526020016102b2565b50505f910152565b5f5f604083850312156102e3575f5ffd5b82516001600160a01b03811681146102f9575f5ffd5b60208401519092506001600160401b03811115610314575f5ffd5b8301601f81018513610324575f5ffd5b80516001600160401b0381111561033d5761033d61029c565b604051601f8201601f19908116603f011681016001600160401b038111828210171561036b5761036b61029c565b604052818152828201602001871015610382575f5ffd5b6103938260208301602086016102b0565b8093505050509250929050565b5f82516103b18184602087016102b0565b9190910192915050565b602081525f82518060208401526103d98160408501602087016102b0565b601f01601f19169190910160400192915050565b60aa806103f95f395ff3fe608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122057d81dde200a5cb820499a4b103c91008793d795e1b7d54c531cf36ed7478ed064736f6c634300081b0033", "balance": "0x0" }, "0x7633750000000000000000000000000000000003": { @@ -145,10 +145,9 @@ "0x0000000000000000000000000000000000000000000000000000000000000097": "0x0000000000000000000000000000000000000000000000000000000000000101", "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000003bc256069ff9af461f3e04494a3ece3f62f183fc", - "0x0000000000000000000000000000000000000000000000000000000000000065": "0x0000000000000000000000007633750000000000000000000000000000000006", "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0763375000000000000000000000000000000003" }, - "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea26469706673582212205339f45a82df35ebd66aff7c8a93c20457e46388cef179bebf6908c53edcb0ec64736f6c634300081b0033", + "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033", "balance": "0x0" }, "0x0763375000000000000000000000000000000004": { @@ -156,7 +155,7 @@ "storage": { "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000003bc256069ff9af461f3e04494a3ece3f62f183fc" }, - "code": "0x608060405260043610610161575f3560e01c806367090ccf116100cd5780639aa8605c11610087578063e07baba611610062578063e07baba61461041e578063f09a40161461045b578063f23a6e611461047a578063f2fde38b146104a6575f5ffd5b80639aa8605c14610389578063a86f9d9e146103b8578063bc197c81146103d7575f5ffd5b806367090ccf146102de578063715018a61461031d5780637f07c947146103315780638456cb59146103445780638abf6077146103585780638da5cb5b1461036c575f5ffd5b80633ab76e9f1161011e5780633ab76e9f1461022d5780633eb6b8cf146102645780633f4ba83a146102835780634f1ef2861461029757806352d1902d146102aa5780635c975abb146102be575f5ffd5b80630178733a1461016557806301ffc9a71461017a57806306fdde03146101ae5780631f59a830146101da5780633075db56146101fa5780633659cfe61461020e575b5f5ffd5b61017861017336600461278c565b6104c5565b005b348015610185575f5ffd5b506101996101943660046127d2565b61063b565b60405190151581526020015b60405180910390f35b3480156101b9575f5ffd5b506c195c98cc4c4d4d57dd985d5b1d609a1b5b6040519081526020016101a5565b6101ed6101e83660046127f9565b61065a565b6040516101a5919061287d565b348015610205575f5ffd5b50610199610ae1565b348015610219575f5ffd5b5061017861022836600461298c565b610af9565b348015610238575f5ffd5b5060655461024c906001600160a01b031681565b6040516001600160a01b0390911681526020016101a5565b34801561026f575f5ffd5b5061024c61027e3660046129d3565b610bc9565b34801561028e575f5ffd5b50610178610bdf565b6101786102a5366004612b3e565b610bf3565b3480156102b5575f5ffd5b506101cc610ca8565b3480156102c9575f5ffd5b50610199609754610100900460ff1660021490565b3480156102e9575f5ffd5b5061024c6102f8366004612b8a565b60fc60209081525f92835260408084209091529082529020546001600160a01b031681565b348015610328575f5ffd5b50610178610d59565b61017861033f366004612bfc565b610d6a565b34801561034f575f5ffd5b50610178610e96565b348015610363575f5ffd5b5061024c610eb1565b348015610377575f5ffd5b506033546001600160a01b031661024c565b348015610394575f5ffd5b506103a86103a336600461298c565b610ebf565b6040516101a59493929190612c3a565b3480156103c3575f5ffd5b5061024c6103d2366004612c89565b611009565b3480156103e2575f5ffd5b506104056103f1366004612cec565b63bc197c8160e01b98975050505050505050565b6040516001600160e01b031990911681526020016101a5565b348015610429575f5ffd5b50609754610443906201000090046001600160401b031681565b6040516001600160401b0390911681526020016101a5565b348015610466575f5ffd5b50610178610475366004612dac565b611015565b348015610485575f5ffd5b50610405610494366004612dd8565b63f23a6e6160e01b9695505050505050565b3480156104b1575f5ffd5b506101786104c036600461298c565b61112c565b6104d9609754610100900460ff1660021490565b156104f75760405163bae6e2a960e01b815260040160405180910390fd5b600261050560975460ff1690565b60ff16036105265760405163dfc60d8560e01b815260040160405180910390fd5b61053060026111a2565b6105386111b8565b505f610548610140840184612e4e565b610556916004908290612e90565b8101906105639190612eb7565b90505f5f5f8380602001905181019061057c9190612fd1565b94509450505092505f6105a3848860a001602081019061059c919061298c565b85856112ae565b90506105cd6101208801356105be60c08a0160a08b0161298c565b6001600160a01b03169061139e565b6105dd60c0880160a0890161298c565b6001600160a01b0316867fe48bef18455e47bca14864ab6e82dffa29df148b051c09de95aec44ecf13598c86602001518487876040516106209493929190613139565b60405180910390a3505050505061063760016111a2565b5050565b5f610645826113a9565b806106545750610654826113f8565b92915050565b60408051610160810182525f8082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e0820183905261010082018390526101208201929092526101408101919091526106c8609754610100900460ff1660021490565b156106e65760405163bae6e2a960e01b815260040160405180910390fd5b6106ef826131ed565b8060e00151518160c0015151146107195760405163196e8a4160e31b815260040160405180910390fd5b60808101516001600160a01b0316610744576040516303f8a7d360e01b815260040160405180910390fd5b600261075260975460ff1690565b60ff16036107735760405163dfc60d8560e01b815260040160405180910390fd5b61077d60026111a2565b61078d60808401606085016132c0565b6001600160401b03163410156107b657604051630178ce0b60e31b815260040160405180910390fd5b5f5b6107c560e08501856132db565b9050811015610819576107db60e08501856132db565b828181106107eb576107eb613320565b905060200201355f0361081157604051634299323b60e11b815260040160405180910390fd5b6001016107b8565b50610844636cdb3d1360e11b61083560a086016080870161298c565b6001600160a01b03169061142c565b61086157604051633ee915f560e11b815260040160405180910390fd5b5f5f61086c856114b9565b6040805161016081019091525f808252929450909250602081016108966080890160608a016132c0565b6001600160401b031681526020016108b460c0890160a08a01613334565b63ffffffff1681525f602080830182905260408301919091523360608301526080909101906108e5908901896132c0565b6001600160401b031681526020015f6001600160a01b0316886020016020810190610910919061298c565b6001600160a01b0316036109245733610934565b6109346040890160208a0161298c565b6001600160a01b0316815260209081019061096d90610955908a018a6132c0565b6c195c98cc4c4d4d57dd985d5b1d609a1b5b5f610bc9565b6001600160a01b0316815260200161098b6080890160608a016132c0565b61099e906001600160401b031634613363565b815260200184905290505f6109bc6562726964676560d01b82611009565b6001600160a01b0316631bdb003734846040518363ffffffff1660e01b81526004016109e8919061287d565b5f6040518083038185885af1158015610a03573d5f5f3e3d5ffd5b50505050506040513d5f823e601f3d908101601f19168201604052610a2b9190810190613381565b96509050610a3f606088016040890161298c565b6001600160a01b03168660a001516001600160a01b0316827fabbf62a1459339f9ac59136d313a5ccd83d2706cc6d4c04d90642520169144dc8960c0015187602001518c6080016020810190610a95919061298c565b610aa260c08f018f6132db565b8f8060e00190610ab291906132db565b604051610ac597969594939291906134d1565b60405180910390a450505050610adb60016111a2565b50919050565b5f6002610af060975460ff1690565b60ff1614905090565b6001600160a01b037f0000000000000000000000000763375000000000000000000000000000000004163003610b4a5760405162461bcd60e51b8152600401610b419061352d565b60405180910390fd5b7f00000000000000000000000007633750000000000000000000000000000000046001600160a01b0316610b7c611978565b6001600160a01b031614610ba25760405162461bcd60e51b8152600401610b4190613579565b610bab81611993565b604080515f80825260208201909252610bc69183919061199b565b50565b5f610bd5848484611b05565b90505b9392505050565b610be7611b57565b610bf1335f611be7565b565b6001600160a01b037f0000000000000000000000000763375000000000000000000000000000000004163003610c3b5760405162461bcd60e51b8152600401610b419061352d565b7f00000000000000000000000007633750000000000000000000000000000000046001600160a01b0316610c6d611978565b6001600160a01b031614610c935760405162461bcd60e51b8152600401610b4190613579565b610c9c82611993565b6106378282600161199b565b5f306001600160a01b037f00000000000000000000000007633750000000000000000000000000000000041614610d475760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610b41565b505f5160206140ff5f395f51905f5290565b610d61611beb565b610bf15f611c45565b610d7e609754610100900460ff1660021490565b15610d9c5760405163bae6e2a960e01b815260040160405180910390fd5b6002610daa60975460ff1690565b60ff1603610dcb5760405163dfc60d8560e01b815260040160405180910390fd5b610dd560026111a2565b5f80808080610de6868801886135c5565b945094509450945094505f610df9611c96565b9050610e0484611db7565b5f610e11878686866112ae565b9050610e266001600160a01b0386163461139e565b846001600160a01b0316866001600160a01b0316835f01517f895f73e418d1bbbad2a311d085fad00e5d98a960e9f2afa4b942071d39bec43a85604001518b60200151868a8a604051610e7d9594939291906136e6565b60405180910390a45050505050505061063760016111a2565b610e9e611df3565b610ea6611e0c565b610bf1336001611be7565b5f610eba611978565b905090565b60fb6020525f9081526040902080546001820180546001600160401b03831693600160401b9093046001600160a01b0316929190610efc9061373e565b80601f0160208091040260200160405190810160405280929190818152602001828054610f289061373e565b8015610f735780601f10610f4a57610100808354040283529160200191610f73565b820191905f5260205f20905b815481529060010190602001808311610f5657829003601f168201915b505050505090806002018054610f889061373e565b80601f0160208091040260200160405190810160405280929190818152602001828054610fb49061373e565b8015610fff5780601f10610fd657610100808354040283529160200191610fff565b820191905f5260205f20905b815481529060010190602001808311610fe257829003601f168201915b5050505050905084565b5f610bd8468484611b05565b5f54610100900460ff161580801561103357505f54600160ff909116105b8061104c5750303b15801561104c57505f5460ff166001145b6110af5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610b41565b5f805460ff1916600117905580156110d0575f805461ff0019166101001790555b6110da8383611e7d565b6110e2611eb7565b8015611127575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050565b611134611beb565b6001600160a01b0381166111995760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610b41565b610bc681611c45565b6097805460ff191660ff92909216919091179055565b604080516060810182525f80825260208201819052918101919091526562726964676560d01b6111e9816001611009565b6001600160a01b0316336001600160a01b03161461121a57604051630d85cccf60e11b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa158015611256573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061127a9190613770565b60208101519092506001600160a01b031633146112aa57604051632583296b60e01b815260040160405180910390fd5b5090565b5f46855f01516001600160401b03160361132c57506020840151604051631759616b60e11b81526001600160a01b03821690632eb2c2d6906112fa9030908890889088906004016137d8565b5f604051808303815f87803b158015611311575f5ffd5b505af1158015611323573d5f5f3e3d5ffd5b50505050611396565b61133585611edd565b60405163d81d0a1560e01b81529091506001600160a01b0382169063d81d0a159061136890879087908790600401613831565b5f604051808303815f87803b15801561137f575f5ffd5b505af1158015611391573d5f5f3e3d5ffd5b505050505b949350505050565b61063782825a611f23565b5f6001600160e01b0319821662bc399d60e11b14806113d857506001600160e01b03198216637f07c94760e01b145b8061065457506001600160e01b031982166301ffc9a760e01b1492915050565b5f6001600160e01b03198216630271189760e51b148061065457506301ffc9a760e01b6001600160e01b0319831614610654565b5f6001600160a01b0383163b61144357505f610654565b6040516301ffc9a760e01b81526001600160e01b0319831660048201526001600160a01b038416906301ffc9a790602401602060405180830381865afa9250505080156114ad575060408051601f3d908101601f191682019092526114aa91810190613866565b60015b15610654579392505050565b604080516080810182525f808252602082015260609181018290528082018290525f60fb816114ee60a087016080880161298c565b6001600160a01b03908116825260208201929092526040015f208054909250600160401b900416156117e1576040805160808101825282546001600160401b0381168252600160401b90046001600160a01b0316602082015260018301805491928492908401919061155f9061373e565b80601f016020809104026020016040519081016040528092919081815260200182805461158b9061373e565b80156115d65780601f106115ad576101008083540402835291602001916115d6565b820191905f5260205f20905b8154815290600101906020018083116115b957829003601f168201915b505050505081526020016002820180546115ef9061373e565b80601f016020809104026020016040519081016040528092919081815260200182805461161b9061373e565b80156116665780601f1061163d57610100808354040283529160200191611666565b820191905f5260205f20905b81548152906001019060200180831161164957829003601f168201915b5050505050815250509150836080016020810190611684919061298c565b6001600160a01b0316632eb2c2d633306116a160c08901896132db565b6116ae60e08b018b6132db565b6040518763ffffffff1660e01b81526004016116cf96959493929190613881565b5f604051808303815f87803b1580156116e6575f5ffd5b505af11580156116f8573d5f5f3e3d5ffd5b505050505f5b61170b60c08601866132db565b90508110156117db5761172460a086016080870161298c565b6001600160a01b031663b390c0ab61173f60c08801886132db565b8481811061174f5761174f613320565b90506020020135878060e0019061176691906132db565b8581811061177657611776613320565b905060200201356040518363ffffffff1660e01b81526004016117a3929190918252602082015260400190565b5f604051808303815f87803b1580156117ba575f5ffd5b505af11580156117cc573d5f5f3e3d5ffd5b505050508060010190506116fe565b506118de565b6040518060800160405280466001600160401b0316815260200185608001602081019061180e919061298c565b6001600160a01b0316815260200161183461182f60a088016080890161298c565b611f66565b815260200161185161184c60a088016080890161298c565b612010565b9052915061186560a085016080860161298c565b6001600160a01b0316632eb2c2d6333061188260c08901896132db565b61188f60e08b018b6132db565b6040518763ffffffff1660e01b81526004016118b096959493929190613881565b5f604051808303815f87803b1580156118c7575f5ffd5b505af11580156118d9573d5f5f3e3d5ffd5b505050505b5030637f07c94782336118f7606088016040890161298c565b61190460c08901896132db565b61191160e08b018b6132db565b60405160200161192797969594939291906138de565b60408051601f19818403018152908290526119449160240161398e565b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050509150915091565b5f5160206140ff5f395f51905f52546001600160a01b031690565b610bc6611beb565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156119ce5761112783612056565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611a28575060408051601f3d908101601f19168201909252611a25918101906139a0565b60015b611a8b5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610b41565b5f5160206140ff5f395f51905f528114611af95760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610b41565b506111278383836120f1565b5f611b10848461211b565b905081158015611b2757506001600160a01b038116155b15610bd857604051632b0d65db60e01b81526001600160401b038516600482015260248101849052604401610b41565b611b6b609754610100900460ff1660021490565b611b885760405163bae6e2a960e01b815260040160405180910390fd5b6097805461010069ffffffffffffffffff001990911662010000426001600160401b031602171790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa906020015b60405180910390a1565b6106375b6033546001600160a01b03163314610bf15760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610b41565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b604080516060810182525f80825260208201819052918101919091526562726964676560d01b611cc7816001611009565b6001600160a01b0316336001600160a01b031614611cf857604051630d85cccf60e11b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa158015611d34573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d589190613770565b91505f611d7a83604001516109676c195c98cc4c4d4d57dd985d5b1d609a1b90565b9050806001600160a01b031683602001516001600160a01b031614611db257604051632583296b60e01b815260040160405180910390fd5b505090565b6001600160a01b0381161580611dd557506001600160a01b03811630145b15610bc657604051635b50f3f960e01b815260040160405180910390fd5b60405163a87dd7cf60e01b815260040160405180910390fd5b611e20609754610100900460ff1660021490565b15611e3e5760405163bae6e2a960e01b815260040160405180910390fd5b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a25890602001611bdd565b806001600160a01b038116611ea55760405163538ba4f960e01b815260040160405180910390fd5b611eae836121bd565b6111278261221b565b5f54610100900460ff16610bf15760405162461bcd60e51b8152600401610b41906139b7565b80516001600160401b03165f90815260fc60209081526040808320828501516001600160a01b0390811685529252909120541680611f1e576106548261228b565b919050565b815f03611f2f57505050565b611f4983838360405180602001604052805f815250612465565b61112757604051634c67134d60e11b815260040160405180910390fd5b60408051600481526024810182526020810180516001600160e01b03166395d89b4160e01b17905290516060915f9182916001600160a01b03861691611fac9190613a02565b5f60405180830381855afa9150503d805f8114611fe4576040519150601f19603f3d011682016040523d82523d5f602084013e611fe9565b606091505b5091509150816120075760405180602001604052805f815250611396565b611396816124a2565b60408051600481526024810182526020810180516001600160e01b03166306fdde0360e01b17905290516060915f9182916001600160a01b03861691611fac9190613a02565b6001600160a01b0381163b6120c35760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610b41565b5f5160206140ff5f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b6120fa8361260f565b5f825111806121065750805b1561112757612115838361264e565b50505050565b6065545f906001600160a01b03168061214757604051638ed88b2560e01b815260040160405180910390fd5b604051630a3dc4f360e21b81526001600160401b0385166004820152602481018490526001600160a01b038216906328f713cc90604401602060405180830381865afa158015612199573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113969190613a1d565b5f54610100900460ff166121e35760405162461bcd60e51b8152600401610b41906139b7565b6121eb611eb7565b6122096001600160a01b038216156122035781611c45565b33611c45565b506097805461ff001916610100179055565b5f54610100900460ff166122415760405162461bcd60e51b8152600401610b41906139b7565b6001600160401b034611156122695760405163a12e8fa960e01b815260040160405180910390fd5b606580546001600160a01b0319166001600160a01b0392909216919091179055565b5f5f61229f6033546001600160a01b031690565b60655460208501518551604080880151606089015191516122d096956001600160a01b031694939290602401613a38565b60408051601f198184030181529190526020810180516001600160e01b03166377c6257360e11b17905290506123186e627269646765645f6572633131353560881b5f611009565b816040516123259061277f565b612330929190613a99565b604051809103905ff080158015612349573d5f5f3e3d5ffd5b506001600160a01b038082165f90815260fb60209081526040918290208751815492890151909416600160401b026001600160e01b03199092166001600160401b0390941693909317178255850151919350849160018201906123ac9082613b07565b50606082015160028201906123c19082613b07565b505083516001600160401b039081165f90815260fc6020908152604080832082890180516001600160a01b039081168652919093529281902080546001600160a01b03191688851690811790915591518851828a015160608b01519351949750919094169493909316927f44977f2d30fe1e3aee2c1476f2f95aaacaf34e44b9359c403da01fcc93fd751b926124579290613bc1565b60405180910390a450919050565b5f6001600160a01b03851661248d57604051634c67134d60e11b815260040160405180910390fd5b5f5f835160208501878988f195945050505050565b606060408251106124c157818060200190518101906106549190613be5565b81516020036125fc575f5b60208160ff161080156125015750828160ff16815181106124ef576124ef613320565b01602001516001600160f81b03191615155b15612518578061251081613c16565b9150506124cc565b5f8160ff166001600160401b0381111561253457612534612a12565b6040519080825280601f01601f19166020018201604052801561255e576020820181803683370190505b5090505f91505b60208260ff1610801561259a5750838260ff168151811061258857612588613320565b01602001516001600160f81b03191615155b15610bd857838260ff16815181106125b4576125b4613320565b602001015160f81c60f81b818360ff16815181106125d4576125d4613320565b60200101906001600160f81b03191690815f1a905350816125f481613c16565b925050612565565b505060408051602081019091525f815290565b61261881612056565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060610bd8838360405180606001604052806027815260200161411f6027913960605f5f856001600160a01b03168560405161268a9190613a02565b5f60405180830381855af49150503d805f81146126c2576040519150601f19603f3d011682016040523d82523d5f602084013e6126c7565b606091505b50915091506126d8868383876126e2565b9695505050505050565b606083156127505782515f03612749576001600160a01b0385163b6127495760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610b41565b5081611396565b61139683838151156127655781518083602001fd5b8060405162461bcd60e51b8152600401610b41919061398e565b6104ca80613c3583390190565b5f5f6040838503121561279d575f5ffd5b82356001600160401b038111156127b2575f5ffd5b830161016081860312156127c4575f5ffd5b946020939093013593505050565b5f602082840312156127e2575f5ffd5b81356001600160e01b031981168114610bd8575f5ffd5b5f60208284031215612809575f5ffd5b81356001600160401b0381111561281e575f5ffd5b82016101008185031215610bd8575f5ffd5b5f5b8381101561284a578181015183820152602001612832565b50505f910152565b5f8151808452612869816020860160208601612830565b601f01601f19169290920160200192915050565b602081526128976020820183516001600160401b03169052565b5f60208301516128b260408401826001600160401b03169052565b50604083015163ffffffff811660608401525060608301516001600160a01b03811660808401525060808301516001600160401b03811660a08401525060a08301516001600160a01b03811660c08401525060c08301516001600160401b03811660e08401525060e08301516001600160a01b038116610100840152506101008301516001600160a01b0381166101208401525061012083015161014083015261014083015161016080840152611396610180840182612852565b6001600160a01b0381168114610bc6575f5ffd5b8035611f1e8161296d565b5f6020828403121561299c575f5ffd5b8135610bd88161296d565b6001600160401b0381168114610bc6575f5ffd5b8035611f1e816129a7565b8015158114610bc6575f5ffd5b5f5f5f606084860312156129e5575f5ffd5b83356129f0816129a7565b9250602084013591506040840135612a07816129c6565b809150509250925092565b634e487b7160e01b5f52604160045260245ffd5b604051608081016001600160401b0381118282101715612a4857612a48612a12565b60405290565b60405161010081016001600160401b0381118282101715612a4857612a48612a12565b60405161016081016001600160401b0381118282101715612a4857612a48612a12565b604051601f8201601f191681016001600160401b0381118282101715612abc57612abc612a12565b604052919050565b5f6001600160401b03821115612adc57612adc612a12565b50601f01601f191660200190565b5f82601f830112612af9575f5ffd5b8135602083015f612b11612b0c84612ac4565b612a94565b9050828152858383011115612b24575f5ffd5b828260208301375f92810160200192909252509392505050565b5f5f60408385031215612b4f575f5ffd5b8235612b5a8161296d565b915060208301356001600160401b03811115612b74575f5ffd5b612b8085828601612aea565b9150509250929050565b5f5f60408385031215612b9b575f5ffd5b823591506020830135612bad8161296d565b809150509250929050565b5f5f83601f840112612bc8575f5ffd5b5081356001600160401b03811115612bde575f5ffd5b602083019150836020828501011115612bf5575f5ffd5b9250929050565b5f5f60208385031215612c0d575f5ffd5b82356001600160401b03811115612c22575f5ffd5b612c2e85828601612bb8565b90969095509350505050565b6001600160401b03851681526001600160a01b03841660208201526080604082018190525f90612c6c90830185612852565b8281036060840152612c7e8185612852565b979650505050505050565b5f5f60408385031215612c9a575f5ffd5b823591506020830135612bad816129c6565b5f5f83601f840112612cbc575f5ffd5b5081356001600160401b03811115612cd2575f5ffd5b6020830191508360208260051b8501011115612bf5575f5ffd5b5f5f5f5f5f5f5f5f60a0898b031215612d03575f5ffd5b8835612d0e8161296d565b97506020890135612d1e8161296d565b965060408901356001600160401b03811115612d38575f5ffd5b612d448b828c01612cac565b90975095505060608901356001600160401b03811115612d62575f5ffd5b612d6e8b828c01612cac565b90955093505060808901356001600160401b03811115612d8c575f5ffd5b612d988b828c01612bb8565b999c989b5096995094979396929594505050565b5f5f60408385031215612dbd575f5ffd5b8235612dc88161296d565b91506020830135612bad8161296d565b5f5f5f5f5f5f60a08789031215612ded575f5ffd5b8635612df88161296d565b95506020870135612e088161296d565b9450604087013593506060870135925060808701356001600160401b03811115612e30575f5ffd5b612e3c89828a01612bb8565b979a9699509497509295939492505050565b5f5f8335601e19843603018112612e63575f5ffd5b8301803591506001600160401b03821115612e7c575f5ffd5b602001915036819003821315612bf5575f5ffd5b5f5f85851115612e9e575f5ffd5b83861115612eaa575f5ffd5b5050820193919092039150565b5f60208284031215612ec7575f5ffd5b81356001600160401b03811115612edc575f5ffd5b61139684828501612aea565b8051611f1e816129a7565b8051611f1e8161296d565b5f82601f830112612f0d575f5ffd5b8151602083015f612f20612b0c84612ac4565b9050828152858383011115612f33575f5ffd5b612f41836020830184612830565b95945050505050565b5f6001600160401b03821115612f6257612f62612a12565b5060051b60200190565b5f82601f830112612f7b575f5ffd5b8151612f89612b0c82612f4a565b8082825260208201915060208360051b860101925085831115612faa575f5ffd5b602085015b83811015612fc7578051835260209283019201612faf565b5095945050505050565b5f5f5f5f5f60a08688031215612fe5575f5ffd5b85516001600160401b03811115612ffa575f5ffd5b86016080818903121561300b575f5ffd5b613013612a26565b815161301e816129a7565b8152602082015161302e8161296d565b602082015260408201516001600160401b0381111561304b575f5ffd5b6130578a828501612efe565b60408301525060608201516001600160401b03811115613075575f5ffd5b6130818a828501612efe565b6060830152509550613097905060208701612ef3565b93506130a560408701612ef3565b925060608601516001600160401b038111156130bf575f5ffd5b6130cb88828901612f6c565b92505060808601516001600160401b038111156130e6575f5ffd5b6130f288828901612f6c565b9150509295509295909350565b5f8151808452602084019350602083015f5b8281101561312f578151865260209586019590910190600101613111565b5093949350505050565b6001600160a01b038581168252841660208201526080604082018190525f90613164908301856130ff565b8281036060840152612c7e81856130ff565b63ffffffff81168114610bc6575f5ffd5b8035611f1e81613176565b5f82601f8301126131a1575f5ffd5b81356131af612b0c82612f4a565b8082825260208201915060208360051b8601019250858311156131d0575f5ffd5b602085015b83811015612fc75780358352602092830192016131d5565b5f61010082360312156131fe575f5ffd5b613206612a4e565b61320f836129bb565b815261321d60208401612981565b602082015261322e60408401612981565b604082015261323f606084016129bb565b606082015261325060808401612981565b608082015261326160a08401613187565b60a082015260c08301356001600160401b0381111561327e575f5ffd5b61328a36828601613192565b60c08301525060e08301356001600160401b038111156132a8575f5ffd5b6132b436828601613192565b60e08301525092915050565b5f602082840312156132d0575f5ffd5b8135610bd8816129a7565b5f5f8335601e198436030181126132f0575f5ffd5b8301803591506001600160401b03821115613309575f5ffd5b6020019150600581901b3603821315612bf5575f5ffd5b634e487b7160e01b5f52603260045260245ffd5b5f60208284031215613344575f5ffd5b8135610bd881613176565b634e487b7160e01b5f52601160045260245ffd5b818103818111156106545761065461334f565b8051611f1e81613176565b5f5f60408385031215613392575f5ffd5b825160208401519092506001600160401b038111156133af575f5ffd5b830161016081860312156133c1575f5ffd5b6133c9612a71565b6133d282612ee8565b81526133e060208301612ee8565b60208201526133f160408301613376565b604082015261340260608301612ef3565b606082015261341360808301612ee8565b608082015261342460a08301612ef3565b60a082015261343560c08301612ee8565b60c082015261344660e08301612ef3565b60e08201526134586101008301612ef3565b61010082015261012082810151908201526101408201516001600160401b03811115613482575f5ffd5b61348e87828501612efe565b6101408301525080925050509250929050565b8183525f6001600160fb1b038311156134b8575f5ffd5b8260051b80836020870137939093016020019392505050565b6001600160401b03881681526001600160a01b0387811660208301528616604082015260a0606082018190525f9061350c90830186886134a1565b828103608084015261351f8185876134a1565b9a9950505050505050505050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b5f5f5f5f5f60a086880312156135d9575f5ffd5b85356001600160401b038111156135ee575f5ffd5b8601608081890312156135ff575f5ffd5b613607612a26565b8135613612816129a7565b815260208201356136228161296d565b602082015260408201356001600160401b0381111561363f575f5ffd5b61364b8a828501612aea565b60408301525060608201356001600160401b03811115613669575f5ffd5b6136758a828501612aea565b606083015250955061368b905060208701612981565b935061369960408701612981565b925060608601356001600160401b038111156136b3575f5ffd5b6136bf88828901613192565b92505060808601356001600160401b038111156136da575f5ffd5b6130f288828901613192565b6001600160401b03861681526001600160a01b0385811660208301528416604082015260a0606082018190525f90613720908301856130ff565b828103608084015261373281856130ff565b98975050505050505050565b600181811c9082168061375257607f821691505b602082108103610adb57634e487b7160e01b5f52602260045260245ffd5b5f6060828403128015613781575f5ffd5b50604051606081016001600160401b03811182821017156137a4576137a4612a12565b6040528251815260208301516137b98161296d565b602082015260408301516137cc816129a7565b60408201529392505050565b6001600160a01b0385811682528416602082015260a0604082018190525f90613803908301856130ff565b828103606084015261381581856130ff565b83810360809094019390935250505f8152602001949350505050565b6001600160a01b03841681526060602082018190525f90613854908301856130ff565b82810360408401526126d881856130ff565b5f60208284031215613876575f5ffd5b8151610bd8816129c6565b6001600160a01b0387811682528616602082015260a0604082018190525f906138ad90830186886134a1565b82810360608401526138c08185876134a1565b83810360809094019390935250505f81526020019695505050505050565b60a080825288516001600160401b03169082015260208801516001600160a01b031660c08201526040880151608060e08301525f90613921610120840182612852565b905060608a0151609f198483030161010085015261393f8282612852565b915050613957602084018a6001600160a01b03169052565b6001600160a01b038816604084015282810360608401526139798187896134a1565b9050828103608084015261351f8185876134a1565b602081525f610bd86020830184612852565b5f602082840312156139b0575f5ffd5b5051919050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f8251613a13818460208701612830565b9190910192915050565b5f60208284031215613a2d575f5ffd5b8151610bd88161296d565b6001600160a01b0387811682528681166020830152851660408201526001600160401b038416606082015260c0608082018190525f90613a7a90830185612852565b82810360a0840152613a8c8185612852565b9998505050505050505050565b6001600160a01b03831681526040602082018190525f90610bd590830184612852565b601f82111561112757805f5260205f20601f840160051c81016020851015613ae15750805b601f840160051c820191505b81811015613b00575f8155600101613aed565b5050505050565b81516001600160401b03811115613b2057613b20612a12565b613b3481613b2e845461373e565b84613abc565b6020601f821160018114613b66575f8315613b4f5750848201515b5f19600385901b1c1916600184901b178455613b00565b5f84815260208120601f198516915b82811015613b955787850151825560209485019460019092019101613b75565b5084821015613bb257868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b604081525f613bd36040830185612852565b8281036020840152612f418185612852565b5f60208284031215613bf5575f5ffd5b81516001600160401b03811115613c0a575f5ffd5b61139684828501612efe565b5f60ff821660ff8103613c2b57613c2b61334f565b6001019291505056fe60806040526040516104ca3803806104ca833981016040819052610022916102d2565b61002d82825f610034565b50506103ed565b61003d8361005f565b5f825111806100495750805b1561005a57610058838361009e565b505b505050565b610068816100ca565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606100c383836040518060600160405280602781526020016104a36027913961017d565b9392505050565b6001600160a01b0381163b61013c5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f5f856001600160a01b03168560405161019991906103a0565b5f60405180830381855af49150503d805f81146101d1576040519150601f19603f3d011682016040523d82523d5f602084013e6101d6565b606091505b5090925090506101e8868383876101f2565b9695505050505050565b606083156102605782515f03610259576001600160a01b0385163b6102595760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610133565b508161026a565b61026a8383610272565b949350505050565b8151156102825781518083602001fd5b8060405162461bcd60e51b815260040161013391906103bb565b634e487b7160e01b5f52604160045260245ffd5b5f5b838110156102ca5781810151838201526020016102b2565b50505f910152565b5f5f604083850312156102e3575f5ffd5b82516001600160a01b03811681146102f9575f5ffd5b60208401519092506001600160401b03811115610314575f5ffd5b8301601f81018513610324575f5ffd5b80516001600160401b0381111561033d5761033d61029c565b604051601f8201601f19908116603f011681016001600160401b038111828210171561036b5761036b61029c565b604052818152828201602001871015610382575f5ffd5b6103938260208301602086016102b0565b8093505050509250929050565b5f82516103b18184602087016102b0565b9190910192915050565b602081525f82518060208401526103d98160408501602087016102b0565b601f01601f19169190910160400192915050565b60aa806103f95f395ff3fe608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea26469706673582212205339f45a82df35ebd66aff7c8a93c20457e46388cef179bebf6908c53edcb0ec64736f6c634300081b0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122082fba9b98317ddefcfd00bec71c941906a22e400697b6a9cd0b42128d933987464736f6c634300081b0033", + "code": "0x608060405260043610610131575f3560e01c80635c975abb116100a85780638abf60771161006d5780638abf6077146103375780638da5cb5b1461034b5780639aa8605c14610368578063bc197c8114610397578063f23a6e61146103de578063f2fde38b1461040a575f5ffd5b80635c975abb1461029d57806367090ccf146102bd578063715018a6146102fc5780637f07c947146103105780638456cb5914610323575f5ffd5b80631f59a830116100f95780631f59a8301461020f5780633075db561461022f5780633659cfe6146102435780633f4ba83a146102625780634f1ef2861461027657806352d1902d14610289575f5ffd5b80630178733a1461013557806301ffc9a71461014a57806304f3bcec1461017e57806306fdde03146101c457806319ab453c146101f0575b5f5ffd5b61014861014336600461271b565b610429565b005b348015610155575f5ffd5b50610169610164366004612761565b61059f565b60405190151581526020015b60405180910390f35b348015610189575f5ffd5b507f00000000000000000000000076337500000000000000000000000000000000065b6040516001600160a01b039091168152602001610175565b3480156101cf575f5ffd5b506c195c98cc4c4d4d57dd985d5b1d609a1b5b604051908152602001610175565b3480156101fb575f5ffd5b5061014861020a3660046127a7565b6105be565b61022261021d3660046127c2565b6106d7565b6040516101759190612846565b34801561023a575f5ffd5b50610169610b89565b34801561024e575f5ffd5b5061014861025d3660046127a7565b610ba1565b34801561026d575f5ffd5b50610148610c68565b610148610284366004612a62565b610cf4565b348015610294575f5ffd5b506101e2610da9565b3480156102a8575f5ffd5b50610169609754610100900460ff1660021490565b3480156102c8575f5ffd5b506101ac6102d7366004612aae565b60fc60209081525f92835260408084209091529082529020546001600160a01b031681565b348015610307575f5ffd5b50610148610e5a565b61014861031e366004612b20565b610e6b565b34801561032e575f5ffd5b50610148610f97565b348015610342575f5ffd5b506101ac61101e565b348015610356575f5ffd5b506033546001600160a01b03166101ac565b348015610373575f5ffd5b506103876103823660046127a7565b61102c565b6040516101759493929190612b5e565b3480156103a2575f5ffd5b506103c56103b1366004612bed565b63bc197c8160e01b98975050505050505050565b6040516001600160e01b03199091168152602001610175565b3480156103e9575f5ffd5b506103c56103f8366004612cad565b63f23a6e6160e01b9695505050505050565b348015610415575f5ffd5b506101486104243660046127a7565b611176565b61043d609754610100900460ff1660021490565b1561045b5760405163bae6e2a960e01b815260040160405180910390fd5b600261046960975460ff1690565b60ff160361048a5760405163dfc60d8560e01b815260040160405180910390fd5b61049460026111ec565b61049c611202565b505f6104ac610140840184612d23565b6104ba916004908290612d65565b8101906104c79190612d8c565b90505f5f5f838060200190518101906104e09190612eba565b94509450505092505f610507848860a001602081019061050091906127a7565b85856112f8565b905061053161012088013561052260c08a0160a08b016127a7565b6001600160a01b0316906113e8565b61054160c0880160a089016127a7565b6001600160a01b0316867fe48bef18455e47bca14864ab6e82dffa29df148b051c09de95aec44ecf13598c86602001518487876040516105849493929190613022565b60405180910390a3505050505061059b60016111ec565b5050565b5f6105a9826113f3565b806105b857506105b882611442565b92915050565b5f54610100900460ff16158080156105dc57505f54600160ff909116105b806105f55750303b1580156105f557505f5460ff166001145b61065d5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b5f805460ff19166001179055801561067e575f805461ff0019166101001790555b61068782611476565b61068f6114d4565b801561059b575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15050565b60408051610160810182525f8082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e082018390526101008201839052610120820192909252610140810191909152610745609754610100900460ff1660021490565b156107635760405163bae6e2a960e01b815260040160405180910390fd5b61076c826130e1565b8060e00151518160c0015151146107965760405163196e8a4160e31b815260040160405180910390fd5b60808101516001600160a01b03166107c1576040516303f8a7d360e01b815260040160405180910390fd5b60026107cf60975460ff1690565b60ff16036107f05760405163dfc60d8560e01b815260040160405180910390fd5b6107fa60026111ec565b61080a60808401606085016131b4565b6001600160401b031634101561083357604051630178ce0b60e31b815260040160405180910390fd5b5f61084160e08501856131cf565b905090505f5b8181101561089a5761085c60e08601866131cf565b8281811061086c5761086c613214565b905060200201355f0361089257604051634299323b60e11b815260040160405180910390fd5b600101610847565b506108c79050636cdb3d1360e11b6108b860a08601608087016127a7565b6001600160a01b0316906114fa565b6108e457604051633ee915f560e11b815260040160405180910390fd5b6109096108f760608501604086016127a7565b61090460208601866131b4565b6115ca565b5f5f6109148561162a565b6040805161016081019091525f8082529294509092506020810161093e6080890160608a016131b4565b6001600160401b0316815260200161095c60c0890160a08a01613228565b63ffffffff1681525f6020808301829052604083019190915233606083015260809091019061098d908901896131b4565b6001600160401b031681526020015f6001600160a01b03168860200160208101906109b891906127a7565b6001600160a01b0316036109cc57336109dc565b6109dc6040890160208a016127a7565b6001600160a01b03168152602090810190610a15906109fd908a018a6131b4565b6c195c98cc4c4d4d57dd985d5b1d609a1b5b5f611af0565b6001600160a01b03168152602001610a336080890160608a016131b4565b610a46906001600160401b031634613257565b815260200184905290505f610a646562726964676560d01b82611b92565b6001600160a01b0316631bdb003734846040518363ffffffff1660e01b8152600401610a909190612846565b5f6040518083038185885af1158015610aab573d5f5f3e3d5ffd5b50505050506040513d5f823e601f3d908101601f19168201604052610ad39190810190613275565b96509050610ae760608801604089016127a7565b6001600160a01b03168660a001516001600160a01b0316827fabbf62a1459339f9ac59136d313a5ccd83d2706cc6d4c04d90642520169144dc8960c0015187602001518c6080016020810190610b3d91906127a7565b610b4a60c08f018f6131cf565b8f8060e00190610b5a91906131cf565b604051610b6d97969594939291906133c5565b60405180910390a450505050610b8360016111ec565b50919050565b5f6002610b9860975460ff1690565b60ff1614905090565b6001600160a01b037f0000000000000000000000000763375000000000000000000000000000000004163003610be95760405162461bcd60e51b815260040161065490613421565b7f00000000000000000000000007633750000000000000000000000000000000046001600160a01b0316610c1b611c32565b6001600160a01b031614610c415760405162461bcd60e51b81526004016106549061346d565b610c4a81611c4d565b604080515f80825260208201909252610c6591839190611c55565b50565b610c7c609754610100900460ff1660021490565b610c995760405163bae6e2a960e01b815260040160405180910390fd5b610ca1611dc4565b610cb56097805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610cf2335f611ddd565b565b6001600160a01b037f0000000000000000000000000763375000000000000000000000000000000004163003610d3c5760405162461bcd60e51b815260040161065490613421565b7f00000000000000000000000007633750000000000000000000000000000000046001600160a01b0316610d6e611c32565b6001600160a01b031614610d945760405162461bcd60e51b81526004016106549061346d565b610d9d82611c4d565b61059b82826001611c55565b5f306001600160a01b037f00000000000000000000000007633750000000000000000000000000000000041614610e485760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610654565b505f516020613fe25f395f51905f5290565b610e62611de1565b610cf25f611e3b565b610e7f609754610100900460ff1660021490565b15610e9d5760405163bae6e2a960e01b815260040160405180910390fd5b6002610eab60975460ff1690565b60ff1603610ecc5760405163dfc60d8560e01b815260040160405180910390fd5b610ed660026111ec565b5f80808080610ee7868801886134b9565b945094509450945094505f610efa611e8c565b9050610f0584611fad565b5f610f12878686866112f8565b9050610f276001600160a01b038616346113e8565b846001600160a01b0316866001600160a01b0316835f01517f895f73e418d1bbbad2a311d085fad00e5d98a960e9f2afa4b942071d39bec43a85604001518b60200151868a8a604051610f7e9594939291906135da565b60405180910390a45050505050505061059b60016111ec565b610fab609754610100900460ff1660021490565b15610fc95760405163bae6e2a960e01b815260040160405180910390fd5b610fd1611dc4565b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a1610cf2336001611ddd565b5f611027611c32565b905090565b60fb6020525f9081526040902080546001820180546001600160401b03831693600160401b9093046001600160a01b031692919061106990613632565b80601f016020809104026020016040519081016040528092919081815260200182805461109590613632565b80156110e05780601f106110b7576101008083540402835291602001916110e0565b820191905f5260205f20905b8154815290600101906020018083116110c357829003601f168201915b5050505050908060020180546110f590613632565b80601f016020809104026020016040519081016040528092919081815260200182805461112190613632565b801561116c5780601f106111435761010080835404028352916020019161116c565b820191905f5260205f20905b81548152906001019060200180831161114f57829003601f168201915b5050505050905084565b61117e611de1565b6001600160a01b0381166111e35760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610654565b610c6581611e3b565b6097805460ff191660ff92909216919091179055565b604080516060810182525f80825260208201819052918101919091526562726964676560d01b611233816001611b92565b6001600160a01b0316336001600160a01b031614611264576040516395383ea160e01b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa1580156112a0573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112c49190613664565b60208101519092506001600160a01b031633146112f457604051632583296b60e01b815260040160405180910390fd5b5090565b5f46855f01516001600160401b03160361137657506020840151604051631759616b60e11b81526001600160a01b03821690632eb2c2d6906113449030908890889088906004016136cc565b5f604051808303815f87803b15801561135b575f5ffd5b505af115801561136d573d5f5f3e3d5ffd5b505050506113e0565b61137f85611fe9565b60405163d81d0a1560e01b81529091506001600160a01b0382169063d81d0a15906113b290879087908790600401613725565b5f604051808303815f87803b1580156113c9575f5ffd5b505af11580156113db573d5f5f3e3d5ffd5b505050505b949350505050565b61059b82825a61202f565b5f6001600160e01b0319821662bc399d60e11b148061142257506001600160e01b03198216637f07c94760e01b145b806105b857506001600160e01b031982166301ffc9a760e01b1492915050565b5f6001600160e01b03198216630271189760e51b14806105b857506301ffc9a760e01b6001600160e01b03198316146105b8565b5f54610100900460ff1661149c5760405162461bcd60e51b81526004016106549061375a565b6114a46114d4565b6114c26001600160a01b038216156114bc5781611e3b565b33611e3b565b506097805461ff001916610100179055565b5f54610100900460ff16610cf25760405162461bcd60e51b81526004016106549061375a565b6040516001600160e01b0319821660248201525f90819081906001600160a01b0386169060440160408051601f198184030181529181526020820180516001600160e01b03166301ffc9a760e01b1790525161155691906137a5565b5f60405180830381855afa9150503d805f811461158e576040519150601f19603f3d011682016040523d82523d5f602084013e611593565b606091505b50915091508180156115a6575080516020145b156115c257808060200190518101906115bf91906137c0565b92505b505092915050565b6001600160a01b038216158061160c57506115f7816c195c98cc4c4d4d57dd985d5b1d609a1b6001611af0565b6001600160a01b0316826001600160a01b0316145b1561059b57604051635b50f3f960e01b815260040160405180910390fd5b604080516080810182525f808252602082015260609181018290528082018290525f60fb8161165f60a08701608088016127a7565b6001600160a01b03908116825260208201929092526040015f208054909250600160401b90041615611959576040805160808101825282546001600160401b0381168252600160401b90046001600160a01b031660208201526001830180549192849290840191906116d090613632565b80601f01602080910402602001604051908101604052809291908181526020018280546116fc90613632565b80156117475780601f1061171e57610100808354040283529160200191611747565b820191905f5260205f20905b81548152906001019060200180831161172a57829003601f168201915b5050505050815260200160028201805461176090613632565b80601f016020809104026020016040519081016040528092919081815260200182805461178c90613632565b80156117d75780601f106117ae576101008083540402835291602001916117d7565b820191905f5260205f20905b8154815290600101906020018083116117ba57829003601f168201915b50505050508152505091508360800160208101906117f591906127a7565b6001600160a01b0316632eb2c2d6333061181260c08901896131cf565b61181f60e08b018b6131cf565b6040518763ffffffff1660e01b8152600401611840969594939291906137df565b5f604051808303815f87803b158015611857575f5ffd5b505af1158015611869573d5f5f3e3d5ffd5b505f925061187d91505060c08601866131cf565b905090505f5b818110156119525761189b60a08701608088016127a7565b6001600160a01b031663b390c0ab6118b660c08901896131cf565b848181106118c6576118c6613214565b90506020020135888060e001906118dd91906131cf565b858181106118ed576118ed613214565b905060200201356040518363ffffffff1660e01b815260040161191a929190918252602082015260400190565b5f604051808303815f87803b158015611931575f5ffd5b505af1158015611943573d5f5f3e3d5ffd5b50505050806001019050611883565b5050611a56565b6040518060800160405280466001600160401b0316815260200185608001602081019061198691906127a7565b6001600160a01b031681526020016119ac6119a760a08801608089016127a7565b612072565b81526020016119c96119c460a08801608089016127a7565b61211c565b905291506119dd60a08501608086016127a7565b6001600160a01b0316632eb2c2d633306119fa60c08901896131cf565b611a0760e08b018b6131cf565b6040518763ffffffff1660e01b8152600401611a28969594939291906137df565b5f604051808303815f87803b158015611a3f575f5ffd5b505af1158015611a51573d5f5f3e3d5ffd5b505050505b5030637f07c9478233611a6f60608801604089016127a7565b611a7c60c08901896131cf565b611a8960e08b018b6131cf565b604051602001611a9f979695949392919061383c565b60408051601f1981840301815290829052611abc916024016138ec565b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050509150915091565b5f7f0000000000000000000000007633750000000000000000000000000000000006604051633632b1fb60e11b81526001600160401b03861660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa158015611b6e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113e091906138fe565b5f7f0000000000000000000000007633750000000000000000000000000000000006604051633632b1fb60e11b81524660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa158015611c07573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c2b91906138fe565b9392505050565b5f516020613fe25f395f51905f52546001600160a01b031690565b610c65611de1565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff1615611c8d57611c8883612162565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611ce7575060408051601f3d908101601f19168201909252611ce491810190613919565b60015b611d4a5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610654565b5f516020613fe25f395f51905f528114611db85760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610654565b50611c888383836121fd565b60405163a87dd7cf60e01b815260040160405180910390fd5b61059b5b6033546001600160a01b03163314610cf25760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610654565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b604080516060810182525f80825260208201819052918101919091526562726964676560d01b611ebd816001611b92565b6001600160a01b0316336001600160a01b031614611eee576040516395383ea160e01b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa158015611f2a573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611f4e9190613664565b91505f611f708360400151610a0f6c195c98cc4c4d4d57dd985d5b1d609a1b90565b9050806001600160a01b031683602001516001600160a01b031614611fa857604051632583296b60e01b815260040160405180910390fd5b505090565b6001600160a01b0381161580611fcb57506001600160a01b03811630145b15610c6557604051635b50f3f960e01b815260040160405180910390fd5b80516001600160401b03165f90815260fc60209081526040808320828501516001600160a01b039081168552925290912054168061202a576105b882612227565b919050565b815f0361203b57505050565b61205583838360405180602001604052805f8152506123f4565b611c8857604051634c67134d60e11b815260040160405180910390fd5b60408051600481526024810182526020810180516001600160e01b03166395d89b4160e01b17905290516060915f9182916001600160a01b038616916120b891906137a5565b5f60405180830381855afa9150503d805f81146120f0576040519150601f19603f3d011682016040523d82523d5f602084013e6120f5565b606091505b5091509150816121135760405180602001604052805f8152506113e0565b6113e081612431565b60408051600481526024810182526020810180516001600160e01b03166306fdde0360e01b17905290516060915f9182916001600160a01b038616916120b891906137a5565b6001600160a01b0381163b6121cf5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610654565b5f516020613fe25f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b6122068361259e565b5f825111806122125750805b15611c885761222183836125dd565b50505050565b5f5f61223b6033546001600160a01b031690565b602084015184516040808701516060880151915161225f9594939290602401613930565b60408051601f198184030181529190526020810180516001600160e01b031663689ccd8d60e11b17905290506122a76e627269646765645f6572633131353560881b5f611b92565b816040516122b49061270e565b6122bf92919061397c565b604051809103905ff0801580156122d8573d5f5f3e3d5ffd5b506001600160a01b038082165f90815260fb60209081526040918290208751815492890151909416600160401b026001600160e01b03199092166001600160401b03909416939093171782558501519193508491600182019061233b90826139ea565b506060820151600282019061235090826139ea565b505083516001600160401b039081165f90815260fc6020908152604080832082890180516001600160a01b039081168652919093529281902080546001600160a01b03191688851690811790915591518851828a015160608b01519351949750919094169493909316927f44977f2d30fe1e3aee2c1476f2f95aaacaf34e44b9359c403da01fcc93fd751b926123e69290613aa4565b60405180910390a450919050565b5f6001600160a01b03851661241c57604051634c67134d60e11b815260040160405180910390fd5b5f5f835160208501878988f195945050505050565b6060604082511061245057818060200190518101906105b89190613ac8565b815160200361258b575f5b60208160ff161080156124905750828160ff168151811061247e5761247e613214565b01602001516001600160f81b03191615155b156124a7578061249f81613af9565b91505061245b565b5f8160ff166001600160401b038111156124c3576124c3612936565b6040519080825280601f01601f1916602001820160405280156124ed576020820181803683370190505b5090505f91505b60208260ff161080156125295750838260ff168151811061251757612517613214565b01602001516001600160f81b03191615155b15611c2b57838260ff168151811061254357612543613214565b602001015160f81c60f81b818360ff168151811061256357612563613214565b60200101906001600160f81b03191690815f1a9053508161258381613af9565b9250506124f4565b505060408051602081019091525f815290565b6125a781612162565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060611c2b83836040518060600160405280602781526020016140026027913960605f5f856001600160a01b03168560405161261991906137a5565b5f60405180830381855af49150503d805f8114612651576040519150601f19603f3d011682016040523d82523d5f602084013e612656565b606091505b509150915061266786838387612671565b9695505050505050565b606083156126df5782515f036126d8576001600160a01b0385163b6126d85760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610654565b50816113e0565b6113e083838151156126f45781518083602001fd5b8060405162461bcd60e51b815260040161065491906138ec565b6104ca80613b1883390190565b5f5f6040838503121561272c575f5ffd5b82356001600160401b03811115612741575f5ffd5b83016101608186031215612753575f5ffd5b946020939093013593505050565b5f60208284031215612771575f5ffd5b81356001600160e01b031981168114611c2b575f5ffd5b6001600160a01b0381168114610c65575f5ffd5b803561202a81612788565b5f602082840312156127b7575f5ffd5b8135611c2b81612788565b5f602082840312156127d2575f5ffd5b81356001600160401b038111156127e7575f5ffd5b82016101008185031215611c2b575f5ffd5b5f5b838110156128135781810151838201526020016127fb565b50505f910152565b5f81518084526128328160208601602086016127f9565b601f01601f19169290920160200192915050565b602081526128606020820183516001600160401b03169052565b5f602083015161287b60408401826001600160401b03169052565b50604083015163ffffffff811660608401525060608301516001600160a01b03811660808401525060808301516001600160401b03811660a08401525060a08301516001600160a01b03811660c08401525060c08301516001600160401b03811660e08401525060e08301516001600160a01b038116610100840152506101008301516001600160a01b03811661012084015250610120830151610140830152610140830151610160808401526113e061018084018261281b565b634e487b7160e01b5f52604160045260245ffd5b604051608081016001600160401b038111828210171561296c5761296c612936565b60405290565b60405161010081016001600160401b038111828210171561296c5761296c612936565b60405161016081016001600160401b038111828210171561296c5761296c612936565b604051601f8201601f191681016001600160401b03811182821017156129e0576129e0612936565b604052919050565b5f6001600160401b03821115612a0057612a00612936565b50601f01601f191660200190565b5f82601f830112612a1d575f5ffd5b8135602083015f612a35612a30846129e8565b6129b8565b9050828152858383011115612a48575f5ffd5b828260208301375f92810160200192909252509392505050565b5f5f60408385031215612a73575f5ffd5b8235612a7e81612788565b915060208301356001600160401b03811115612a98575f5ffd5b612aa485828601612a0e565b9150509250929050565b5f5f60408385031215612abf575f5ffd5b823591506020830135612ad181612788565b809150509250929050565b5f5f83601f840112612aec575f5ffd5b5081356001600160401b03811115612b02575f5ffd5b602083019150836020828501011115612b19575f5ffd5b9250929050565b5f5f60208385031215612b31575f5ffd5b82356001600160401b03811115612b46575f5ffd5b612b5285828601612adc565b90969095509350505050565b6001600160401b03851681526001600160a01b03841660208201526080604082018190525f90612b909083018561281b565b8281036060840152612ba2818561281b565b979650505050505050565b5f5f83601f840112612bbd575f5ffd5b5081356001600160401b03811115612bd3575f5ffd5b6020830191508360208260051b8501011115612b19575f5ffd5b5f5f5f5f5f5f5f5f60a0898b031215612c04575f5ffd5b8835612c0f81612788565b97506020890135612c1f81612788565b965060408901356001600160401b03811115612c39575f5ffd5b612c458b828c01612bad565b90975095505060608901356001600160401b03811115612c63575f5ffd5b612c6f8b828c01612bad565b90955093505060808901356001600160401b03811115612c8d575f5ffd5b612c998b828c01612adc565b999c989b5096995094979396929594505050565b5f5f5f5f5f5f60a08789031215612cc2575f5ffd5b8635612ccd81612788565b95506020870135612cdd81612788565b9450604087013593506060870135925060808701356001600160401b03811115612d05575f5ffd5b612d1189828a01612adc565b979a9699509497509295939492505050565b5f5f8335601e19843603018112612d38575f5ffd5b8301803591506001600160401b03821115612d51575f5ffd5b602001915036819003821315612b19575f5ffd5b5f5f85851115612d73575f5ffd5b83861115612d7f575f5ffd5b5050820193919092039150565b5f60208284031215612d9c575f5ffd5b81356001600160401b03811115612db1575f5ffd5b6113e084828501612a0e565b6001600160401b0381168114610c65575f5ffd5b805161202a81612dbd565b805161202a81612788565b5f82601f830112612df6575f5ffd5b8151602083015f612e09612a30846129e8565b9050828152858383011115612e1c575f5ffd5b612e2a8360208301846127f9565b95945050505050565b5f6001600160401b03821115612e4b57612e4b612936565b5060051b60200190565b5f82601f830112612e64575f5ffd5b8151612e72612a3082612e33565b8082825260208201915060208360051b860101925085831115612e93575f5ffd5b602085015b83811015612eb0578051835260209283019201612e98565b5095945050505050565b5f5f5f5f5f60a08688031215612ece575f5ffd5b85516001600160401b03811115612ee3575f5ffd5b860160808189031215612ef4575f5ffd5b612efc61294a565b8151612f0781612dbd565b81526020820151612f1781612788565b602082015260408201516001600160401b03811115612f34575f5ffd5b612f408a828501612de7565b60408301525060608201516001600160401b03811115612f5e575f5ffd5b612f6a8a828501612de7565b6060830152509550612f80905060208701612ddc565b9350612f8e60408701612ddc565b925060608601516001600160401b03811115612fa8575f5ffd5b612fb488828901612e55565b92505060808601516001600160401b03811115612fcf575f5ffd5b612fdb88828901612e55565b9150509295509295909350565b5f8151808452602084019350602083015f5b82811015613018578151865260209586019590910190600101612ffa565b5093949350505050565b6001600160a01b038581168252841660208201526080604082018190525f9061304d90830185612fe8565b8281036060840152612ba28185612fe8565b803561202a81612dbd565b63ffffffff81168114610c65575f5ffd5b803561202a8161306a565b5f82601f830112613095575f5ffd5b81356130a3612a3082612e33565b8082825260208201915060208360051b8601019250858311156130c4575f5ffd5b602085015b83811015612eb05780358352602092830192016130c9565b5f61010082360312156130f2575f5ffd5b6130fa612972565b6131038361305f565b81526131116020840161279c565b60208201526131226040840161279c565b60408201526131336060840161305f565b60608201526131446080840161279c565b608082015261315560a0840161307b565b60a082015260c08301356001600160401b03811115613172575f5ffd5b61317e36828601613086565b60c08301525060e08301356001600160401b0381111561319c575f5ffd5b6131a836828601613086565b60e08301525092915050565b5f602082840312156131c4575f5ffd5b8135611c2b81612dbd565b5f5f8335601e198436030181126131e4575f5ffd5b8301803591506001600160401b038211156131fd575f5ffd5b6020019150600581901b3603821315612b19575f5ffd5b634e487b7160e01b5f52603260045260245ffd5b5f60208284031215613238575f5ffd5b8135611c2b8161306a565b634e487b7160e01b5f52601160045260245ffd5b818103818111156105b8576105b8613243565b805161202a8161306a565b5f5f60408385031215613286575f5ffd5b825160208401519092506001600160401b038111156132a3575f5ffd5b830161016081860312156132b5575f5ffd5b6132bd612995565b6132c682612dd1565b81526132d460208301612dd1565b60208201526132e56040830161326a565b60408201526132f660608301612ddc565b606082015261330760808301612dd1565b608082015261331860a08301612ddc565b60a082015261332960c08301612dd1565b60c082015261333a60e08301612ddc565b60e082015261334c6101008301612ddc565b61010082015261012082810151908201526101408201516001600160401b03811115613376575f5ffd5b61338287828501612de7565b6101408301525080925050509250929050565b8183525f6001600160fb1b038311156133ac575f5ffd5b8260051b80836020870137939093016020019392505050565b6001600160401b03881681526001600160a01b0387811660208301528616604082015260a0606082018190525f906134009083018688613395565b8281036080840152613413818587613395565b9a9950505050505050505050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b5f5f5f5f5f60a086880312156134cd575f5ffd5b85356001600160401b038111156134e2575f5ffd5b8601608081890312156134f3575f5ffd5b6134fb61294a565b813561350681612dbd565b8152602082013561351681612788565b602082015260408201356001600160401b03811115613533575f5ffd5b61353f8a828501612a0e565b60408301525060608201356001600160401b0381111561355d575f5ffd5b6135698a828501612a0e565b606083015250955061357f90506020870161279c565b935061358d6040870161279c565b925060608601356001600160401b038111156135a7575f5ffd5b6135b388828901613086565b92505060808601356001600160401b038111156135ce575f5ffd5b612fdb88828901613086565b6001600160401b03861681526001600160a01b0385811660208301528416604082015260a0606082018190525f9061361490830185612fe8565b82810360808401526136268185612fe8565b98975050505050505050565b600181811c9082168061364657607f821691505b602082108103610b8357634e487b7160e01b5f52602260045260245ffd5b5f6060828403128015613675575f5ffd5b50604051606081016001600160401b038111828210171561369857613698612936565b6040528251815260208301516136ad81612788565b602082015260408301516136c081612dbd565b60408201529392505050565b6001600160a01b0385811682528416602082015260a0604082018190525f906136f790830185612fe8565b82810360608401526137098185612fe8565b83810360809094019390935250505f8152602001949350505050565b6001600160a01b03841681526060602082018190525f9061374890830185612fe8565b82810360408401526126678185612fe8565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f82516137b68184602087016127f9565b9190910192915050565b5f602082840312156137d0575f5ffd5b81518015158114611c2b575f5ffd5b6001600160a01b0387811682528616602082015260a0604082018190525f9061380b9083018688613395565b828103606084015261381e818587613395565b83810360809094019390935250505f81526020019695505050505050565b60a080825288516001600160401b03169082015260208801516001600160a01b031660c08201526040880151608060e08301525f9061387f61012084018261281b565b905060608a0151609f198483030161010085015261389d828261281b565b9150506138b5602084018a6001600160a01b03169052565b6001600160a01b038816604084015282810360608401526138d7818789613395565b90508281036080840152613413818587613395565b602081525f611c2b602083018461281b565b5f6020828403121561390e575f5ffd5b8151611c2b81612788565b5f60208284031215613929575f5ffd5b5051919050565b6001600160a01b038681168252851660208201526001600160401b038416604082015260a0606082018190525f9061396a9083018561281b565b8281036080840152613626818561281b565b6001600160a01b03831681526040602082018190525f906113e09083018461281b565b601f821115611c8857805f5260205f20601f840160051c810160208510156139c45750805b601f840160051c820191505b818110156139e3575f81556001016139d0565b5050505050565b81516001600160401b03811115613a0357613a03612936565b613a1781613a118454613632565b8461399f565b6020601f821160018114613a49575f8315613a325750848201515b5f19600385901b1c1916600184901b1784556139e3565b5f84815260208120601f198516915b82811015613a785787850151825560209485019460019092019101613a58565b5084821015613a9557868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b604081525f613ab6604083018561281b565b8281036020840152612e2a818561281b565b5f60208284031215613ad8575f5ffd5b81516001600160401b03811115613aed575f5ffd5b6113e084828501612de7565b5f60ff821660ff8103613b0e57613b0e613243565b6001019291505056fe60806040526040516104ca3803806104ca833981016040819052610022916102d2565b61002d82825f610034565b50506103ed565b61003d8361005f565b5f825111806100495750805b1561005a57610058838361009e565b505b505050565b610068816100ca565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606100c383836040518060600160405280602781526020016104a36027913961017d565b9392505050565b6001600160a01b0381163b61013c5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f5f856001600160a01b03168560405161019991906103a0565b5f60405180830381855af49150503d805f81146101d1576040519150601f19603f3d011682016040523d82523d5f602084013e6101d6565b606091505b5090925090506101e8868383876101f2565b9695505050505050565b606083156102605782515f03610259576001600160a01b0385163b6102595760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610133565b508161026a565b61026a8383610272565b949350505050565b8151156102825781518083602001fd5b8060405162461bcd60e51b815260040161013391906103bb565b634e487b7160e01b5f52604160045260245ffd5b5f5b838110156102ca5781810151838201526020016102b2565b50505f910152565b5f5f604083850312156102e3575f5ffd5b82516001600160a01b03811681146102f9575f5ffd5b60208401519092506001600160401b03811115610314575f5ffd5b8301601f81018513610324575f5ffd5b80516001600160401b0381111561033d5761033d61029c565b604051601f8201601f19908116603f011681016001600160401b038111828210171561036b5761036b61029c565b604052818152828201602001871015610382575f5ffd5b6103938260208301602086016102b0565b8093505050509250929050565b5f82516103b18184602087016102b0565b9190910192915050565b602081525f82518060208401526103d98160408501602087016102b0565b601f01601f19169190910160400192915050565b60aa806103f95f395ff3fe608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212207a5489136f6b9c4646d2fd495a861920ec1ca8ef814ec593dd3aacbe32f6d94f64736f6c634300081b0033", "balance": "0x0" }, "0x7633750000000000000000000000000000000004": { @@ -165,28 +164,27 @@ "0x0000000000000000000000000000000000000000000000000000000000000097": "0x0000000000000000000000000000000000000000000000000000000000000101", "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000003bc256069ff9af461f3e04494a3ece3f62f183fc", - "0x0000000000000000000000000000000000000000000000000000000000000065": "0x0000000000000000000000007633750000000000000000000000000000000006", "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0763375000000000000000000000000000000004" }, - "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea26469706673582212205339f45a82df35ebd66aff7c8a93c20457e46388cef179bebf6908c53edcb0ec64736f6c634300081b0033", + "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033", "balance": "0x0" }, "0x0763375000000000000000000000000000010096": { "contractName": "BridgedERC20", "storage": {}, - "code": "0x60806040526004361061021d575f3560e01c80635c975abb1161011e578063a457c2d7116100a8578063bb86ef931161006d578063bb86ef9314610633578063dd62ed3e14610652578063e07baba614610671578063f12506c1146106ae578063f2fde38b146106c2575f5ffd5b8063a457c2d714610597578063a77f1516146105b6578063a86f9d9e146105d6578063a9059cbb146105f5578063b8f2e0c514610614575f5ffd5b80637e474634116100ee5780637e4746341461051f5780638456cb591461053e5780638abf6077146105525780638da5cb5b1461056657806395d89b4114610583575f5ffd5b80635c975abb1461049857806370a08231146104b8578063715018a6146104ec5780637cf8ed0d14610500575f5ffd5b80633659cfe6116101aa57806340c10f191161016f57806340c10f191461041e57806342966c681461043d57806349d126051461045c5780634f1ef2861461047157806352d1902d14610484575f5ffd5b80633659cfe61461037457806339509351146103955780633ab76e9f146103b45780633eb6b8cf146103eb5780633f4ba83a1461040a575f5ffd5b806318160ddd116101f057806318160ddd146102b557806323b872dd146102d357806326afaadd146102f25780633075db5614610334578063313ce56714610348575f5ffd5b806301ffc9a71461022157806306fdde0314610255578063095ea7b3146102765780630ae7454814610295575b5f5ffd5b34801561022c575f5ffd5b5061024061023b3660046120ca565b6106e1565b60405190151581526020015b60405180910390f35b348015610260575f5ffd5b50610269610783565b60405161024c9190612113565b348015610281575f5ffd5b50610240610290366004612159565b610813565b3480156102a0575f5ffd5b5060fd5461024090600160a01b900460ff1681565b3480156102c0575f5ffd5b5060cb545b60405190815260200161024c565b3480156102de575f5ffd5b506102406102ed366004612183565b61082a565b3480156102fd575f5ffd5b5061031560fb5460fc546001600160a01b0390911691565b604080516001600160a01b03909316835260208301919091520161024c565b34801561033f575f5ffd5b5061024061084f565b348015610353575f5ffd5b5060fb54600160a01b900460ff165b60405160ff909116815260200161024c565b34801561037f575f5ffd5b5061039361038e3660046121c1565b610867565b005b3480156103a0575f5ffd5b506102406103af366004612159565b610937565b3480156103bf575f5ffd5b506065546103d3906001600160a01b031681565b6040516001600160a01b03909116815260200161024c565b3480156103f6575f5ffd5b506103d36104053660046121f0565b610958565b348015610415575f5ffd5b5061039361096c565b348015610429575f5ffd5b50610393610438366004612159565b610980565b348015610448575f5ffd5b50610393610457366004612236565b610a99565b348015610467575f5ffd5b506102c560fc5481565b61039361047f366004612261565b610bd5565b34801561048f575f5ffd5b506102c5610c8a565b3480156104a3575f5ffd5b50610240609754610100900460ff1660021490565b3480156104c3575f5ffd5b506102c56104d23660046121c1565b6001600160a01b03165f90815260c9602052604090205490565b3480156104f7575f5ffd5b50610393610d3b565b34801561050b575f5ffd5b5060fb546103d3906001600160a01b031681565b34801561052a575f5ffd5b5060fd546103d3906001600160a01b031681565b348015610549575f5ffd5b50610393610d4c565b34801561055d575f5ffd5b506103d3610d67565b348015610571575f5ffd5b506033546001600160a01b03166103d3565b34801561058e575f5ffd5b50610269610d75565b3480156105a2575f5ffd5b506102406105b1366004612159565b610d84565b3480156105c1575f5ffd5b5060fb5461036290600160a01b900460ff1681565b3480156105e1575f5ffd5b506103d36105f0366004612324565b610e09565b348015610600575f5ffd5b5061024061060f366004612159565b610e15565b34801561061f575f5ffd5b5061039361062e36600461234e565b610e22565b34801561063e575f5ffd5b5061039361064d3660046123bc565b610f9c565b34801561065d575f5ffd5b506102c561066c366004612482565b61115a565b34801561067c575f5ffd5b50609754610696906201000090046001600160401b031681565b6040516001600160401b03909116815260200161024c565b3480156106b9575f5ffd5b50610240611184565b3480156106cd575f5ffd5b506103936106dc3660046121c1565b6111ad565b5f6001600160e01b0319821663093e326b60e21b148061071157506001600160e01b0319821663bb86ef9360e01b145b8061072c57506001600160e01b0319821663b8f2e0c560e01b145b8061074757506001600160e01b031982166336372b0760e01b145b8061076257506001600160e01b0319821663a219a02560e01b145b8061077d57506001600160e01b031982166301ffc9a760e01b145b92915050565b606060cc8054610792906124b9565b80601f01602080910402602001604051908101604052809291908181526020018280546107be906124b9565b80156108095780601f106107e057610100808354040283529160200191610809565b820191905f5260205f20905b8154815290600101906020018083116107ec57829003601f168201915b5050505050905090565b5f33610820818585611223565b5060019392505050565b5f33610837858285611346565b6108428585856113be565b60019150505b9392505050565b5f600261085e60975460ff1690565b60ff1614905090565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000100961630036108b85760405162461bcd60e51b81526004016108af906124f1565b60405180910390fd5b7f00000000000000000000000007633750000000000000000000000000000100966001600160a01b03166108ea611572565b6001600160a01b0316146109105760405162461bcd60e51b81526004016108af9061253d565b6109198161158d565b604080515f8082526020820190925261093491839190611595565b50565b5f33610820818585610949838361115a565b6109539190612589565b611223565b5f6109648484846116ff565b949350505050565b610974611751565b61097e335f6117e1565b565b610994609754610100900460ff1660021490565b156109b25760405163bae6e2a960e01b815260040160405180910390fd5b60026109c060975460ff1690565b60ff16036109e15760405163dfc60d8560e01b815260040160405180910390fd5b6109eb60026117e9565b6109f3611184565b15610a115760405163270bf77560e01b815260040160405180910390fd5b60fd546001600160a01b031633819003610a7757826001600160a01b0316816001600160a01b03167fe502aa3e015149f4b76a0b2b5394e3100903c4af27c3ddc98385395d3f55252684604051610a6a91815260200190565b60405180910390a3610a80565b610a80336117ff565b610a8a838361187b565b50610a9560016117e9565b5050565b610aad609754610100900460ff1660021490565b15610acb5760405163bae6e2a960e01b815260040160405180910390fd5b6002610ad960975460ff1690565b60ff1603610afa5760405163dfc60d8560e01b815260040160405180910390fd5b610b0460026117e9565b610b0c611184565b15610bb85760fd546040518281526001600160a01b0390911690339082907f638edf84937fb2534b47cac985ea84d6ea4f4076315b56ea1c784d26b87e2bcb9060200160405180910390a36040516340c10f1960e01b8152336004820152602481018390526001600160a01b038216906340c10f19906044015f604051808303815f87803b158015610b9c575f5ffd5b505af1158015610bae573d5f5f3e3d5ffd5b5050505050610bc1565b610bc1336117ff565b610bcb3382611945565b61093460016117e9565b6001600160a01b037f0000000000000000000000000763375000000000000000000000000000010096163003610c1d5760405162461bcd60e51b81526004016108af906124f1565b7f00000000000000000000000007633750000000000000000000000000000100966001600160a01b0316610c4f611572565b6001600160a01b031614610c755760405162461bcd60e51b81526004016108af9061253d565b610c7e8261158d565b610a9582826001611595565b5f306001600160a01b037f00000000000000000000000007633750000000000000000000000000000100961614610d295760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c000000000000000060648201526084016108af565b505f5160206127465f395f51905f5290565b610d43611a82565b61097e5f611adc565b610d54611b2d565b610d5c611b46565b61097e3360016117e1565b5f610d70611572565b905090565b606060cd8054610792906124b9565b5f3381610d91828661115a565b905083811015610df15760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084016108af565b610dfe8286868403611223565b506001949350505050565b5f6108484684846116ff565b5f336108208185856113be565b610e36609754610100900460ff1660021490565b15610e545760405163bae6e2a960e01b815260040160405180910390fd5b6a195c98cc8c17dd985d5b1d60aa1b610e6e816001610e09565b6001600160a01b0316336001600160a01b031614610e9f57604051630d85cccf60e11b815260040160405180910390fd5b6002610ead60975460ff1690565b60ff1603610ece5760405163dfc60d8560e01b815260040160405180910390fd5b610ed860026117e9565b60fd546001600160a01b038481169116148015610f07575060fd60149054906101000a900460ff161515821515145b15610f255760405163c118d2f360e01b815260040160405180910390fd5b60fd80546001600160a01b0385166001600160a81b03199091168117600160a01b851515908102919091179092556040805191825260208201929092527fa6b6f959792843a48d9d03d13595f2de7c86ae0ce12ef0fa759dd911b205e565910160405180910390a1610f9760016117e9565b505050565b5f54610100900460ff1615808015610fba57505f54600160ff909116105b80610fd35750303b158015610fd357505f5460ff166001145b6110365760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016108af565b5f805460ff191660011790558015611057575f805461ff0019166101001790555b6110618888611bb7565b61106b8a8a611bf3565b6110dc83838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525050604080516020601f8b0181900481028201810190925289815292508991508890819084018382808284375f92019190915250611c2d92505050565b60fb805460fc89905560ff8816600160a01b026001600160a81b03199091166001600160a01b038b1617179055801561114e575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050505050505050565b6001600160a01b039182165f90815260ca6020908152604080832093909416825291909152205490565b60fd545f906001600160a01b031615801590610d7057505060fd54600160a01b900460ff161590565b6111b5611a82565b6001600160a01b03811661121a5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016108af565b61093481611adc565b6001600160a01b0383166112855760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016108af565b6001600160a01b0382166112e65760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016108af565b6001600160a01b038381165f81815260ca602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b5f611351848461115a565b90505f1981146113b857818110156113ab5760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016108af565b6113b88484848403611223565b50505050565b6001600160a01b0383166114225760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016108af565b6001600160a01b0382166114845760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016108af565b61148f838383611c5d565b6001600160a01b0383165f90815260c96020526040902054818110156115065760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b60648201526084016108af565b6001600160a01b038085165f81815260c9602052604080822086860390559286168082529083902080548601905591517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906115659086815260200190565b60405180910390a36113b8565b5f5160206127465f395f51905f52546001600160a01b031690565b610934611a82565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156115c857610f9783611c98565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611622575060408051601f3d908101601f1916820190925261161f918101906125a8565b60015b6116855760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b60648201526084016108af565b5f5160206127465f395f51905f5281146116f35760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b60648201526084016108af565b50610f97838383611d33565b5f61170a8484611d57565b90508115801561172157506001600160a01b038116155b1561084857604051632b0d65db60e01b81526001600160401b0385166004820152602481018490526044016108af565b611765609754610100900460ff1660021490565b6117825760405163bae6e2a960e01b815260040160405180910390fd5b6097805461010069ffffffffffffffffff001990911662010000426001600160401b031602171790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa906020015b60405180910390a1565b610a95611a82565b6097805460ff191660ff92909216919091179055565b6a195c98cc8c17dd985d5b1d60aa1b6118206033546001600160a01b031690565b6001600160a01b0316336001600160a01b03161415801561185d5750611847816001610e09565b6001600160a01b0316336001600160a01b031614155b15610a9557604051630d85cccf60e11b815260040160405180910390fd5b6001600160a01b0382166118d15760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016108af565b6118dc5f8383611c5d565b8060cb5f8282546118ed9190612589565b90915550506001600160a01b0382165f81815260c960209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b6001600160a01b0382166119a55760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b60648201526084016108af565b6119b0825f83611c5d565b6001600160a01b0382165f90815260c9602052604090205481811015611a235760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b60648201526084016108af565b6001600160a01b0383165f81815260c960209081526040808320868603905560cb80548790039055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3505050565b6033546001600160a01b0316331461097e5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016108af565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b60405163a87dd7cf60e01b815260040160405180910390fd5b611b5a609754610100900460ff1660021490565b15611b785760405163bae6e2a960e01b815260040160405180910390fd5b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258906020016117d7565b6001600160a01b0382161580611bcb575080155b80611bd557504681145b15610a955760405163c118d2f360e01b815260040160405180910390fd5b806001600160a01b038116611c1b5760405163538ba4f960e01b815260040160405180910390fd5b611c2483611df9565b610f9782611e57565b5f54610100900460ff16611c535760405162461bcd60e51b81526004016108af906125bf565b610a958282611ec7565b611c71609754610100900460ff1660021490565b15611c8f5760405163bae6e2a960e01b815260040160405180910390fd5b610f9782611f06565b6001600160a01b0381163b611d055760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016108af565b5f5160206127465f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b611d3c83611f2f565b5f82511180611d485750805b15610f97576113b88383611f6e565b6065545f906001600160a01b031680611d8357604051638ed88b2560e01b815260040160405180910390fd5b604051630a3dc4f360e21b81526001600160401b0385166004820152602481018490526001600160a01b038216906328f713cc90604401602060405180830381865afa158015611dd5573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610964919061260a565b5f54610100900460ff16611e1f5760405162461bcd60e51b81526004016108af906125bf565b611e27611f93565b611e456001600160a01b03821615611e3f5781611adc565b33611adc565b506097805461ff001916610100179055565b5f54610100900460ff16611e7d5760405162461bcd60e51b81526004016108af906125bf565b6001600160401b03461115611ea55760405163a12e8fa960e01b815260040160405180910390fd5b606580546001600160a01b0319166001600160a01b0392909216919091179055565b5f54610100900460ff16611eed5760405162461bcd60e51b81526004016108af906125bf565b60cc611ef98382612670565b5060cd610f978282612670565b306001600160a01b0382160361093457604051630c292c9d60e21b815260040160405180910390fd5b611f3881611c98565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060610848838360405180606001604052806027815260200161276660279139611fb9565b5f54610100900460ff1661097e5760405162461bcd60e51b81526004016108af906125bf565b60605f5f856001600160a01b031685604051611fd5919061272a565b5f60405180830381855af49150503d805f811461200d576040519150601f19603f3d011682016040523d82523d5f602084013e612012565b606091505b50915091506120238683838761202d565b9695505050505050565b6060831561209b5782515f03612094576001600160a01b0385163b6120945760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016108af565b5081610964565b61096483838151156120b05781518083602001fd5b8060405162461bcd60e51b81526004016108af9190612113565b5f602082840312156120da575f5ffd5b81356001600160e01b031981168114610848575f5ffd5b5f5b8381101561210b5781810151838201526020016120f3565b50505f910152565b602081525f82518060208401526121318160408501602087016120f1565b601f01601f19169190910160400192915050565b6001600160a01b0381168114610934575f5ffd5b5f5f6040838503121561216a575f5ffd5b823561217581612145565b946020939093013593505050565b5f5f5f60608486031215612195575f5ffd5b83356121a081612145565b925060208401356121b081612145565b929592945050506040919091013590565b5f602082840312156121d1575f5ffd5b813561084881612145565b803580151581146121eb575f5ffd5b919050565b5f5f5f60608486031215612202575f5ffd5b83356001600160401b0381168114612218575f5ffd5b92506020840135915061222d604085016121dc565b90509250925092565b5f60208284031215612246575f5ffd5b5035919050565b634e487b7160e01b5f52604160045260245ffd5b5f5f60408385031215612272575f5ffd5b823561227d81612145565b915060208301356001600160401b03811115612297575f5ffd5b8301601f810185136122a7575f5ffd5b80356001600160401b038111156122c0576122c061224d565b604051601f8201601f19908116603f011681016001600160401b03811182821017156122ee576122ee61224d565b604052818152828201602001871015612305575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f5f60408385031215612335575f5ffd5b82359150612345602084016121dc565b90509250929050565b5f5f6040838503121561235f575f5ffd5b823561236a81612145565b9150612345602084016121dc565b5f5f83601f840112612388575f5ffd5b5081356001600160401b0381111561239e575f5ffd5b6020830191508360208285010111156123b5575f5ffd5b9250929050565b5f5f5f5f5f5f5f5f5f60e08a8c0312156123d4575f5ffd5b89356123df81612145565b985060208a01356123ef81612145565b975060408a01356123ff81612145565b965060608a0135955060808a013560ff8116811461241b575f5ffd5b945060a08a01356001600160401b03811115612435575f5ffd5b6124418c828d01612378565b90955093505060c08a01356001600160401b0381111561245f575f5ffd5b61246b8c828d01612378565b915080935050809150509295985092959850929598565b5f5f60408385031215612493575f5ffd5b823561249e81612145565b915060208301356124ae81612145565b809150509250929050565b600181811c908216806124cd57607f821691505b6020821081036124eb57634e487b7160e01b5f52602260045260245ffd5b50919050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b8082018082111561077d57634e487b7160e01b5f52601160045260245ffd5b5f602082840312156125b8575f5ffd5b5051919050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f6020828403121561261a575f5ffd5b815161084881612145565b601f821115610f9757805f5260205f20601f840160051c8101602085101561264a5750805b601f840160051c820191505b81811015612669575f8155600101612656565b5050505050565b81516001600160401b038111156126895761268961224d565b61269d8161269784546124b9565b84612625565b6020601f8211600181146126cf575f83156126b85750848201515b5f19600385901b1c1916600184901b178455612669565b5f84815260208120601f198516915b828110156126fe57878501518255602094850194600190920191016126de565b508482101561271b57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b5f825161273b8184602087016120f1565b919091019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212202a7158cd4a2b509c628eb45833453a69c95879669a82d60c84f8626b4c5e2b8d64736f6c634300081b0033", + "code": "0x608060405260043610610207575f3560e01c806352d1902d116101135780638da5cb5b1161009d578063a9059cbb1161006d578063a9059cbb14610602578063b8f2e0c514610621578063dd62ed3e14610640578063f12506c11461065f578063f2fde38b14610673575f5ffd5b80638da5cb5b1461059257806395d89b41146105af578063a457c2d7146105c3578063a77f1516146105e2575f5ffd5b8063715018a6116100e3578063715018a6146105185780637cf8ed0d1461052c5780637e4746341461054b5780638456cb591461056a5780638abf60771461057e575f5ffd5b806352d1902d146104915780635c975abb146104a55780636c0db62b146104c557806370a08231146104e4575f5ffd5b80633075db56116101945780633f4ba83a116101645780633f4ba83a1461041757806340c10f191461042b57806342966c681461044a57806349d12605146104695780634f1ef2861461047e575f5ffd5b80633075db5614610397578063313ce567146103ab5780633659cfe6146103d757806339509351146103f8575f5ffd5b8063095ea7b3116101da578063095ea7b3146102d95780630ae74548146102f857806318160ddd1461031857806323b872dd1461033657806326afaadd14610355575f5ffd5b806301ffc9a71461020b57806304f3bcec1461023f57806306fdde0314610285578063090f9221146102a6575b5f5ffd5b348015610216575f5ffd5b5061022a610225366004611e9f565b610692565b60405190151581526020015b60405180910390f35b34801561024a575f5ffd5b507f00000000000000000000000000000000000000000000000000000000000000005b6040516001600160a01b039091168152602001610236565b348015610290575f5ffd5b50610299610734565b6040516102369190611ee8565b3480156102b1575f5ffd5b5061026d7f000000000000000000000000763375000000000000000000000000000000000281565b3480156102e4575f5ffd5b5061022a6102f3366004611f35565b6107c4565b348015610303575f5ffd5b5060fd5461022a90600160a01b900460ff1681565b348015610323575f5ffd5b5060cb545b604051908152602001610236565b348015610341575f5ffd5b5061022a610350366004611f5d565b6107db565b348015610360575f5ffd5b5061037860fb5460fc546001600160a01b0390911691565b604080516001600160a01b039093168352602083019190915201610236565b3480156103a2575f5ffd5b5061022a6107fe565b3480156103b6575f5ffd5b5060fb54600160a01b900460ff165b60405160ff9091168152602001610236565b3480156103e2575f5ffd5b506103f66103f1366004611f97565b610816565b005b348015610403575f5ffd5b5061022a610412366004611f35565b6108e6565b348015610422575f5ffd5b506103f6610907565b348015610436575f5ffd5b506103f6610445366004611f35565b610993565b348015610455575f5ffd5b506103f6610464366004611fb0565b610aac565b348015610474575f5ffd5b5061032860fc5481565b6103f661048c366004611fdb565b610be8565b34801561049c575f5ffd5b50610328610c9d565b3480156104b0575f5ffd5b5061022a609754610100900460ff1660021490565b3480156104d0575f5ffd5b506103f66104df3660046120e4565b610d4e565b3480156104ef575f5ffd5b506103286104fe366004611f97565b6001600160a01b03165f90815260c9602052604090205490565b348015610523575f5ffd5b506103f6610f0a565b348015610537575f5ffd5b5060fb5461026d906001600160a01b031681565b348015610556575f5ffd5b5060fd5461026d906001600160a01b031681565b348015610575575f5ffd5b506103f6610f1b565b348015610589575f5ffd5b5061026d610fa2565b34801561059d575f5ffd5b506033546001600160a01b031661026d565b3480156105ba575f5ffd5b50610299610fb0565b3480156105ce575f5ffd5b5061022a6105dd366004611f35565b610fbf565b3480156105ed575f5ffd5b5060fb546103c590600160a01b900460ff1681565b34801561060d575f5ffd5b5061022a61061c366004611f35565b611039565b34801561062c575f5ffd5b506103f661063b366004612194565b611046565b34801561064b575f5ffd5b5061032861065a3660046121cd565b6111bf565b34801561066a575f5ffd5b5061022a6111e9565b34801561067e575f5ffd5b506103f661068d366004611f97565b611212565b5f6001600160e01b0319821663093e326b60e21b14806106c257506001600160e01b03198216636c0db62b60e01b145b806106dd57506001600160e01b0319821663b8f2e0c560e01b145b806106f857506001600160e01b031982166336372b0760e01b145b8061071357506001600160e01b0319821663a219a02560e01b145b8061072e57506001600160e01b031982166301ffc9a760e01b145b92915050565b606060cc8054610743906121fe565b80601f016020809104026020016040519081016040528092919081815260200182805461076f906121fe565b80156107ba5780601f10610791576101008083540402835291602001916107ba565b820191905f5260205f20905b81548152906001019060200180831161079d57829003601f168201915b5050505050905090565b5f336107d1818585611288565b5060019392505050565b5f336107e88582856113ab565b6107f3858585611423565b506001949350505050565b5f600261080d60975460ff1690565b60ff1614905090565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000100961630036108675760405162461bcd60e51b815260040161085e90612236565b60405180910390fd5b7f00000000000000000000000007633750000000000000000000000000000100966001600160a01b03166108996115d7565b6001600160a01b0316146108bf5760405162461bcd60e51b815260040161085e90612282565b6108c8816115f2565b604080515f808252602082019092526108e3918391906115fa565b50565b5f336107d18185856108f883836111bf565b61090291906122ce565b611288565b61091b609754610100900460ff1660021490565b6109385760405163bae6e2a960e01b815260040160405180910390fd5b610940611764565b6109546097805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610991335f61177d565b565b6109a7609754610100900460ff1660021490565b156109c55760405163bae6e2a960e01b815260040160405180910390fd5b60026109d360975460ff1690565b60ff16036109f45760405163dfc60d8560e01b815260040160405180910390fd5b6109fe6002611785565b610a066111e9565b15610a245760405163270bf77560e01b815260040160405180910390fd5b60fd546001600160a01b031633819003610a8a57826001600160a01b0316816001600160a01b03167fe502aa3e015149f4b76a0b2b5394e3100903c4af27c3ddc98385395d3f55252684604051610a7d91815260200190565b60405180910390a3610a93565b610a933361179b565b610a9d8383611812565b50610aa86001611785565b5050565b610ac0609754610100900460ff1660021490565b15610ade5760405163bae6e2a960e01b815260040160405180910390fd5b6002610aec60975460ff1690565b60ff1603610b0d5760405163dfc60d8560e01b815260040160405180910390fd5b610b176002611785565b610b1f6111e9565b15610bcb5760fd546040518281526001600160a01b0390911690339082907f638edf84937fb2534b47cac985ea84d6ea4f4076315b56ea1c784d26b87e2bcb9060200160405180910390a36040516340c10f1960e01b8152336004820152602481018390526001600160a01b038216906340c10f19906044015f604051808303815f87803b158015610baf575f5ffd5b505af1158015610bc1573d5f5f3e3d5ffd5b5050505050610bd4565b610bd43361179b565b610bde33826118dc565b6108e36001611785565b6001600160a01b037f0000000000000000000000000763375000000000000000000000000000010096163003610c305760405162461bcd60e51b815260040161085e90612236565b7f00000000000000000000000007633750000000000000000000000000000100966001600160a01b0316610c626115d7565b6001600160a01b031614610c885760405162461bcd60e51b815260040161085e90612282565b610c91826115f2565b610aa8828260016115fa565b5f306001600160a01b037f00000000000000000000000007633750000000000000000000000000000100961614610d3c5760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c0000000000000000606482015260840161085e565b505f5160206124715f395f51905f5290565b5f54610100900460ff1615808015610d6c57505f54600160ff909116105b80610d855750303b158015610d8557505f5460ff166001145b610de85760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161085e565b5f805460ff191660011790558015610e09575f805461ff0019166101001790555b610e138888611a19565b610e1c89611a55565b610e8d83838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525050604080516020601f8b0181900481028201810190925289815292508991508890819084018382808284375f92019190915250611ab392505050565b60fb805460fc89905560ff8816600160a01b026001600160a81b03199091166001600160a01b038b16171790558015610eff575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050505050565b610f12611ae3565b6109915f611b3d565b610f2f609754610100900460ff1660021490565b15610f4d5760405163bae6e2a960e01b815260040160405180910390fd5b610f55611764565b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a161099133600161177d565b5f610fab6115d7565b905090565b606060cd8054610743906121fe565b5f3381610fcc82866111bf565b90508381101561102c5760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b606482015260840161085e565b6107f38286868403611288565b5f336107d1818585611423565b61105a609754610100900460ff1660021490565b156110785760405163bae6e2a960e01b815260040160405180910390fd5b7f0000000000000000000000007633750000000000000000000000000000000002336001600160a01b038216146110c2576040516395383ea160e01b815260040160405180910390fd5b60026110d060975460ff1690565b60ff16036110f15760405163dfc60d8560e01b815260040160405180910390fd5b6110fb6002611785565b60fd546001600160a01b03848116911614801561112a575060fd60149054906101000a900460ff161515821515145b156111485760405163c118d2f360e01b815260040160405180910390fd5b60fd80546001600160a01b0385166001600160a81b03199091168117600160a01b851515908102919091179092556040805191825260208201929092527fa6b6f959792843a48d9d03d13595f2de7c86ae0ce12ef0fa759dd911b205e565910160405180910390a16111ba6001611785565b505050565b6001600160a01b039182165f90815260ca6020908152604080832093909416825291909152205490565b60fd545f906001600160a01b031615801590610fab57505060fd54600160a01b900460ff161590565b61121a611ae3565b6001600160a01b03811661127f5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b606482015260840161085e565b6108e381611b3d565b6001600160a01b0383166112ea5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b606482015260840161085e565b6001600160a01b03821661134b5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b606482015260840161085e565b6001600160a01b038381165f81815260ca602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b5f6113b684846111bf565b90505f19811461141d57818110156114105760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000604482015260640161085e565b61141d8484848403611288565b50505050565b6001600160a01b0383166114875760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b606482015260840161085e565b6001600160a01b0382166114e95760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b606482015260840161085e565b6114f4838383611b8e565b6001600160a01b0383165f90815260c960205260409020548181101561156b5760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b606482015260840161085e565b6001600160a01b038085165f81815260c9602052604080822086860390559286168082529083902080548601905591517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906115ca9086815260200190565b60405180910390a361141d565b5f5160206124715f395f51905f52546001600160a01b031690565b6108e3611ae3565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff161561162d576111ba83611bc9565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611687575060408051601f3d908101601f19168201909252611684918101906122ed565b60015b6116ea5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b606482015260840161085e565b5f5160206124715f395f51905f5281146117585760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b606482015260840161085e565b506111ba838383611c64565b60405163a87dd7cf60e01b815260040160405180910390fd5b610aa8611ae3565b6097805460ff191660ff92909216919091179055565b7f00000000000000000000000076337500000000000000000000000000000000026117ce6033546001600160a01b031690565b6001600160a01b0316336001600160a01b031614806117f55750336001600160a01b038216145b610aa8576040516395383ea160e01b815260040160405180910390fd5b6001600160a01b0382166118685760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015260640161085e565b6118735f8383611b8e565b8060cb5f82825461188491906122ce565b90915550506001600160a01b0382165f81815260c960209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b6001600160a01b03821661193c5760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b606482015260840161085e565b611947825f83611b8e565b6001600160a01b0382165f90815260c96020526040902054818110156119ba5760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b606482015260840161085e565b6001600160a01b0383165f81815260c960209081526040808320868603905560cb80548790039055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3505050565b6001600160a01b0382161580611a2d575080155b80611a3757504681145b15610aa85760405163c118d2f360e01b815260040160405180910390fd5b5f54610100900460ff16611a7b5760405162461bcd60e51b815260040161085e90612304565b611a83611c88565b611aa16001600160a01b03821615611a9b5781611b3d565b33611b3d565b506097805461ff001916610100179055565b5f54610100900460ff16611ad95760405162461bcd60e51b815260040161085e90612304565b610aa88282611cae565b6033546001600160a01b031633146109915760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161085e565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b611ba2609754610100900460ff1660021490565b15611bc05760405163bae6e2a960e01b815260040160405180910390fd5b6111ba82611ced565b6001600160a01b0381163b611c365760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161085e565b5f5160206124715f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b611c6d83611d16565b5f82511180611c795750805b156111ba5761141d8383611d55565b5f54610100900460ff166109915760405162461bcd60e51b815260040161085e90612304565b5f54610100900460ff16611cd45760405162461bcd60e51b815260040161085e90612304565b60cc611ce0838261239a565b5060cd6111ba828261239a565b306001600160a01b038216036108e357604051630c292c9d60e21b815260040160405180910390fd5b611d1f81611bc9565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060611d7a838360405180606001604052806027815260200161249160279139611d81565b9392505050565b60605f5f856001600160a01b031685604051611d9d9190612455565b5f60405180830381855af49150503d805f8114611dd5576040519150601f19603f3d011682016040523d82523d5f602084013e611dda565b606091505b5091509150611deb86838387611df5565b9695505050505050565b60608315611e635782515f03611e5c576001600160a01b0385163b611e5c5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161085e565b5081611e6d565b611e6d8383611e75565b949350505050565b815115611e855781518083602001fd5b8060405162461bcd60e51b815260040161085e9190611ee8565b5f60208284031215611eaf575f5ffd5b81356001600160e01b031981168114611d7a575f5ffd5b5f5b83811015611ee0578181015183820152602001611ec8565b50505f910152565b602081525f8251806020840152611f06816040850160208701611ec6565b601f01601f19169190910160400192915050565b80356001600160a01b0381168114611f30575f5ffd5b919050565b5f5f60408385031215611f46575f5ffd5b611f4f83611f1a565b946020939093013593505050565b5f5f5f60608486031215611f6f575f5ffd5b611f7884611f1a565b9250611f8660208501611f1a565b929592945050506040919091013590565b5f60208284031215611fa7575f5ffd5b611d7a82611f1a565b5f60208284031215611fc0575f5ffd5b5035919050565b634e487b7160e01b5f52604160045260245ffd5b5f5f60408385031215611fec575f5ffd5b611ff583611f1a565b9150602083013567ffffffffffffffff811115612010575f5ffd5b8301601f81018513612020575f5ffd5b803567ffffffffffffffff81111561203a5761203a611fc7565b604051601f8201601f19908116603f0116810167ffffffffffffffff8111828210171561206957612069611fc7565b604052818152828201602001871015612080575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f5f83601f8401126120af575f5ffd5b50813567ffffffffffffffff8111156120c6575f5ffd5b6020830191508360208285010111156120dd575f5ffd5b9250929050565b5f5f5f5f5f5f5f5f60c0898b0312156120fb575f5ffd5b61210489611f1a565b975061211260208a01611f1a565b965060408901359550606089013560ff8116811461212e575f5ffd5b9450608089013567ffffffffffffffff811115612149575f5ffd5b6121558b828c0161209f565b90955093505060a089013567ffffffffffffffff811115612174575f5ffd5b6121808b828c0161209f565b999c989b5096995094979396929594505050565b5f5f604083850312156121a5575f5ffd5b6121ae83611f1a565b9150602083013580151581146121c2575f5ffd5b809150509250929050565b5f5f604083850312156121de575f5ffd5b6121e783611f1a565b91506121f560208401611f1a565b90509250929050565b600181811c9082168061221257607f821691505b60208210810361223057634e487b7160e01b5f52602260045260245ffd5b50919050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b8082018082111561072e57634e487b7160e01b5f52601160045260245ffd5b5f602082840312156122fd575f5ffd5b5051919050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b601f8211156111ba57805f5260205f20601f840160051c810160208510156123745750805b601f840160051c820191505b81811015612393575f8155600101612380565b5050505050565b815167ffffffffffffffff8111156123b4576123b4611fc7565b6123c8816123c284546121fe565b8461234f565b6020601f8211600181146123fa575f83156123e35750848201515b5f19600385901b1c1916600184901b178455612393565b5f84815260208120601f198516915b828110156124295787850151825560209485019460019092019101612409565b508482101561244657868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b5f8251612466818460208701611ec6565b919091019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220010c4268de17d4ac55a4196d0fab5987e56c022fe089b369ca01cce3b2982bb164736f6c634300081b0033", "balance": "0x0" }, "0x0763375000000000000000000000000000010097": { "contractName": "BridgedERC721", "storage": {}, - "code": "0x6080604052600436106101f1575f3560e01c80635c975abb1161010857806395d89b411161009d578063c87b56dd1161006d578063c87b56dd1461057a578063e07baba614610599578063e985e9c5146105d6578063ef8c4ae61461061e578063f2fde38b1461063d575f5ffd5b806395d89b4114610509578063a22cb4651461051d578063a86f9d9e1461053c578063b88d4fde1461055b575f5ffd5b80637cf8ed0d116100d85780637cf8ed0d146104a45780638456cb59146104c45780638abf6077146104d85780638da5cb5b146104ec575f5ffd5b80635c975abb146104325780636352211e1461045257806370a0823114610471578063715018a614610490575f5ffd5b80633ab76e9f1161018957806342842e0e1161015957806342842e0e146103a957806342966c68146103c857806349d12605146103e75780634f1ef2861461040b57806352d1902d1461041e575f5ffd5b80633ab76e9f146103385780633eb6b8cf146103575780633f4ba83a1461037657806340c10f191461038a575f5ffd5b806323b872dd116101c457806323b872dd146102a257806326afaadd146102c15780633075db56146103055780633659cfe614610319575f5ffd5b806301ffc9a7146101f557806306fdde0314610229578063081812fc1461024a578063095ea7b314610281575b5f5ffd5b348015610200575f5ffd5b5061021461020f366004612477565b61065c565b60405190151581526020015b60405180910390f35b348015610234575f5ffd5b5061023d6106a1565b60405161022091906124df565b348015610255575f5ffd5b506102696102643660046124f1565b610731565b6040516001600160a01b039091168152602001610220565b34801561028c575f5ffd5b506102a061029b36600461251c565b610756565b005b3480156102ad575f5ffd5b506102a06102bc366004612546565b61086f565b3480156102cc575f5ffd5b506102e661012d5461012e546001600160a01b0390911691565b604080516001600160a01b039093168352602083019190915201610220565b348015610310575f5ffd5b506102146108a0565b348015610324575f5ffd5b506102a0610333366004612584565b6108b8565b348015610343575f5ffd5b50606554610269906001600160a01b031681565b348015610362575f5ffd5b506102696103713660046125b3565b61097f565b348015610381575f5ffd5b506102a0610995565b348015610395575f5ffd5b506102a06103a436600461251c565b6109a9565b3480156103b4575f5ffd5b506102a06103c3366004612546565b610a74565b3480156103d3575f5ffd5b506102a06103e23660046124f1565b610a8e565b3480156103f2575f5ffd5b506103fd61012e5481565b604051908152602001610220565b6102a0610419366004612696565b610b8d565b348015610429575f5ffd5b506103fd610c42565b34801561043d575f5ffd5b50610214609754610100900460ff1660021490565b34801561045d575f5ffd5b5061026961046c3660046124f1565b610cf3565b34801561047c575f5ffd5b506103fd61048b366004612584565b610d52565b34801561049b575f5ffd5b506102a0610dd6565b3480156104af575f5ffd5b5061012d54610269906001600160a01b031681565b3480156104cf575f5ffd5b506102a0610de7565b3480156104e3575f5ffd5b50610269610e02565b3480156104f7575f5ffd5b506033546001600160a01b0316610269565b348015610514575f5ffd5b5061023d610e10565b348015610528575f5ffd5b506102a06105373660046126e2565b610e1f565b348015610547575f5ffd5b50610269610556366004612715565b610e2a565b348015610566575f5ffd5b506102a0610575366004612736565b610e36565b348015610585575f5ffd5b5061023d6105943660046124f1565b610e6e565b3480156105a4575f5ffd5b506097546105be906201000090046001600160401b031681565b6040516001600160401b039091168152602001610220565b3480156105e1575f5ffd5b506102146105f036600461279d565b6001600160a01b039182165f9081526101006020908152604080832093909416825291909152205460ff1690565b348015610629575f5ffd5b506102a0610638366004612818565b610e97565b348015610648575f5ffd5b506102a0610657366004612584565b611049565b5f6001600160e01b0319821663093e326b60e21b148061068c57506001600160e01b031982166377c6257360e11b145b8061069b575061069b826110bf565b92915050565b606060fb80546106b0906128c5565b80601f01602080910402602001604051908101604052809291908181526020018280546106dc906128c5565b80156107275780601f106106fe57610100808354040283529160200191610727565b820191905f5260205f20905b81548152906001019060200180831161070a57829003601f168201915b5050505050905090565b5f61073b8261110e565b505f90815260ff60205260409020546001600160a01b031690565b5f61076082610cf3565b9050806001600160a01b0316836001600160a01b0316036107d25760405162461bcd60e51b815260206004820152602160248201527f4552433732313a20617070726f76616c20746f2063757272656e74206f776e656044820152603960f91b60648201526084015b60405180910390fd5b336001600160a01b03821614806107ee57506107ee81336105f0565b6108605760405162461bcd60e51b815260206004820152603d60248201527f4552433732313a20617070726f76652063616c6c6572206973206e6f7420746f60448201527f6b656e206f776e6572206f7220617070726f76656420666f7220616c6c00000060648201526084016107c9565b61086a838361116c565b505050565b61087933826111d9565b6108955760405162461bcd60e51b81526004016107c9906128fd565b61086a838383611257565b5f60026108af60975460ff1690565b60ff1614905090565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000100971630036109005760405162461bcd60e51b81526004016107c99061294a565b7f00000000000000000000000007633750000000000000000000000000000100976001600160a01b03166109326113c6565b6001600160a01b0316146109585760405162461bcd60e51b81526004016107c990612996565b610961816113e1565b604080515f8082526020820190925261097c918391906113e9565b50565b5f61098b848484611553565b90505b9392505050565b61099d6115a5565b6109a7335f611635565b565b6109bd609754610100900460ff1660021490565b156109db5760405163bae6e2a960e01b815260040160405180910390fd5b6b195c98cdcc8c57dd985d5b1d60a21b6109f6816001610e2a565b6001600160a01b0316336001600160a01b031614610a2757604051630d85cccf60e11b815260040160405180910390fd5b6002610a3560975460ff1690565b60ff1603610a565760405163dfc60d8560e01b815260040160405180910390fd5b610a60600261163d565b610a6a8383611653565b61086a600161163d565b61086a83838360405180602001604052805f815250610e36565b610aa2609754610100900460ff1660021490565b15610ac05760405163bae6e2a960e01b815260040160405180910390fd5b6b195c98cdcc8c57dd985d5b1d60a21b610adb816001610e2a565b6001600160a01b0316336001600160a01b031614610b0c57604051630d85cccf60e11b815260040160405180910390fd5b6002610b1a60975460ff1690565b60ff1603610b3b5760405163dfc60d8560e01b815260040160405180910390fd5b610b45600261163d565b33610b4f83610cf3565b6001600160a01b031614610b765760405163358bf3d960e01b815260040160405180910390fd5b610b7f8261166c565b610b89600161163d565b5050565b6001600160a01b037f0000000000000000000000000763375000000000000000000000000000010097163003610bd55760405162461bcd60e51b81526004016107c99061294a565b7f00000000000000000000000007633750000000000000000000000000000100976001600160a01b0316610c076113c6565b6001600160a01b031614610c2d5760405162461bcd60e51b81526004016107c990612996565b610c36826113e1565b610b89828260016113e9565b5f306001600160a01b037f00000000000000000000000007633750000000000000000000000000000100971614610ce15760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c000000000000000060648201526084016107c9565b505f516020612d535f395f51905f5290565b5f81815260fd60205260408120546001600160a01b03168061069b5760405162461bcd60e51b8152602060048201526018602482015277115490cdcc8c4e881a5b9d985b1a59081d1bdad95b88125160421b60448201526064016107c9565b5f6001600160a01b038216610dbb5760405162461bcd60e51b815260206004820152602960248201527f4552433732313a2061646472657373207a65726f206973206e6f7420612076616044820152683634b21037bbb732b960b91b60648201526084016107c9565b506001600160a01b03165f90815260fe602052604090205490565b610dde61170b565b6109a75f611765565b610def6117b6565b610df76117cf565b6109a7336001611635565b5f610e0b6113c6565b905090565b606060fc80546106b0906128c5565b610b89338383611840565b5f61098e468484611553565b610e4033836111d9565b610e5c5760405162461bcd60e51b81526004016107c9906128fd565b610e688484848461190e565b50505050565b61012d5461012e5460609161069b916001600160a01b0390911690610e9285611941565b6119d0565b5f54610100900460ff1615808015610eb557505f54600160ff909116105b80610ece5750303b158015610ece57505f5460ff166001145b610f315760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016107c9565b5f805460ff191660011790558015610f52575f805461ff0019166101001790555b610f5c8787611a1a565b610f668989611a56565b610fd783838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525050604080516020601f8b0181900481028201810190925289815292508991508890819084018382808284375f92019190915250611a9092505050565b61012d80546001600160a01b0319166001600160a01b03891617905561012e869055801561103e575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050505050565b61105161170b565b6001600160a01b0381166110b65760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016107c9565b61097c81611765565b5f6001600160e01b031982166380ac58cd60e01b14806110ef57506001600160e01b03198216635b5e139f60e01b145b8061069b57506301ffc9a760e01b6001600160e01b031983161461069b565b5f81815260fd60205260409020546001600160a01b031661097c5760405162461bcd60e51b8152602060048201526018602482015277115490cdcc8c4e881a5b9d985b1a59081d1bdad95b88125160421b60448201526064016107c9565b5f81815260ff6020526040902080546001600160a01b0319166001600160a01b03841690811790915581906111a082610cf3565b6001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b5f5f6111e483610cf3565b9050806001600160a01b0316846001600160a01b0316148061122b57506001600160a01b038082165f908152610100602090815260408083209388168352929052205460ff165b8061124f5750836001600160a01b031661124484610731565b6001600160a01b0316145b949350505050565b826001600160a01b031661126a82610cf3565b6001600160a01b0316146112905760405162461bcd60e51b81526004016107c9906129e2565b6001600160a01b0382166112f25760405162461bcd60e51b8152602060048201526024808201527f4552433732313a207472616e7366657220746f20746865207a65726f206164646044820152637265737360e01b60648201526084016107c9565b6112ff8383836001611ac0565b826001600160a01b031661131282610cf3565b6001600160a01b0316146113385760405162461bcd60e51b81526004016107c9906129e2565b5f81815260ff6020908152604080832080546001600160a01b03199081169091556001600160a01b0387811680865260fe855283862080545f190190559087168086528386208054600101905586865260fd90945282852080549092168417909155905184937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4505050565b5f516020612d535f395f51905f52546001600160a01b031690565b61097c61170b565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff161561141c5761086a83611b00565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611476575060408051601f3d908101601f1916820190925261147391810190612a27565b60015b6114d95760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b60648201526084016107c9565b5f516020612d535f395f51905f5281146115475760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b60648201526084016107c9565b5061086a838383611b9b565b5f61155e8484611bbf565b90508115801561157557506001600160a01b038116155b1561098e57604051632b0d65db60e01b81526001600160401b0385166004820152602481018490526044016107c9565b6115b9609754610100900460ff1660021490565b6115d65760405163bae6e2a960e01b815260040160405180910390fd5b6097805461010069ffffffffffffffffff001990911662010000426001600160401b031602171790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa906020015b60405180910390a1565b610b8961170b565b6097805460ff191660ff92909216919091179055565b610b89828260405180602001604052805f815250611c61565b5f61167682610cf3565b9050611685815f846001611ac0565b61168e82610cf3565b5f83815260ff6020908152604080832080546001600160a01b03199081169091556001600160a01b03851680855260fe845282852080545f1901905587855260fd909352818420805490911690555192935084927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908390a45050565b6033546001600160a01b031633146109a75760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016107c9565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b60405163a87dd7cf60e01b815260040160405180910390fd5b6117e3609754610100900460ff1660021490565b156118015760405163bae6e2a960e01b815260040160405180910390fd5b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200161162b565b816001600160a01b0316836001600160a01b0316036118a15760405162461bcd60e51b815260206004820152601960248201527f4552433732313a20617070726f766520746f2063616c6c65720000000000000060448201526064016107c9565b6001600160a01b038381165f8181526101006020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b611919848484611257565b61192584848484611c93565b610e685760405162461bcd60e51b81526004016107c990612a3e565b60605f61194d83611d90565b60010190505f816001600160401b0381111561196b5761196b6125f9565b6040519080825280601f01601f191660200182016040528015611995576020820181803683370190505b5090508181016020015b5f19016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a850494508461199f57509392505050565b60606119e6846001600160a01b03166014611e67565b6119ef84611941565b83604051602001611a0293929190612a90565b60405160208183030381529060405290509392505050565b6001600160a01b0382161580611a2e575080155b80611a3857504681145b15610b895760405163c118d2f360e01b815260040160405180910390fd5b806001600160a01b038116611a7e5760405163538ba4f960e01b815260040160405180910390fd5b611a8783611ffc565b61086a8261205a565b5f54610100900460ff16611ab65760405162461bcd60e51b81526004016107c990612b18565b610b8982826120ca565b611ad4609754610100900460ff1660021490565b15611af25760405163bae6e2a960e01b815260040160405180910390fd5b611afb83612109565b610e68565b6001600160a01b0381163b611b6d5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016107c9565b5f516020612d535f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b611ba483612132565b5f82511180611bb05750805b1561086a57610e688383612171565b6065545f906001600160a01b031680611beb57604051638ed88b2560e01b815260040160405180910390fd5b604051630a3dc4f360e21b81526001600160401b0385166004820152602481018490526001600160a01b038216906328f713cc90604401602060405180830381865afa158015611c3d573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061124f9190612b63565b611c6b8383612196565b611c775f848484611c93565b61086a5760405162461bcd60e51b81526004016107c990612a3e565b5f6001600160a01b0384163b15611d8557604051630a85bd0160e11b81526001600160a01b0385169063150b7a0290611cd6903390899088908890600401612b7e565b6020604051808303815f875af1925050508015611d10575060408051601f3d908101601f19168201909252611d0d91810190612bb0565b60015b611d6b573d808015611d3d576040519150601f19603f3d011682016040523d82523d5f602084013e611d42565b606091505b5080515f03611d635760405162461bcd60e51b81526004016107c990612a3e565b805181602001fd5b6001600160e01b031916630a85bd0160e11b14905061124f565b506001949350505050565b5f8072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b8310611dce5772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef81000000008310611dfa576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc100008310611e1857662386f26fc10000830492506010015b6305f5e1008310611e30576305f5e100830492506008015b6127108310611e4457612710830492506004015b60648310611e56576064830492506002015b600a831061069b5760010192915050565b60605f611e75836002612bdf565b611e80906002612bf6565b6001600160401b03811115611e9757611e976125f9565b6040519080825280601f01601f191660200182016040528015611ec1576020820181803683370190505b509050600360fc1b815f81518110611edb57611edb612c09565b60200101906001600160f81b03191690815f1a905350600f60fb1b81600181518110611f0957611f09612c09565b60200101906001600160f81b03191690815f1a9053505f611f2b846002612bdf565b611f36906001612bf6565b90505b6001811115611fad576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110611f6a57611f6a612c09565b1a60f81b828281518110611f8057611f80612c09565b60200101906001600160f81b03191690815f1a90535060049490941c93611fa681612c1d565b9050611f39565b50831561098e5760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e7460448201526064016107c9565b5f54610100900460ff166120225760405162461bcd60e51b81526004016107c990612b18565b61202a61232b565b6120486001600160a01b038216156120425781611765565b33611765565b506097805461ff001916610100179055565b5f54610100900460ff166120805760405162461bcd60e51b81526004016107c990612b18565b6001600160401b034611156120a85760405163a12e8fa960e01b815260040160405180910390fd5b606580546001600160a01b0319166001600160a01b0392909216919091179055565b5f54610100900460ff166120f05760405162461bcd60e51b81526004016107c990612b18565b60fb6120fc8382612c7d565b5060fc61086a8282612c7d565b306001600160a01b0382160361097c57604051630c292c9d60e21b815260040160405180910390fd5b61213b81611b00565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606061098e8383604051806060016040528060278152602001612d7360279139612351565b6001600160a01b0382166121ec5760405162461bcd60e51b815260206004820181905260248201527f4552433732313a206d696e7420746f20746865207a65726f206164647265737360448201526064016107c9565b5f81815260fd60205260409020546001600160a01b0316156122505760405162461bcd60e51b815260206004820152601c60248201527f4552433732313a20746f6b656e20616c7265616479206d696e7465640000000060448201526064016107c9565b61225d5f83836001611ac0565b5f81815260fd60205260409020546001600160a01b0316156122c15760405162461bcd60e51b815260206004820152601c60248201527f4552433732313a20746f6b656e20616c7265616479206d696e7465640000000060448201526064016107c9565b6001600160a01b0382165f81815260fe602090815260408083208054600101905584835260fd90915280822080546001600160a01b0319168417905551839291907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a45050565b5f54610100900460ff166109a75760405162461bcd60e51b81526004016107c990612b18565b60605f5f856001600160a01b03168560405161236d9190612d37565b5f60405180830381855af49150503d805f81146123a5576040519150601f19603f3d011682016040523d82523d5f602084013e6123aa565b606091505b50915091506123bb868383876123c5565b9695505050505050565b606083156124335782515f0361242c576001600160a01b0385163b61242c5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016107c9565b508161124f565b61124f83838151156124485781518083602001fd5b8060405162461bcd60e51b81526004016107c991906124df565b6001600160e01b03198116811461097c575f5ffd5b5f60208284031215612487575f5ffd5b813561098e81612462565b5f5b838110156124ac578181015183820152602001612494565b50505f910152565b5f81518084526124cb816020860160208601612492565b601f01601f19169290920160200192915050565b602081525f61098e60208301846124b4565b5f60208284031215612501575f5ffd5b5035919050565b6001600160a01b038116811461097c575f5ffd5b5f5f6040838503121561252d575f5ffd5b823561253881612508565b946020939093013593505050565b5f5f5f60608486031215612558575f5ffd5b833561256381612508565b9250602084013561257381612508565b929592945050506040919091013590565b5f60208284031215612594575f5ffd5b813561098e81612508565b803580151581146125ae575f5ffd5b919050565b5f5f5f606084860312156125c5575f5ffd5b83356001600160401b03811681146125db575f5ffd5b9250602084013591506125f06040850161259f565b90509250925092565b634e487b7160e01b5f52604160045260245ffd5b5f82601f83011261261c575f5ffd5b81356001600160401b03811115612635576126356125f9565b604051601f8201601f19908116603f011681016001600160401b0381118282101715612663576126636125f9565b60405281815283820160200185101561267a575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f5f604083850312156126a7575f5ffd5b82356126b281612508565b915060208301356001600160401b038111156126cc575f5ffd5b6126d88582860161260d565b9150509250929050565b5f5f604083850312156126f3575f5ffd5b82356126fe81612508565b915061270c6020840161259f565b90509250929050565b5f5f60408385031215612726575f5ffd5b8235915061270c6020840161259f565b5f5f5f5f60808587031215612749575f5ffd5b843561275481612508565b9350602085013561276481612508565b92506040850135915060608501356001600160401b03811115612785575f5ffd5b6127918782880161260d565b91505092959194509250565b5f5f604083850312156127ae575f5ffd5b82356127b981612508565b915060208301356127c981612508565b809150509250929050565b5f5f83601f8401126127e4575f5ffd5b5081356001600160401b038111156127fa575f5ffd5b602083019150836020828501011115612811575f5ffd5b9250929050565b5f5f5f5f5f5f5f5f60c0898b03121561282f575f5ffd5b883561283a81612508565b9750602089013561284a81612508565b9650604089013561285a81612508565b95506060890135945060808901356001600160401b0381111561287b575f5ffd5b6128878b828c016127d4565b90955093505060a08901356001600160401b038111156128a5575f5ffd5b6128b18b828c016127d4565b999c989b5096995094979396929594505050565b600181811c908216806128d957607f821691505b6020821081036128f757634e487b7160e01b5f52602260045260245ffd5b50919050565b6020808252602d908201527f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560408201526c1c881bdc88185c1c1c9bdd9959609a1b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b60208082526025908201527f4552433732313a207472616e736665722066726f6d20696e636f72726563742060408201526437bbb732b960d91b606082015260800190565b5f60208284031215612a37575f5ffd5b5051919050565b60208082526032908201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560408201527131b2b4bb32b91034b6b83632b6b2b73a32b960711b606082015260800190565b6832ba3432b932bab69d60b91b81525f8451612ab3816009850160208901612492565b600160fe1b6009918401918201528451612ad481600a840160208901612492565b600981830101915050712f746f6b656e5552493f75696e743235363d60701b60018201528351612b0b816013840160208801612492565b0160130195945050505050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f60208284031215612b73575f5ffd5b815161098e81612508565b6001600160a01b03858116825284166020820152604081018390526080606082018190525f906123bb908301846124b4565b5f60208284031215612bc0575f5ffd5b815161098e81612462565b634e487b7160e01b5f52601160045260245ffd5b808202811582820484141761069b5761069b612bcb565b8082018082111561069b5761069b612bcb565b634e487b7160e01b5f52603260045260245ffd5b5f81612c2b57612c2b612bcb565b505f190190565b601f82111561086a57805f5260205f20601f840160051c81016020851015612c575750805b601f840160051c820191505b81811015612c76575f8155600101612c63565b5050505050565b81516001600160401b03811115612c9657612c966125f9565b612caa81612ca484546128c5565b84612c32565b6020601f821160018114612cdc575f8315612cc55750848201515b5f19600385901b1c1916600184901b178455612c76565b5f84815260208120601f198516915b82811015612d0b5787850151825560209485019460019092019101612ceb565b5084821015612d2857868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b5f8251612d48818460208701612492565b919091019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220bf89547c7769429be90e7f8c3018a654d841ae3bb533c4245d480333373741ed64736f6c634300081b0033", + "code": "0x6080604052600436106101db575f3560e01c80635c975abb116100fd57806395d89b4111610092578063c87b56dd11610062578063c87b56dd14610568578063d1399b1a14610587578063e985e9c5146105a6578063f2fde38b146105c5575f5ffd5b806395d89b41146104e3578063a22cb465146104f7578063b88d4fde14610516578063c0bb46d614610535575f5ffd5b80637cf8ed0d116100cd5780637cf8ed0d1461047e5780638456cb591461049e5780638abf6077146104b25780638da5cb5b146104c6575f5ffd5b80635c975abb1461040c5780636352211e1461042c57806370a082311461044b578063715018a61461046a575f5ffd5b80633659cfe61161017357806342966c681161014357806342966c68146103a257806349d12605146103c15780634f1ef286146103e557806352d1902d146103f8575f5ffd5b80633659cfe6146103315780633f4ba83a1461035057806340c10f191461036457806342842e0e14610383575f5ffd5b8063095ea7b3116101ae578063095ea7b31461029957806323b872dd146102ba57806326afaadd146102d95780633075db561461031d575f5ffd5b806301ffc9a7146101df57806304f3bcec1461021357806306fdde0314610259578063081812fc1461027a575b5f5ffd5b3480156101ea575f5ffd5b506101fe6101f9366004612233565b6105e4565b60405190151581526020015b60405180910390f35b34801561021e575f5ffd5b507f00000000000000000000000000000000000000000000000000000000000000005b6040516001600160a01b03909116815260200161020a565b348015610264575f5ffd5b5061026d610629565b60405161020a919061229b565b348015610285575f5ffd5b506102416102943660046122ad565b6106b9565b3480156102a4575f5ffd5b506102b86102b33660046122df565b6106de565b005b3480156102c5575f5ffd5b506102b86102d4366004612307565b6107f7565b3480156102e4575f5ffd5b506102fe61012d5461012e546001600160a01b0390911691565b604080516001600160a01b03909316835260208301919091520161020a565b348015610328575f5ffd5b506101fe610828565b34801561033c575f5ffd5b506102b861034b366004612341565b610840565b34801561035b575f5ffd5b506102b8610907565b34801561036f575f5ffd5b506102b861037e3660046122df565b610993565b34801561038e575f5ffd5b506102b861039d366004612307565b610a5c565b3480156103ad575f5ffd5b506102b86103bc3660046122ad565b610a76565b3480156103cc575f5ffd5b506103d761012e5481565b60405190815260200161020a565b6102b86103f33660046123f9565b610b73565b348015610403575f5ffd5b506103d7610c28565b348015610417575f5ffd5b506101fe609754610100900460ff1660021490565b348015610437575f5ffd5b506102416104463660046122ad565b610cd9565b348015610456575f5ffd5b506103d7610465366004612341565b610d38565b348015610475575f5ffd5b506102b8610dbc565b348015610489575f5ffd5b5061012d54610241906001600160a01b031681565b3480156104a9575f5ffd5b506102b8610dcd565b3480156104bd575f5ffd5b50610241610e54565b3480156104d1575f5ffd5b506033546001600160a01b0316610241565b3480156104ee575f5ffd5b5061026d610e62565b348015610502575f5ffd5b506102b8610511366004612444565b610e71565b348015610521575f5ffd5b506102b861053036600461247d565b610e7c565b348015610540575f5ffd5b506102417f000000000000000000000000763375000000000000000000000000000000000381565b348015610573575f5ffd5b5061026d6105823660046122ad565b610eb4565b348015610592575f5ffd5b506102b86105a1366004612526565b610edd565b3480156105b1575f5ffd5b506101fe6105c03660046125bf565b61108d565b3480156105d0575f5ffd5b506102b86105df366004612341565b6110bb565b5f6001600160e01b0319821663093e326b60e21b148061061457506001600160e01b0319821663689ccd8d60e11b145b80610623575061062382611131565b92915050565b606060fb8054610638906125f0565b80601f0160208091040260200160405190810160405280929190818152602001828054610664906125f0565b80156106af5780601f10610686576101008083540402835291602001916106af565b820191905f5260205f20905b81548152906001019060200180831161069257829003601f168201915b5050505050905090565b5f6106c382611180565b505f90815260ff60205260409020546001600160a01b031690565b5f6106e882610cd9565b9050806001600160a01b0316836001600160a01b03160361075a5760405162461bcd60e51b815260206004820152602160248201527f4552433732313a20617070726f76616c20746f2063757272656e74206f776e656044820152603960f91b60648201526084015b60405180910390fd5b336001600160a01b03821614806107765750610776813361108d565b6107e85760405162461bcd60e51b815260206004820152603d60248201527f4552433732313a20617070726f76652063616c6c6572206973206e6f7420746f60448201527f6b656e206f776e6572206f7220617070726f76656420666f7220616c6c0000006064820152608401610751565b6107f283836111de565b505050565b610801338261124b565b61081d5760405162461bcd60e51b815260040161075190612628565b6107f28383836112a9565b5f600261083760975460ff1690565b60ff1614905090565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000100971630036108885760405162461bcd60e51b815260040161075190612675565b7f00000000000000000000000007633750000000000000000000000000000100976001600160a01b03166108ba611418565b6001600160a01b0316146108e05760405162461bcd60e51b8152600401610751906126c1565b6108e981611433565b604080515f808252602082019092526109049183919061143b565b50565b61091b609754610100900460ff1660021490565b6109385760405163bae6e2a960e01b815260040160405180910390fd5b6109406115a5565b6109546097805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610991335f6115be565b565b6109a7609754610100900460ff1660021490565b156109c55760405163bae6e2a960e01b815260040160405180910390fd5b7f0000000000000000000000007633750000000000000000000000000000000003336001600160a01b03821614610a0f576040516395383ea160e01b815260040160405180910390fd5b6002610a1d60975460ff1690565b60ff1603610a3e5760405163dfc60d8560e01b815260040160405180910390fd5b610a4860026115c6565b610a5283836115dc565b6107f260016115c6565b6107f283838360405180602001604052805f815250610e7c565b610a8a609754610100900460ff1660021490565b15610aa85760405163bae6e2a960e01b815260040160405180910390fd5b7f0000000000000000000000007633750000000000000000000000000000000003336001600160a01b03821614610af2576040516395383ea160e01b815260040160405180910390fd5b6002610b0060975460ff1690565b60ff1603610b215760405163dfc60d8560e01b815260040160405180910390fd5b610b2b60026115c6565b33610b3583610cd9565b6001600160a01b031614610b5c5760405163358bf3d960e01b815260040160405180910390fd5b610b65826115f5565b610b6f60016115c6565b5050565b6001600160a01b037f0000000000000000000000000763375000000000000000000000000000010097163003610bbb5760405162461bcd60e51b815260040161075190612675565b7f00000000000000000000000007633750000000000000000000000000000100976001600160a01b0316610bed611418565b6001600160a01b031614610c135760405162461bcd60e51b8152600401610751906126c1565b610c1c82611433565b610b6f8282600161143b565b5f306001600160a01b037f00000000000000000000000007633750000000000000000000000000000100971614610cc75760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610751565b505f516020612a645f395f51905f5290565b5f81815260fd60205260408120546001600160a01b0316806106235760405162461bcd60e51b8152602060048201526018602482015277115490cdcc8c4e881a5b9d985b1a59081d1bdad95b88125160421b6044820152606401610751565b5f6001600160a01b038216610da15760405162461bcd60e51b815260206004820152602960248201527f4552433732313a2061646472657373207a65726f206973206e6f7420612076616044820152683634b21037bbb732b960b91b6064820152608401610751565b506001600160a01b03165f90815260fe602052604090205490565b610dc4611694565b6109915f6116ee565b610de1609754610100900460ff1660021490565b15610dff5760405163bae6e2a960e01b815260040160405180910390fd5b610e076115a5565b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a16109913360016115be565b5f610e5d611418565b905090565b606060fc8054610638906125f0565b610b6f33838361173f565b610e86338361124b565b610ea25760405162461bcd60e51b815260040161075190612628565b610eae8484848461180d565b50505050565b61012d5461012e54606091610623916001600160a01b0390911690610ed885611840565b6118d0565b5f54610100900460ff1615808015610efb57505f54600160ff909116105b80610f145750303b158015610f1457505f5460ff166001145b610f775760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610751565b5f805460ff191660011790558015610f98575f805461ff0019166101001790555b610fa2878761191a565b610fab88611956565b61101c83838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525050604080516020601f8b0181900481028201810190925289815292508991508890819084018382808284375f920191909152506119b492505050565b61012d80546001600160a01b0319166001600160a01b03891617905561012e8690558015611083575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b6001600160a01b039182165f9081526101006020908152604080832093909416825291909152205460ff1690565b6110c3611694565b6001600160a01b0381166111285760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610751565b610904816116ee565b5f6001600160e01b031982166380ac58cd60e01b148061116157506001600160e01b03198216635b5e139f60e01b145b8061062357506301ffc9a760e01b6001600160e01b0319831614610623565b5f81815260fd60205260409020546001600160a01b03166109045760405162461bcd60e51b8152602060048201526018602482015277115490cdcc8c4e881a5b9d985b1a59081d1bdad95b88125160421b6044820152606401610751565b5f81815260ff6020526040902080546001600160a01b0319166001600160a01b038416908117909155819061121282610cd9565b6001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b5f5f61125683610cd9565b9050806001600160a01b0316846001600160a01b0316148061127d575061127d818561108d565b806112a15750836001600160a01b0316611296846106b9565b6001600160a01b0316145b949350505050565b826001600160a01b03166112bc82610cd9565b6001600160a01b0316146112e25760405162461bcd60e51b81526004016107519061270d565b6001600160a01b0382166113445760405162461bcd60e51b8152602060048201526024808201527f4552433732313a207472616e7366657220746f20746865207a65726f206164646044820152637265737360e01b6064820152608401610751565b61135183838360016119e4565b826001600160a01b031661136482610cd9565b6001600160a01b03161461138a5760405162461bcd60e51b81526004016107519061270d565b5f81815260ff6020908152604080832080546001600160a01b03199081169091556001600160a01b0387811680865260fe855283862080545f190190559087168086528386208054600101905586865260fd90945282852080549092168417909155905184937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4505050565b5f516020612a645f395f51905f52546001600160a01b031690565b610904611694565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff161561146e576107f283611a24565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156114c8575060408051601f3d908101601f191682019092526114c591810190612752565b60015b61152b5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610751565b5f516020612a645f395f51905f5281146115995760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610751565b506107f2838383611abf565b60405163a87dd7cf60e01b815260040160405180910390fd5b610b6f611694565b6097805460ff191660ff92909216919091179055565b610b6f828260405180602001604052805f815250611ae3565b5f6115ff82610cd9565b905061160e815f8460016119e4565b61161782610cd9565b5f83815260ff6020908152604080832080546001600160a01b03199081169091556001600160a01b03851680855260fe845282852080545f1901905587855260fd909352818420805490911690555192935084927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908390a45050565b6033546001600160a01b031633146109915760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610751565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b816001600160a01b0316836001600160a01b0316036117a05760405162461bcd60e51b815260206004820152601960248201527f4552433732313a20617070726f766520746f2063616c6c6572000000000000006044820152606401610751565b6001600160a01b038381165f8181526101006020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b6118188484846112a9565b61182484848484611b15565b610eae5760405162461bcd60e51b815260040161075190612769565b60605f61184c83611c12565b60010190505f8167ffffffffffffffff81111561186b5761186b61235a565b6040519080825280601f01601f191660200182016040528015611895576020820181803683370190505b5090508181016020015b5f19016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a850494508461189f57509392505050565b60606118e6846001600160a01b03166014611ce9565b6118ef84611840565b83604051602001611902939291906127bb565b60405160208183030381529060405290509392505050565b6001600160a01b038216158061192e575080155b8061193857504681145b15610b6f5760405163c118d2f360e01b815260040160405180910390fd5b5f54610100900460ff1661197c5760405162461bcd60e51b815260040161075190612843565b611984611e86565b6119a26001600160a01b0382161561199c57816116ee565b336116ee565b506097805461ff001916610100179055565b5f54610100900460ff166119da5760405162461bcd60e51b815260040161075190612843565b610b6f8282611eac565b6119f8609754610100900460ff1660021490565b15611a165760405163bae6e2a960e01b815260040160405180910390fd5b611a1f83611eeb565b610eae565b6001600160a01b0381163b611a915760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610751565b5f516020612a645f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b611ac883611f14565b5f82511180611ad45750805b156107f257610eae8383611f53565b611aed8383611f78565b611af95f848484611b15565b6107f25760405162461bcd60e51b815260040161075190612769565b5f6001600160a01b0384163b15611c0757604051630a85bd0160e11b81526001600160a01b0385169063150b7a0290611b5890339089908890889060040161288e565b6020604051808303815f875af1925050508015611b92575060408051601f3d908101601f19168201909252611b8f918101906128c0565b60015b611bed573d808015611bbf576040519150601f19603f3d011682016040523d82523d5f602084013e611bc4565b606091505b5080515f03611be55760405162461bcd60e51b815260040161075190612769565b805181602001fd5b6001600160e01b031916630a85bd0160e11b1490506112a1565b506001949350505050565b5f8072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b8310611c505772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef81000000008310611c7c576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc100008310611c9a57662386f26fc10000830492506010015b6305f5e1008310611cb2576305f5e100830492506008015b6127108310611cc657612710830492506004015b60648310611cd8576064830492506002015b600a83106106235760010192915050565b60605f611cf78360026128ef565b611d02906002612906565b67ffffffffffffffff811115611d1a57611d1a61235a565b6040519080825280601f01601f191660200182016040528015611d44576020820181803683370190505b509050600360fc1b815f81518110611d5e57611d5e612919565b60200101906001600160f81b03191690815f1a905350600f60fb1b81600181518110611d8c57611d8c612919565b60200101906001600160f81b03191690815f1a9053505f611dae8460026128ef565b611db9906001612906565b90505b6001811115611e30576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110611ded57611ded612919565b1a60f81b828281518110611e0357611e03612919565b60200101906001600160f81b03191690815f1a90535060049490941c93611e298161292d565b9050611dbc565b508315611e7f5760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610751565b9392505050565b5f54610100900460ff166109915760405162461bcd60e51b815260040161075190612843565b5f54610100900460ff16611ed25760405162461bcd60e51b815260040161075190612843565b60fb611ede838261298d565b5060fc6107f2828261298d565b306001600160a01b0382160361090457604051630c292c9d60e21b815260040160405180910390fd5b611f1d81611a24565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060611e7f8383604051806060016040528060278152602001612a846027913961210d565b6001600160a01b038216611fce5760405162461bcd60e51b815260206004820181905260248201527f4552433732313a206d696e7420746f20746865207a65726f20616464726573736044820152606401610751565b5f81815260fd60205260409020546001600160a01b0316156120325760405162461bcd60e51b815260206004820152601c60248201527f4552433732313a20746f6b656e20616c7265616479206d696e746564000000006044820152606401610751565b61203f5f838360016119e4565b5f81815260fd60205260409020546001600160a01b0316156120a35760405162461bcd60e51b815260206004820152601c60248201527f4552433732313a20746f6b656e20616c7265616479206d696e746564000000006044820152606401610751565b6001600160a01b0382165f81815260fe602090815260408083208054600101905584835260fd90915280822080546001600160a01b0319168417905551839291907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a45050565b60605f5f856001600160a01b0316856040516121299190612a48565b5f60405180830381855af49150503d805f8114612161576040519150601f19603f3d011682016040523d82523d5f602084013e612166565b606091505b509150915061217786838387612181565b9695505050505050565b606083156121ef5782515f036121e8576001600160a01b0385163b6121e85760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610751565b50816112a1565b6112a183838151156122045781518083602001fd5b8060405162461bcd60e51b8152600401610751919061229b565b6001600160e01b031981168114610904575f5ffd5b5f60208284031215612243575f5ffd5b8135611e7f8161221e565b5f5b83811015612268578181015183820152602001612250565b50505f910152565b5f815180845261228781602086016020860161224e565b601f01601f19169290920160200192915050565b602081525f611e7f6020830184612270565b5f602082840312156122bd575f5ffd5b5035919050565b80356001600160a01b03811681146122da575f5ffd5b919050565b5f5f604083850312156122f0575f5ffd5b6122f9836122c4565b946020939093013593505050565b5f5f5f60608486031215612319575f5ffd5b612322846122c4565b9250612330602085016122c4565b929592945050506040919091013590565b5f60208284031215612351575f5ffd5b611e7f826122c4565b634e487b7160e01b5f52604160045260245ffd5b5f82601f83011261237d575f5ffd5b813567ffffffffffffffff8111156123975761239761235a565b604051601f8201601f19908116603f0116810167ffffffffffffffff811182821017156123c6576123c661235a565b6040528181528382016020018510156123dd575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f5f6040838503121561240a575f5ffd5b612413836122c4565b9150602083013567ffffffffffffffff81111561242e575f5ffd5b61243a8582860161236e565b9150509250929050565b5f5f60408385031215612455575f5ffd5b61245e836122c4565b915060208301358015158114612472575f5ffd5b809150509250929050565b5f5f5f5f60808587031215612490575f5ffd5b612499856122c4565b93506124a7602086016122c4565b925060408501359150606085013567ffffffffffffffff8111156124c9575f5ffd5b6124d58782880161236e565b91505092959194509250565b5f5f83601f8401126124f1575f5ffd5b50813567ffffffffffffffff811115612508575f5ffd5b60208301915083602082850101111561251f575f5ffd5b9250929050565b5f5f5f5f5f5f5f60a0888a03121561253c575f5ffd5b612545886122c4565b9650612553602089016122c4565b955060408801359450606088013567ffffffffffffffff811115612575575f5ffd5b6125818a828b016124e1565b909550935050608088013567ffffffffffffffff8111156125a0575f5ffd5b6125ac8a828b016124e1565b989b979a50959850939692959293505050565b5f5f604083850312156125d0575f5ffd5b6125d9836122c4565b91506125e7602084016122c4565b90509250929050565b600181811c9082168061260457607f821691505b60208210810361262257634e487b7160e01b5f52602260045260245ffd5b50919050565b6020808252602d908201527f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560408201526c1c881bdc88185c1c1c9bdd9959609a1b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b60208082526025908201527f4552433732313a207472616e736665722066726f6d20696e636f72726563742060408201526437bbb732b960d91b606082015260800190565b5f60208284031215612762575f5ffd5b5051919050565b60208082526032908201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560408201527131b2b4bb32b91034b6b83632b6b2b73a32b960711b606082015260800190565b6832ba3432b932bab69d60b91b81525f84516127de81600985016020890161224e565b600160fe1b60099184019182015284516127ff81600a84016020890161224e565b600981830101915050712f746f6b656e5552493f75696e743235363d60701b6001820152835161283681601384016020880161224e565b0160130195945050505050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6001600160a01b03858116825284166020820152604081018390526080606082018190525f9061217790830184612270565b5f602082840312156128d0575f5ffd5b8151611e7f8161221e565b634e487b7160e01b5f52601160045260245ffd5b8082028115828204841417610623576106236128db565b80820180821115610623576106236128db565b634e487b7160e01b5f52603260045260245ffd5b5f8161293b5761293b6128db565b505f190190565b601f8211156107f257805f5260205f20601f840160051c810160208510156129675750805b601f840160051c820191505b81811015612986575f8155600101612973565b5050505050565b815167ffffffffffffffff8111156129a7576129a761235a565b6129bb816129b584546125f0565b84612942565b6020601f8211600181146129ed575f83156129d65750848201515b5f19600385901b1c1916600184901b178455612986565b5f84815260208120601f198516915b82811015612a1c57878501518255602094850194600190920191016129fc565b5084821015612a3957868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b5f8251612a5981846020870161224e565b919091019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220953b42878992a5e137fd27a944b0193d7c4624dc117ef8acf63b20993b831dec64736f6c634300081b0033", "balance": "0x0" }, "0x0763375000000000000000000000000000010098": { "contractName": "BridgedERC1155", "storage": {}, - "code": "0x6080604052600436106101cf575f3560e01c80635c975abb116100fd578063a86f9d9e11610092578063e985e9c511610062578063e985e9c514610545578063ef8c4ae61461058c578063f242432a146105ab578063f2fde38b146105ca575f5ffd5b8063a86f9d9e146104ab578063b390c0ab146104ca578063d81d0a15146104e9578063e07baba614610508575f5ffd5b80638abf6077116100cd5780638abf6077146104475780638da5cb5b1461045b57806395d89b4114610478578063a22cb4651461048c575f5ffd5b80635c975abb146103df578063715018a6146103ff5780637cf8ed0d146104135780638456cb5914610433575f5ffd5b80633659cfe61161017357806349d126051161014357806349d12605146103765780634e1273f41461038c5780634f1ef286146103b857806352d1902d146103cb575f5ffd5b80633659cfe6146102ed5780633ab76e9f1461030c5780633eb6b8cf146103435780633f4ba83a14610362575f5ffd5b80630e89341c116101ae5780630e89341c1461025557806326afaadd146102745780632eb2c2d6146102b85780633075db56146102d9575f5ffd5b8062fdd58e146101d357806301ffc9a71461020557806306fdde0314610234575b5f5ffd5b3480156101de575f5ffd5b506101f26101ed3660046125f1565b6105e9565b6040519081526020015b60405180910390f35b348015610210575f5ffd5b5061022461021f366004612630565b610682565b60405190151581526020016101fc565b34801561023f575f5ffd5b506102486106c1565b6040516101fc9190612698565b348015610260575f5ffd5b5061024861026f3660046126aa565b61074e565b34801561027f575f5ffd5b5061029961012d5461012e546001600160a01b0390911691565b604080516001600160a01b0390931683526020830191909152016101fc565b3480156102c3575f5ffd5b506102d76102d2366004612803565b6107e0565b005b3480156102e4575f5ffd5b5061022461082c565b3480156102f8575f5ffd5b506102d76103073660046128b3565b610844565b348015610317575f5ffd5b5060655461032b906001600160a01b031681565b6040516001600160a01b0390911681526020016101fc565b34801561034e575f5ffd5b5061032b61035d3660046128e2565b61090b565b34801561036d575f5ffd5b506102d7610921565b348015610381575f5ffd5b506101f261012e5481565b348015610397575f5ffd5b506103ab6103a6366004612928565b610935565b6040516101fc9190612a31565b6102d76103c6366004612a43565b610a54565b3480156103d6575f5ffd5b506101f2610b0d565b3480156103ea575f5ffd5b50610224609754610100900460ff1660021490565b34801561040a575f5ffd5b506102d7610bbf565b34801561041e575f5ffd5b5061012d5461032b906001600160a01b031681565b34801561043e575f5ffd5b506102d7610bd0565b348015610452575f5ffd5b5061032b610beb565b348015610466575f5ffd5b506033546001600160a01b031661032b565b348015610483575f5ffd5b50610248610bf9565b348015610497575f5ffd5b506102d76104a6366004612a85565b610c07565b3480156104b6575f5ffd5b5061032b6104c5366004612ab8565b610c12565b3480156104d5575f5ffd5b506102d76104e4366004612ad9565b610c1e565b3480156104f4575f5ffd5b506102d7610503366004612b40565b610cf0565b348015610513575f5ffd5b5060975461052d906201000090046001600160401b031681565b6040516001600160401b0390911681526020016101fc565b348015610550575f5ffd5b5061022461055f366004612bc0565b6001600160a01b039182165f90815260fc6020908152604080832093909416825291909152205460ff1690565b348015610597575f5ffd5b506102d76105a6366004612c34565b610e34565b3480156105b6575f5ffd5b506102d76105c5366004612ce1565b610fb4565b3480156105d5575f5ffd5b506102d76105e43660046128b3565b610ff9565b5f6001600160a01b0383166106585760405162461bcd60e51b815260206004820152602a60248201527f455243313135353a2061646472657373207a65726f206973206e6f742061207660448201526930b634b21037bbb732b960b11b60648201526084015b60405180910390fd5b505f81815260fb602090815260408083206001600160a01b03861684529091529020545b92915050565b5f6001600160e01b03198216634d22606360e01b14806106b257506001600160e01b031982166377c6257360e11b145b8061067c575061067c8261106f565b61013080546106cf90612d38565b80601f01602080910402602001604051908101604052809291908181526020018280546106fb90612d38565b80156107465780601f1061071d57610100808354040283529160200191610746565b820191905f5260205f20905b81548152906001019060200180831161072957829003601f168201915b505050505081565b606060fd805461075d90612d38565b80601f016020809104026020016040519081016040528092919081815260200182805461078990612d38565b80156107d45780601f106107ab576101008083540402835291602001916107d4565b820191905f5260205f20905b8154815290600101906020018083116107b757829003601f168201915b50505050509050919050565b6001600160a01b0385163314806107fc57506107fc853361055f565b6108185760405162461bcd60e51b815260040161064f90612d70565b61082585858585856110be565b5050505050565b5f600261083b60975460ff1690565b60ff1614905090565b6001600160a01b037f000000000000000000000000076337500000000000000000000000000001009816300361088c5760405162461bcd60e51b815260040161064f90612dbe565b7f00000000000000000000000007633750000000000000000000000000000100986001600160a01b03166108be611257565b6001600160a01b0316146108e45760405162461bcd60e51b815260040161064f90612e0a565b6108ed81611272565b604080515f808252602082019092526109089183919061127a565b50565b5f6109178484846113e4565b90505b9392505050565b610929611436565b610933335f6114c6565b565b6060815183511461099a5760405162461bcd60e51b815260206004820152602960248201527f455243313135353a206163636f756e747320616e6420696473206c656e677468604482015268040dad2e6dac2e8c6d60bb1b606482015260840161064f565b5f83516001600160401b038111156109b4576109b46126c1565b6040519080825280602002602001820160405280156109dd578160200160208202803683370190505b5090505f5b8451811015610a4c57610a27858281518110610a0057610a00612e56565b6020026020010151858381518110610a1a57610a1a612e56565b60200260200101516105e9565b828281518110610a3957610a39612e56565b60209081029190910101526001016109e2565b509392505050565b6001600160a01b037f0000000000000000000000000763375000000000000000000000000000010098163003610a9c5760405162461bcd60e51b815260040161064f90612dbe565b7f00000000000000000000000007633750000000000000000000000000000100986001600160a01b0316610ace611257565b6001600160a01b031614610af45760405162461bcd60e51b815260040161064f90612e0a565b610afd82611272565b610b098282600161127a565b5050565b5f306001600160a01b037f00000000000000000000000007633750000000000000000000000000000100981614610bac5760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c0000000000000000606482015260840161064f565b505f51602061342f5f395f51905f525b90565b610bc76114ca565b6109335f611524565b610bd8611575565b610be061158e565b6109333360016114c6565b5f610bf4611257565b905090565b61012f80546106cf90612d38565b610b093383836115ff565b5f61091a4684846113e4565b610c32609754610100900460ff1660021490565b15610c505760405163bae6e2a960e01b815260040160405180910390fd5b6c195c98cc4c4d4d57dd985d5b1d609a1b610c6c816001610c12565b6001600160a01b0316336001600160a01b031614610c9d57604051630d85cccf60e11b815260040160405180910390fd5b6002610cab60975460ff1690565b60ff1603610ccc5760405163dfc60d8560e01b815260040160405180910390fd5b610cd660026116de565b610ce13384846116f4565b610ceb60016116de565b505050565b610d04609754610100900460ff1660021490565b15610d225760405163bae6e2a960e01b815260040160405180910390fd5b6c195c98cc4c4d4d57dd985d5b1d609a1b610d3e816001610c12565b6001600160a01b0316336001600160a01b031614610d6f57604051630d85cccf60e11b815260040160405180910390fd5b6002610d7d60975460ff1690565b60ff1603610d9e5760405163dfc60d8560e01b815260040160405180910390fd5b610da860026116de565b610e22868686808060200260200160405190810160405280939291908181526020018383602002808284375f9201919091525050604080516020808a028281018201909352898252909350899250889182918501908490808284375f92018290525060408051602081019091529081529250611886915050565b610e2c60016116de565b505050505050565b5f54610100900460ff1615808015610e5257505f54600160ff909116105b80610e6b5750303b158015610e6b57505f5460ff166001145b610ece5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161064f565b5f805460ff191660011790558015610eef575f805461ff0019166101001790555b610ef98787611a07565b610f038989611a43565b610f24610f1f888860405180602001604052805f815250611a7d565b611ac7565b61012d80546001600160a01b0319166001600160a01b03891617905561012e86905561012f610f54858783612eae565b50610130610f63838583612eae565b508015610fa9575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050505050565b6001600160a01b038516331480610fd05750610fd0853361055f565b610fec5760405162461bcd60e51b815260040161064f90612d70565b6108258585858585611af6565b6110016114ca565b6001600160a01b0381166110665760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b606482015260840161064f565b61090881611524565b5f6001600160e01b03198216636cdb3d1360e11b148061109f57506001600160e01b031982166303a24d0760e21b145b8061067c57506301ffc9a760e01b6001600160e01b031983161461067c565b81518351146110df5760405162461bcd60e51b815260040161064f90612f67565b6001600160a01b0384166111055760405162461bcd60e51b815260040161064f90612faf565b33611114818787878787611c23565b5f5b84518110156111f1575f85828151811061113257611132612e56565b602002602001015190505f85838151811061114f5761114f612e56565b6020908102919091018101515f84815260fb835260408082206001600160a01b038e16835290935291909120549091508181101561119f5760405162461bcd60e51b815260040161064f90612ff4565b5f83815260fb602090815260408083206001600160a01b038e8116855292528083208585039055908b168252812080548492906111dd908490613052565b909155505060019093019250611116915050565b50846001600160a01b0316866001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8787604051611241929190613065565b60405180910390a4610e2c818787878787611c63565b5f51602061342f5f395f51905f52546001600160a01b031690565b6109086114ca565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156112ad57610ceb83611dbd565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611307575060408051601f3d908101601f1916820190925261130491810190613092565b60015b61136a5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b606482015260840161064f565b5f51602061342f5f395f51905f5281146113d85760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b606482015260840161064f565b50610ceb838383611e58565b5f6113ef8484611e82565b90508115801561140657506001600160a01b038116155b1561091a57604051632b0d65db60e01b81526001600160401b03851660048201526024810184905260440161064f565b61144a609754610100900460ff1660021490565b6114675760405163bae6e2a960e01b815260040160405180910390fd5b6097805461010069ffffffffffffffffff001990911662010000426001600160401b031602171790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa906020015b60405180910390a1565b610b095b6033546001600160a01b031633146109335760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161064f565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b60405163a87dd7cf60e01b815260040160405180910390fd5b6115a2609754610100900460ff1660021490565b156115c05760405163bae6e2a960e01b815260040160405180910390fd5b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258906020016114bc565b816001600160a01b0316836001600160a01b0316036116725760405162461bcd60e51b815260206004820152602960248201527f455243313135353a2073657474696e6720617070726f76616c20737461747573604482015268103337b91039b2b63360b91b606482015260840161064f565b6001600160a01b038381165f81815260fc6020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b6097805460ff191660ff92909216919091179055565b6001600160a01b0383166117565760405162461bcd60e51b815260206004820152602360248201527f455243313135353a206275726e2066726f6d20746865207a65726f206164647260448201526265737360e81b606482015260840161064f565b335f61176184611f2c565b90505f61176d84611f2c565b905061178b83875f858560405180602001604052805f815250611c23565b5f85815260fb602090815260408083206001600160a01b038a168452909152902054848110156118095760405162461bcd60e51b8152602060048201526024808201527f455243313135353a206275726e20616d6f756e7420657863656564732062616c604482015263616e636560e01b606482015260840161064f565b5f86815260fb602090815260408083206001600160a01b038b81168086529184528285208a8703905582518b81529384018a90529092908816917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a460408051602081019091525f90525b50505050505050565b6001600160a01b0384166118e65760405162461bcd60e51b815260206004820152602160248201527f455243313135353a206d696e7420746f20746865207a65726f206164647265736044820152607360f81b606482015260840161064f565b81518351146119075760405162461bcd60e51b815260040161064f90612f67565b33611916815f87878787611c23565b5f5b84518110156119a15783818151811061193357611933612e56565b602002602001015160fb5f87848151811061195057611950612e56565b602002602001015181526020019081526020015f205f886001600160a01b03166001600160a01b031681526020019081526020015f205f8282546119949190613052565b9091555050600101611918565b50846001600160a01b03165f6001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb87876040516119f1929190613065565b60405180910390a4610825815f87878787611c63565b6001600160a01b0382161580611a1b575080155b80611a2557504681145b15610b095760405163c118d2f360e01b815260040160405180910390fd5b806001600160a01b038116611a6b5760405163538ba4f960e01b815260040160405180910390fd5b611a7483611f75565b610ceb82611fd3565b6060611a93846001600160a01b03166014612043565b611a9c846121d8565b83604051602001611aaf939291906130a9565b60405160208183030381529060405290509392505050565b5f54610100900460ff16611aed5760405162461bcd60e51b815260040161064f90613131565b61090881612267565b6001600160a01b038416611b1c5760405162461bcd60e51b815260040161064f90612faf565b335f611b2785611f2c565b90505f611b3385611f2c565b9050611b43838989858589611c23565b5f86815260fb602090815260408083206001600160a01b038c16845290915290205485811015611b855760405162461bcd60e51b815260040161064f90612ff4565b5f87815260fb602090815260408083206001600160a01b038d8116855292528083208985039055908a16825281208054889290611bc3908490613052565b909155505060408051888152602081018890526001600160a01b03808b16928c821692918816917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a4610fa9848a8a8a8a8a612296565b611c37609754610100900460ff1660021490565b15611c555760405163bae6e2a960e01b815260040160405180910390fd5b611c5e84612350565b610e2c565b6001600160a01b0384163b15610e2c5760405163bc197c8160e01b81526001600160a01b0385169063bc197c8190611ca7908990899088908890889060040161317c565b6020604051808303815f875af1925050508015611ce1575060408051601f3d908101601f19168201909252611cde918101906131d9565b60015b611d8d57611ced6131f4565b806308c379a003611d265750611d0161320c565b80611d0c5750611d28565b8060405162461bcd60e51b815260040161064f9190612698565b505b60405162461bcd60e51b815260206004820152603460248201527f455243313135353a207472616e7366657220746f206e6f6e2d455243313135356044820152732932b1b2b4bb32b91034b6b83632b6b2b73a32b960611b606482015260840161064f565b6001600160e01b0319811663bc197c8160e01b1461187d5760405162461bcd60e51b815260040161064f90613286565b6001600160a01b0381163b611e2a5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161064f565b5f51602061342f5f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b611e6183612379565b5f82511180611e6d5750805b15610ceb57611e7c83836123b8565b50505050565b6065545f906001600160a01b031680611eae57604051638ed88b2560e01b815260040160405180910390fd5b604051630a3dc4f360e21b81526001600160401b0385166004820152602481018490526001600160a01b038216906328f713cc90604401602060405180830381865afa158015611f00573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611f2491906132ce565b949350505050565b6040805160018082528183019092526060915f91906020808301908036833701905050905082815f81518110611f6457611f64612e56565b602090810291909101015292915050565b5f54610100900460ff16611f9b5760405162461bcd60e51b815260040161064f90613131565b611fa36123dd565b611fc16001600160a01b03821615611fbb5781611524565b33611524565b506097805461ff001916610100179055565b5f54610100900460ff16611ff95760405162461bcd60e51b815260040161064f90613131565b6001600160401b034611156120215760405163a12e8fa960e01b815260040160405180910390fd5b606580546001600160a01b0319166001600160a01b0392909216919091179055565b60605f6120518360026132e9565b61205c906002613052565b6001600160401b03811115612073576120736126c1565b6040519080825280601f01601f19166020018201604052801561209d576020820181803683370190505b509050600360fc1b815f815181106120b7576120b7612e56565b60200101906001600160f81b03191690815f1a905350600f60fb1b816001815181106120e5576120e5612e56565b60200101906001600160f81b03191690815f1a9053505f6121078460026132e9565b612112906001613052565b90505b6001811115612189576f181899199a1a9b1b9c1cb0b131b232b360811b85600f166010811061214657612146612e56565b1a60f81b82828151811061215c5761215c612e56565b60200101906001600160f81b03191690815f1a90535060049490941c9361218281613300565b9050612115565b50831561091a5760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e74604482015260640161064f565b60605f6121e483612403565b60010190505f816001600160401b03811115612202576122026126c1565b6040519080825280601f01601f19166020018201604052801561222c576020820181803683370190505b5090508181016020015b5f19016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a850494508461223657509392505050565b5f54610100900460ff1661228d5760405162461bcd60e51b815260040161064f90613131565b610908816124da565b6001600160a01b0384163b15610e2c5760405163f23a6e6160e01b81526001600160a01b0385169063f23a6e61906122da9089908990889088908890600401613315565b6020604051808303815f875af1925050508015612314575060408051601f3d908101601f19168201909252612311918101906131d9565b60015b61232057611ced6131f4565b6001600160e01b0319811663f23a6e6160e01b1461187d5760405162461bcd60e51b815260040161064f90613286565b306001600160a01b0382160361090857604051630c292c9d60e21b815260040160405180910390fd5b61238281611dbd565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606061091a838360405180606001604052806027815260200161344f602791396124e6565b5f54610100900460ff166109335760405162461bcd60e51b815260040161064f90613131565b5f8072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b83106124415772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef8100000000831061246d576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc10000831061248b57662386f26fc10000830492506010015b6305f5e10083106124a3576305f5e100830492506008015b61271083106124b757612710830492506004015b606483106124c9576064830492506002015b600a831061067c5760010192915050565b60fd610b098282613359565b60605f5f856001600160a01b0316856040516125029190613413565b5f60405180830381855af49150503d805f811461253a576040519150601f19603f3d011682016040523d82523d5f602084013e61253f565b606091505b50915091506125508683838761255a565b9695505050505050565b606083156125c85782515f036125c1576001600160a01b0385163b6125c15760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161064f565b5081611f24565b611f248383815115611d0c5781518083602001fd5b6001600160a01b0381168114610908575f5ffd5b5f5f60408385031215612602575f5ffd5b823561260d816125dd565b946020939093013593505050565b6001600160e01b031981168114610908575f5ffd5b5f60208284031215612640575f5ffd5b813561091a8161261b565b5f5b8381101561266557818101518382015260200161264d565b50505f910152565b5f815180845261268481602086016020860161264b565b601f01601f19169290920160200192915050565b602081525f61091a602083018461266d565b5f602082840312156126ba575f5ffd5b5035919050565b634e487b7160e01b5f52604160045260245ffd5b601f8201601f191681016001600160401b03811182821017156126fa576126fa6126c1565b6040525050565b5f6001600160401b03821115612719576127196126c1565b5060051b60200190565b5f82601f830112612732575f5ffd5b813561273d81612701565b60405161274a82826126d5565b80915082815260208101915060208360051b86010192508583111561276d575f5ffd5b602085015b8381101561278a578035835260209283019201612772565b5095945050505050565b5f82601f8301126127a3575f5ffd5b81356001600160401b038111156127bc576127bc6126c1565b6040516127d3601f8301601f1916602001826126d5565b8181528460208386010111156127e7575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f5f5f5f5f60a08688031215612817575f5ffd5b8535612822816125dd565b94506020860135612832816125dd565b935060408601356001600160401b0381111561284c575f5ffd5b61285888828901612723565b93505060608601356001600160401b03811115612873575f5ffd5b61287f88828901612723565b92505060808601356001600160401b0381111561289a575f5ffd5b6128a688828901612794565b9150509295509295909350565b5f602082840312156128c3575f5ffd5b813561091a816125dd565b803580151581146128dd575f5ffd5b919050565b5f5f5f606084860312156128f4575f5ffd5b83356001600160401b038116811461290a575f5ffd5b92506020840135915061291f604085016128ce565b90509250925092565b5f5f60408385031215612939575f5ffd5b82356001600160401b0381111561294e575f5ffd5b8301601f8101851361295e575f5ffd5b803561296981612701565b60405161297682826126d5565b80915082815260208101915060208360051b850101925087831115612999575f5ffd5b6020840193505b828410156129c45783356129b3816125dd565b8252602093840193909101906129a0565b945050505060208301356001600160401b038111156129e1575f5ffd5b6129ed85828601612723565b9150509250929050565b5f8151808452602084019350602083015f5b82811015612a27578151865260209586019590910190600101612a09565b5093949350505050565b602081525f61091a60208301846129f7565b5f5f60408385031215612a54575f5ffd5b8235612a5f816125dd565b915060208301356001600160401b03811115612a79575f5ffd5b6129ed85828601612794565b5f5f60408385031215612a96575f5ffd5b8235612aa1816125dd565b9150612aaf602084016128ce565b90509250929050565b5f5f60408385031215612ac9575f5ffd5b82359150612aaf602084016128ce565b5f5f60408385031215612aea575f5ffd5b50508035926020909101359150565b5f5f83601f840112612b09575f5ffd5b5081356001600160401b03811115612b1f575f5ffd5b6020830191508360208260051b8501011115612b39575f5ffd5b9250929050565b5f5f5f5f5f60608688031215612b54575f5ffd5b8535612b5f816125dd565b945060208601356001600160401b03811115612b79575f5ffd5b612b8588828901612af9565b90955093505060408601356001600160401b03811115612ba3575f5ffd5b612baf88828901612af9565b969995985093965092949392505050565b5f5f60408385031215612bd1575f5ffd5b8235612bdc816125dd565b91506020830135612bec816125dd565b809150509250929050565b5f5f83601f840112612c07575f5ffd5b5081356001600160401b03811115612c1d575f5ffd5b602083019150836020828501011115612b39575f5ffd5b5f5f5f5f5f5f5f5f60c0898b031215612c4b575f5ffd5b8835612c56816125dd565b97506020890135612c66816125dd565b96506040890135612c76816125dd565b95506060890135945060808901356001600160401b03811115612c97575f5ffd5b612ca38b828c01612bf7565b90955093505060a08901356001600160401b03811115612cc1575f5ffd5b612ccd8b828c01612bf7565b999c989b5096995094979396929594505050565b5f5f5f5f5f60a08688031215612cf5575f5ffd5b8535612d00816125dd565b94506020860135612d10816125dd565b9350604086013592506060860135915060808601356001600160401b0381111561289a575f5ffd5b600181811c90821680612d4c57607f821691505b602082108103612d6a57634e487b7160e01b5f52602260045260245ffd5b50919050565b6020808252602e908201527f455243313135353a2063616c6c6572206973206e6f7420746f6b656e206f776e60408201526d195c881bdc88185c1c1c9bdd995960921b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b634e487b7160e01b5f52603260045260245ffd5b601f821115610ceb57805f5260205f20601f840160051c81016020851015612e8f5750805b601f840160051c820191505b81811015610825575f8155600101612e9b565b6001600160401b03831115612ec557612ec56126c1565b612ed983612ed38354612d38565b83612e6a565b5f601f841160018114612f0a575f8515612ef35750838201355b5f19600387901b1c1916600186901b178355610825565b5f83815260208120601f198716915b82811015612f395786850135825560209485019460019092019101612f19565b5086821015612f55575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b60208082526028908201527f455243313135353a2069647320616e6420616d6f756e7473206c656e677468206040820152670dad2e6dac2e8c6d60c31b606082015260800190565b60208082526025908201527f455243313135353a207472616e7366657220746f20746865207a65726f206164604082015264647265737360d81b606082015260800190565b6020808252602a908201527f455243313135353a20696e73756666696369656e742062616c616e636520666f60408201526939103a3930b739b332b960b11b606082015260800190565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561067c5761067c61303e565b604081525f61307760408301856129f7565b828103602084015261308981856129f7565b95945050505050565b5f602082840312156130a2575f5ffd5b5051919050565b6832ba3432b932bab69d60b91b81525f84516130cc81600985016020890161264b565b600160fe1b60099184019182015284516130ed81600a84016020890161264b565b600981830101915050712f746f6b656e5552493f75696e743235363d60701b6001820152835161312481601384016020880161264b565b0160130195945050505050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6001600160a01b0386811682528516602082015260a0604082018190525f906131a7908301866129f7565b82810360608401526131b981866129f7565b905082810360808401526131cd818561266d565b98975050505050505050565b5f602082840312156131e9575f5ffd5b815161091a8161261b565b5f60033d1115610bbc5760045f5f3e505f5160e01c90565b5f60443d10156132195790565b6040513d600319016004823e80513d60248201116001600160401b038211171561324257505090565b80820180516001600160401b0381111561325d575050505090565b3d8401600319018282016020011115613277575050505090565b610a4c602082850101856126d5565b60208082526028908201527f455243313135353a204552433131353552656365697665722072656a656374656040820152676420746f6b656e7360c01b606082015260800190565b5f602082840312156132de575f5ffd5b815161091a816125dd565b808202811582820484141761067c5761067c61303e565b5f8161330e5761330e61303e565b505f190190565b6001600160a01b03868116825285166020820152604081018490526060810183905260a0608082018190525f9061334e9083018461266d565b979650505050505050565b81516001600160401b03811115613372576133726126c1565b613386816133808454612d38565b84612e6a565b6020601f8211600181146133b8575f83156133a15750848201515b5f19600385901b1c1916600184901b178455610825565b5f84815260208120601f198516915b828110156133e757878501518255602094850194600190920191016133c7565b508482101561340457868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b5f825161342481846020870161264b565b919091019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220f5b748fd673d492547e0301c2704643d95dd9cb904c4bb3d1beba024cb52626164736f6c634300081b0033", + "code": "0x6080604052600436106101b9575f3560e01c80635c975abb116100f257806395d89b4111610092578063d81d0a1511610062578063d81d0a15146104f6578063e985e9c514610515578063f242432a1461055c578063f2fde38b1461057b575f5ffd5b806395d89b4114610485578063a22cb46514610499578063b390c0ab146104b8578063d1399b1a146104d7575f5ffd5b80637cf8ed0d116100cd5780637cf8ed0d146104205780638456cb59146104405780638abf6077146104545780638da5cb5b14610468575f5ffd5b80635c975abb146103b9578063715018a6146103d957806379275a77146103ed575f5ffd5b80633075db561161015d57806349d126051161013857806349d12605146103505780634e1273f4146103665780634f1ef2861461039257806352d1902d146103a5575f5ffd5b80633075db56146103095780633659cfe61461031d5780633f4ba83a1461033c575f5ffd5b806306fdde031161019857806306fdde03146102645780630e89341c1461028557806326afaadd146102a45780632eb2c2d6146102e8575f5ffd5b8062fdd58e146101bd57806301ffc9a7146101ef57806304f3bcec1461021e575b5f5ffd5b3480156101c8575f5ffd5b506101dc6101d73660046123db565b61059a565b6040519081526020015b60405180910390f35b3480156101fa575f5ffd5b5061020e610209366004612418565b610633565b60405190151581526020016101e6565b348015610229575f5ffd5b507f00000000000000000000000000000000000000000000000000000000000000005b6040516001600160a01b0390911681526020016101e6565b34801561026f575f5ffd5b50610278610672565b6040516101e69190612480565b348015610290575f5ffd5b5061027861029f366004612492565b6106ff565b3480156102af575f5ffd5b506102c961012d5461012e546001600160a01b0390911691565b604080516001600160a01b0390931683526020830191909152016101e6565b3480156102f3575f5ffd5b506103076103023660046125eb565b610791565b005b348015610314575f5ffd5b5061020e6107dd565b348015610328575f5ffd5b50610307610337366004612697565b6107f5565b348015610347575f5ffd5b506103076108bc565b34801561035b575f5ffd5b506101dc61012e5481565b348015610371575f5ffd5b506103856103803660046126b0565b610948565b6040516101e691906127b7565b6103076103a03660046127c9565b610a67565b3480156103b0575f5ffd5b506101dc610b20565b3480156103c4575f5ffd5b5061020e609754610100900460ff1660021490565b3480156103e4575f5ffd5b50610307610bd2565b3480156103f8575f5ffd5b5061024c7f000000000000000000000000763375000000000000000000000000000000000481565b34801561042b575f5ffd5b5061012d5461024c906001600160a01b031681565b34801561044b575f5ffd5b50610307610be3565b34801561045f575f5ffd5b5061024c610c6a565b348015610473575f5ffd5b506033546001600160a01b031661024c565b348015610490575f5ffd5b50610278610c78565b3480156104a4575f5ffd5b506103076104b3366004612809565b610c86565b3480156104c3575f5ffd5b506103076104d2366004612842565b610c91565b3480156104e2575f5ffd5b506103076104f13660046128a6565b610d60565b348015610501575f5ffd5b5061030761051036600461297d565b610ede565b348015610520575f5ffd5b5061020e61052f3660046129fb565b6001600160a01b039182165f90815260fc6020908152604080832093909416825291909152205460ff1690565b348015610567575f5ffd5b50610307610576366004612a2c565b61101f565b348015610586575f5ffd5b50610307610595366004612697565b611064565b5f6001600160a01b0383166106095760405162461bcd60e51b815260206004820152602a60248201527f455243313135353a2061646472657373207a65726f206973206e6f742061207660448201526930b634b21037bbb732b960b11b60648201526084015b60405180910390fd5b505f81815260fb602090815260408083206001600160a01b03861684529091529020545b92915050565b5f6001600160e01b03198216634d22606360e01b148061066357506001600160e01b0319821663689ccd8d60e11b145b8061062d575061062d826110da565b610130805461068090612a7f565b80601f01602080910402602001604051908101604052809291908181526020018280546106ac90612a7f565b80156106f75780601f106106ce576101008083540402835291602001916106f7565b820191905f5260205f20905b8154815290600101906020018083116106da57829003601f168201915b505050505081565b606060fd805461070e90612a7f565b80601f016020809104026020016040519081016040528092919081815260200182805461073a90612a7f565b80156107855780601f1061075c57610100808354040283529160200191610785565b820191905f5260205f20905b81548152906001019060200180831161076857829003601f168201915b50505050509050919050565b6001600160a01b0385163314806107ad57506107ad853361052f565b6107c95760405162461bcd60e51b815260040161060090612ab7565b6107d68585858585611129565b5050505050565b5f60026107ec60975460ff1690565b60ff1614905090565b6001600160a01b037f000000000000000000000000076337500000000000000000000000000001009816300361083d5760405162461bcd60e51b815260040161060090612b05565b7f00000000000000000000000007633750000000000000000000000000000100986001600160a01b031661086f6112c2565b6001600160a01b0316146108955760405162461bcd60e51b815260040161060090612b51565b61089e816112dd565b604080515f808252602082019092526108b9918391906112e5565b50565b6108d0609754610100900460ff1660021490565b6108ed5760405163bae6e2a960e01b815260040160405180910390fd5b6108f561144f565b6109096097805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610946335f611468565b565b606081518351146109ad5760405162461bcd60e51b815260206004820152602960248201527f455243313135353a206163636f756e747320616e6420696473206c656e677468604482015268040dad2e6dac2e8c6d60bb1b6064820152608401610600565b5f83516001600160401b038111156109c7576109c76124a9565b6040519080825280602002602001820160405280156109f0578160200160208202803683370190505b5090505f5b8451811015610a5f57610a3a858281518110610a1357610a13612b9d565b6020026020010151858381518110610a2d57610a2d612b9d565b602002602001015161059a565b828281518110610a4c57610a4c612b9d565b60209081029190910101526001016109f5565b509392505050565b6001600160a01b037f0000000000000000000000000763375000000000000000000000000000010098163003610aaf5760405162461bcd60e51b815260040161060090612b05565b7f00000000000000000000000007633750000000000000000000000000000100986001600160a01b0316610ae16112c2565b6001600160a01b031614610b075760405162461bcd60e51b815260040161060090612b51565b610b10826112dd565b610b1c828260016112e5565b5050565b5f306001600160a01b037f00000000000000000000000007633750000000000000000000000000000100981614610bbf5760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610600565b505f51602061315b5f395f51905f525b90565b610bda61146c565b6109465f6114c6565b610bf7609754610100900460ff1660021490565b15610c155760405163bae6e2a960e01b815260040160405180910390fd5b610c1d61144f565b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a1610946336001611468565b5f610c736112c2565b905090565b61012f805461068090612a7f565b610b1c338383611517565b610ca5609754610100900460ff1660021490565b15610cc35760405163bae6e2a960e01b815260040160405180910390fd5b7f0000000000000000000000007633750000000000000000000000000000000004336001600160a01b03821614610d0d576040516395383ea160e01b815260040160405180910390fd5b6002610d1b60975460ff1690565b60ff1603610d3c5760405163dfc60d8560e01b815260040160405180910390fd5b610d4660026115f6565b610d5133848461160c565b610d5b60016115f6565b505050565b5f54610100900460ff1615808015610d7e57505f54600160ff909116105b80610d975750303b158015610d9757505f5460ff166001145b610dfa5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610600565b5f805460ff191660011790558015610e1b575f805461ff0019166101001790555b610e25878761179e565b610e2e886117da565b610e4f610e4a888860405180602001604052805f815250611838565b611882565b61012d80546001600160a01b0319166001600160a01b03891617905561012e86905561012f610e7f858783612bf5565b50610130610e8e838583612bf5565b508015610ed4575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b610ef2609754610100900460ff1660021490565b15610f105760405163bae6e2a960e01b815260040160405180910390fd5b7f0000000000000000000000007633750000000000000000000000000000000004336001600160a01b03821614610f5a576040516395383ea160e01b815260040160405180910390fd5b6002610f6860975460ff1690565b60ff1603610f895760405163dfc60d8560e01b815260040160405180910390fd5b610f9360026115f6565b61100d868686808060200260200160405190810160405280939291908181526020018383602002808284375f9201919091525050604080516020808a028281018201909352898252909350899250889182918501908490808284375f920182905250604080516020810190915290815292506118b1915050565b61101760016115f6565b505050505050565b6001600160a01b03851633148061103b575061103b853361052f565b6110575760405162461bcd60e51b815260040161060090612ab7565b6107d68585858585611a32565b61106c61146c565b6001600160a01b0381166110d15760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610600565b6108b9816114c6565b5f6001600160e01b03198216636cdb3d1360e11b148061110a57506001600160e01b031982166303a24d0760e21b145b8061062d57506301ffc9a760e01b6001600160e01b031983161461062d565b815183511461114a5760405162461bcd60e51b815260040161060090612cae565b6001600160a01b0384166111705760405162461bcd60e51b815260040161060090612cf6565b3361117f818787878787611b6a565b5f5b845181101561125c575f85828151811061119d5761119d612b9d565b602002602001015190505f8583815181106111ba576111ba612b9d565b6020908102919091018101515f84815260fb835260408082206001600160a01b038e16835290935291909120549091508181101561120a5760405162461bcd60e51b815260040161060090612d3b565b5f83815260fb602090815260408083206001600160a01b038e8116855292528083208585039055908b16825281208054849290611248908490612d99565b909155505060019093019250611181915050565b50846001600160a01b0316866001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb87876040516112ac929190612dac565b60405180910390a4611017818787878787611baa565b5f51602061315b5f395f51905f52546001600160a01b031690565b6108b961146c565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff161561131857610d5b83611d04565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611372575060408051601f3d908101601f1916820190925261136f91810190612dd9565b60015b6113d55760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610600565b5f51602061315b5f395f51905f5281146114435760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610600565b50610d5b838383611d9f565b60405163a87dd7cf60e01b815260040160405180910390fd5b610b1c5b6033546001600160a01b031633146109465760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610600565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b816001600160a01b0316836001600160a01b03160361158a5760405162461bcd60e51b815260206004820152602960248201527f455243313135353a2073657474696e6720617070726f76616c20737461747573604482015268103337b91039b2b63360b91b6064820152608401610600565b6001600160a01b038381165f81815260fc6020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b6097805460ff191660ff92909216919091179055565b6001600160a01b03831661166e5760405162461bcd60e51b815260206004820152602360248201527f455243313135353a206275726e2066726f6d20746865207a65726f206164647260448201526265737360e81b6064820152608401610600565b335f61167984611dc9565b90505f61168584611dc9565b90506116a383875f858560405180602001604052805f815250611b6a565b5f85815260fb602090815260408083206001600160a01b038a168452909152902054848110156117215760405162461bcd60e51b8152602060048201526024808201527f455243313135353a206275726e20616d6f756e7420657863656564732062616c604482015263616e636560e01b6064820152608401610600565b5f86815260fb602090815260408083206001600160a01b038b81168086529184528285208a8703905582518b81529384018a90529092908816917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a460408051602081019091525f90525b50505050505050565b6001600160a01b03821615806117b2575080155b806117bc57504681145b15610b1c5760405163c118d2f360e01b815260040160405180910390fd5b5f54610100900460ff166118005760405162461bcd60e51b815260040161060090612df0565b611808611e12565b6118266001600160a01b0382161561182057816114c6565b336114c6565b506097805461ff001916610100179055565b606061184e846001600160a01b03166014611e38565b61185784611fd4565b8360405160200161186a93929190612e3b565b60405160208183030381529060405290509392505050565b5f54610100900460ff166118a85760405162461bcd60e51b815260040161060090612df0565b6108b981612063565b6001600160a01b0384166119115760405162461bcd60e51b815260206004820152602160248201527f455243313135353a206d696e7420746f20746865207a65726f206164647265736044820152607360f81b6064820152608401610600565b81518351146119325760405162461bcd60e51b815260040161060090612cae565b33611941815f87878787611b6a565b5f5b84518110156119cc5783818151811061195e5761195e612b9d565b602002602001015160fb5f87848151811061197b5761197b612b9d565b602002602001015181526020019081526020015f205f886001600160a01b03166001600160a01b031681526020019081526020015f205f8282546119bf9190612d99565b9091555050600101611943565b50846001600160a01b03165f6001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8787604051611a1c929190612dac565b60405180910390a46107d6815f87878787611baa565b6001600160a01b038416611a585760405162461bcd60e51b815260040161060090612cf6565b335f611a6385611dc9565b90505f611a6f85611dc9565b9050611a7f838989858589611b6a565b5f86815260fb602090815260408083206001600160a01b038c16845290915290205485811015611ac15760405162461bcd60e51b815260040161060090612d3b565b5f87815260fb602090815260408083206001600160a01b038d8116855292528083208985039055908a16825281208054889290611aff908490612d99565b909155505060408051888152602081018890526001600160a01b03808b16928c821692918816917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a4611b5f848a8a8a8a8a612092565b505050505050505050565b611b7e609754610100900460ff1660021490565b15611b9c5760405163bae6e2a960e01b815260040160405180910390fd5b611ba58461214c565b611017565b6001600160a01b0384163b156110175760405163bc197c8160e01b81526001600160a01b0385169063bc197c8190611bee9089908990889088908890600401612ec3565b6020604051808303815f875af1925050508015611c28575060408051601f3d908101601f19168201909252611c2591810190612f20565b60015b611cd457611c34612f3b565b806308c379a003611c6d5750611c48612f53565b80611c535750611c6f565b8060405162461bcd60e51b81526004016106009190612480565b505b60405162461bcd60e51b815260206004820152603460248201527f455243313135353a207472616e7366657220746f206e6f6e2d455243313135356044820152732932b1b2b4bb32b91034b6b83632b6b2b73a32b960611b6064820152608401610600565b6001600160e01b0319811663bc197c8160e01b146117955760405162461bcd60e51b815260040161060090612fcd565b6001600160a01b0381163b611d715760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610600565b5f51602061315b5f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b611da883612175565b5f82511180611db45750805b15610d5b57611dc383836121b4565b50505050565b6040805160018082528183019092526060915f91906020808301908036833701905050905082815f81518110611e0157611e01612b9d565b602090810291909101015292915050565b5f54610100900460ff166109465760405162461bcd60e51b815260040161060090612df0565b60605f611e46836002613015565b611e51906002612d99565b6001600160401b03811115611e6857611e686124a9565b6040519080825280601f01601f191660200182016040528015611e92576020820181803683370190505b509050600360fc1b815f81518110611eac57611eac612b9d565b60200101906001600160f81b03191690815f1a905350600f60fb1b81600181518110611eda57611eda612b9d565b60200101906001600160f81b03191690815f1a9053505f611efc846002613015565b611f07906001612d99565b90505b6001811115611f7e576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110611f3b57611f3b612b9d565b1a60f81b828281518110611f5157611f51612b9d565b60200101906001600160f81b03191690815f1a90535060049490941c93611f778161302c565b9050611f0a565b508315611fcd5760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610600565b9392505050565b60605f611fe0836121d9565b60010190505f816001600160401b03811115611ffe57611ffe6124a9565b6040519080825280601f01601f191660200182016040528015612028576020820181803683370190505b5090508181016020015b5f19016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a850494508461203257509392505050565b5f54610100900460ff166120895760405162461bcd60e51b815260040161060090612df0565b6108b9816122b0565b6001600160a01b0384163b156110175760405163f23a6e6160e01b81526001600160a01b0385169063f23a6e61906120d69089908990889088908890600401613041565b6020604051808303815f875af1925050508015612110575060408051601f3d908101601f1916820190925261210d91810190612f20565b60015b61211c57611c34612f3b565b6001600160e01b0319811663f23a6e6160e01b146117955760405162461bcd60e51b815260040161060090612fcd565b306001600160a01b038216036108b957604051630c292c9d60e21b815260040160405180910390fd5b61217e81611d04565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060611fcd838360405180606001604052806027815260200161317b602791396122bc565b5f8072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b83106122175772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef81000000008310612243576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc10000831061226157662386f26fc10000830492506010015b6305f5e1008310612279576305f5e100830492506008015b612710831061228d57612710830492506004015b6064831061229f576064830492506002015b600a831061062d5760010192915050565b60fd610b1c8282613085565b60605f5f856001600160a01b0316856040516122d8919061313f565b5f60405180830381855af49150503d805f8114612310576040519150601f19603f3d011682016040523d82523d5f602084013e612315565b606091505b509150915061232686838387612330565b9695505050505050565b6060831561239e5782515f03612397576001600160a01b0385163b6123975760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610600565b50816123a8565b6123a883836123b0565b949350505050565b815115611c535781518083602001fd5b80356001600160a01b03811681146123d6575f5ffd5b919050565b5f5f604083850312156123ec575f5ffd5b6123f5836123c0565b946020939093013593505050565b6001600160e01b0319811681146108b9575f5ffd5b5f60208284031215612428575f5ffd5b8135611fcd81612403565b5f5b8381101561244d578181015183820152602001612435565b50505f910152565b5f815180845261246c816020860160208601612433565b601f01601f19169290920160200192915050565b602081525f611fcd6020830184612455565b5f602082840312156124a2575f5ffd5b5035919050565b634e487b7160e01b5f52604160045260245ffd5b601f8201601f191681016001600160401b03811182821017156124e2576124e26124a9565b6040525050565b5f6001600160401b03821115612501576125016124a9565b5060051b60200190565b5f82601f83011261251a575f5ffd5b8135612525816124e9565b60405161253282826124bd565b80915082815260208101915060208360051b860101925085831115612555575f5ffd5b602085015b8381101561257257803583526020928301920161255a565b5095945050505050565b5f82601f83011261258b575f5ffd5b81356001600160401b038111156125a4576125a46124a9565b6040516125bb601f8301601f1916602001826124bd565b8181528460208386010111156125cf575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f5f5f5f5f60a086880312156125ff575f5ffd5b612608866123c0565b9450612616602087016123c0565b935060408601356001600160401b03811115612630575f5ffd5b61263c8882890161250b565b93505060608601356001600160401b03811115612657575f5ffd5b6126638882890161250b565b92505060808601356001600160401b0381111561267e575f5ffd5b61268a8882890161257c565b9150509295509295909350565b5f602082840312156126a7575f5ffd5b611fcd826123c0565b5f5f604083850312156126c1575f5ffd5b82356001600160401b038111156126d6575f5ffd5b8301601f810185136126e6575f5ffd5b80356126f1816124e9565b6040516126fe82826124bd565b80915082815260208101915060208360051b850101925087831115612721575f5ffd5b6020840193505b8284101561274a57612739846123c0565b825260209384019390910190612728565b945050505060208301356001600160401b03811115612767575f5ffd5b6127738582860161250b565b9150509250929050565b5f8151808452602084019350602083015f5b828110156127ad57815186526020958601959091019060010161278f565b5093949350505050565b602081525f611fcd602083018461277d565b5f5f604083850312156127da575f5ffd5b6127e3836123c0565b915060208301356001600160401b038111156127fd575f5ffd5b6127738582860161257c565b5f5f6040838503121561281a575f5ffd5b612823836123c0565b915060208301358015158114612837575f5ffd5b809150509250929050565b5f5f60408385031215612853575f5ffd5b50508035926020909101359150565b5f5f83601f840112612872575f5ffd5b5081356001600160401b03811115612888575f5ffd5b60208301915083602082850101111561289f575f5ffd5b9250929050565b5f5f5f5f5f5f5f60a0888a0312156128bc575f5ffd5b6128c5886123c0565b96506128d3602089016123c0565b95506040880135945060608801356001600160401b038111156128f4575f5ffd5b6129008a828b01612862565b90955093505060808801356001600160401b0381111561291e575f5ffd5b61292a8a828b01612862565b989b979a50959850939692959293505050565b5f5f83601f84011261294d575f5ffd5b5081356001600160401b03811115612963575f5ffd5b6020830191508360208260051b850101111561289f575f5ffd5b5f5f5f5f5f60608688031215612991575f5ffd5b61299a866123c0565b945060208601356001600160401b038111156129b4575f5ffd5b6129c08882890161293d565b90955093505060408601356001600160401b038111156129de575f5ffd5b6129ea8882890161293d565b969995985093965092949392505050565b5f5f60408385031215612a0c575f5ffd5b612a15836123c0565b9150612a23602084016123c0565b90509250929050565b5f5f5f5f5f60a08688031215612a40575f5ffd5b612a49866123c0565b9450612a57602087016123c0565b9350604086013592506060860135915060808601356001600160401b0381111561267e575f5ffd5b600181811c90821680612a9357607f821691505b602082108103612ab157634e487b7160e01b5f52602260045260245ffd5b50919050565b6020808252602e908201527f455243313135353a2063616c6c6572206973206e6f7420746f6b656e206f776e60408201526d195c881bdc88185c1c1c9bdd995960921b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b634e487b7160e01b5f52603260045260245ffd5b601f821115610d5b57805f5260205f20601f840160051c81016020851015612bd65750805b601f840160051c820191505b818110156107d6575f8155600101612be2565b6001600160401b03831115612c0c57612c0c6124a9565b612c2083612c1a8354612a7f565b83612bb1565b5f601f841160018114612c51575f8515612c3a5750838201355b5f19600387901b1c1916600186901b1783556107d6565b5f83815260208120601f198716915b82811015612c805786850135825560209485019460019092019101612c60565b5086821015612c9c575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b60208082526028908201527f455243313135353a2069647320616e6420616d6f756e7473206c656e677468206040820152670dad2e6dac2e8c6d60c31b606082015260800190565b60208082526025908201527f455243313135353a207472616e7366657220746f20746865207a65726f206164604082015264647265737360d81b606082015260800190565b6020808252602a908201527f455243313135353a20696e73756666696369656e742062616c616e636520666f60408201526939103a3930b739b332b960b11b606082015260800190565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561062d5761062d612d85565b604081525f612dbe604083018561277d565b8281036020840152612dd0818561277d565b95945050505050565b5f60208284031215612de9575f5ffd5b5051919050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6832ba3432b932bab69d60b91b81525f8451612e5e816009850160208901612433565b600160fe1b6009918401918201528451612e7f81600a840160208901612433565b600981830101915050712f746f6b656e5552493f75696e743235363d60701b60018201528351612eb6816013840160208801612433565b0160130195945050505050565b6001600160a01b0386811682528516602082015260a0604082018190525f90612eee9083018661277d565b8281036060840152612f00818661277d565b90508281036080840152612f148185612455565b98975050505050505050565b5f60208284031215612f30575f5ffd5b8151611fcd81612403565b5f60033d1115610bcf5760045f5f3e505f5160e01c90565b5f60443d1015612f605790565b6040513d600319016004823e80513d60248201116001600160401b0382111715612f8957505090565b80820180516001600160401b03811115612fa4575050505090565b3d8401600319018282016020011115612fbe575050505090565b610a5f602082850101856124bd565b60208082526028908201527f455243313135353a204552433131353552656365697665722072656a656374656040820152676420746f6b656e7360c01b606082015260800190565b808202811582820484141761062d5761062d612d85565b5f8161303a5761303a612d85565b505f190190565b6001600160a01b03868116825285166020820152604081018490526060810183905260a0608082018190525f9061307a90830184612455565b979650505050505050565b81516001600160401b0381111561309e5761309e6124a9565b6130b2816130ac8454612a7f565b84612bb1565b6020601f8211600181146130e4575f83156130cd5750848201515b5f19600385901b1c1916600184901b1784556107d6565b5f84815260208120601f198516915b8281101561311357878501518255602094850194600190920191016130f3565b508482101561313057868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b5f8251613150818460208701612433565b919091019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220132703fefa02aac0d4af7fe9cbbb2d7c5cf33ec5aa63497d9282e87d182bfb9464736f6c634300081b0033", "balance": "0x0" }, "0x0763375000000000000000000000000000000005": { @@ -194,7 +192,7 @@ "storage": { "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000003bc256069ff9af461f3e04494a3ece3f62f183fc" }, - "code": "0x6080604052600436106101af575f3560e01c8063715018a6116100e7578063a86f9d9e11610087578063e07baba611610062578063e07baba6146104e0578063f09a401614610505578063f2fde38b14610524578063fe9fbb8014610543575f5ffd5b8063a86f9d9e14610464578063ce9d082014610483578063dfc8ff1d146104a2575f5ffd5b80638da5cb5b116100c25780638da5cb5b146103ea578063910af6ed1461040757806391f3f74b146104265780639b527cfa14610445575f5ffd5b8063715018a6146103ae5780638456cb59146103c25780638abf6077146103d6575f5ffd5b80633eb6b8cf116101525780634f90a6741161012d5780634f90a6741461032e57806352d1902d1461035b5780635c975abb1461036f57806366ca2bc01461038f575f5ffd5b80633eb6b8cf146102e85780633f4ba83a146103075780634f1ef2861461031b575f5ffd5b8063355bcc3d1161018d578063355bcc3d1461021c5780633659cfe6146102735780633ab76e9f146102925780633ced0e08146102c9575f5ffd5b80632d1fb389146101b35780633075db56146101d457806332676bc6146101fd575b5f5ffd5b3480156101be575f5ffd5b506101d26101cd36600461346a565b610571565b005b3480156101df575f5ffd5b506101e8610617565b60405190151581526020015b60405180910390f35b348015610208575f5ffd5b506101e861021736600461349d565b61062f565b348015610227575f5ffd5b5061025b6102363660046134dd565b60c960209081525f92835260408084209091529082529020546001600160401b031681565b6040516001600160401b0390911681526020016101f4565b34801561027e575f5ffd5b506101d261028d3660046134f7565b610645565b34801561029d575f5ffd5b506065546102b1906001600160a01b031681565b6040516001600160a01b0390911681526020016101f4565b3480156102d4575f5ffd5b506101e86102e3366004613512565b610715565b3480156102f3575f5ffd5b506102b1610302366004613553565b61075c565b348015610312575f5ffd5b506101d2610772565b6101d2610329366004613663565b610786565b348015610339575f5ffd5b5061034d610348366004613512565b61083f565b6040519081526020016101f4565b348015610366575f5ffd5b5061034d610885565b34801561037a575f5ffd5b506101e8609754610100900460ff1660021490565b34801561039a575f5ffd5b5061034d6103a93660046136af565b610936565b3480156103b9575f5ffd5b506101d2610942565b3480156103cd575f5ffd5b506101d2610953565b3480156103e1575f5ffd5b506102b161096e565b3480156103f5575f5ffd5b506033546001600160a01b03166102b1565b348015610412575f5ffd5b5061034d6104213660046136c6565b61097c565b348015610431575f5ffd5b5061034d61044036600461375b565b610a57565b348015610450575f5ffd5b5061034d61045f366004613797565b610ac2565b34801561046f575f5ffd5b506102b161047e3660046137c7565b610aee565b34801561048e575f5ffd5b506101d261049d3660046136c6565b610afa565b3480156104ad575f5ffd5b506104c16104bc366004613797565b610b10565b604080516001600160401b0390931683526020830191909152016101f4565b3480156104eb575f5ffd5b5060975461025b906201000090046001600160401b031681565b348015610510575f5ffd5b506101d261051f3660046137e8565b610ba4565b34801561052f575f5ffd5b506101d261053e3660046134f7565b610cb3565b34801561054e575f5ffd5b506101e861055d3660046134f7565b60ca6020525f908152604090205460ff1681565b610579610d29565b6001600160a01b0382165f90815260ca602052604090205481151560ff9091161515036105b9576040516398f26f4560e01b815260040160405180910390fd5b6001600160a01b0382165f81815260ca6020908152604091829020805460ff191685151590811790915591519182527f4c0079b9bcd37cd5d29a13938effd97c881798cbc6bd52a3026a29d94b27d1bf910160405180910390a25050565b5f600261062660975460ff1690565b60ff1614905090565b5f61063a8383610d83565b151590505b92915050565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000000051630036106965760405162461bcd60e51b815260040161068d9061381f565b60405180910390fd5b7f00000000000000000000000007633750000000000000000000000000000000056001600160a01b03166106c8610de5565b6001600160a01b0316146106ee5760405162461bcd60e51b815260040161068d9061386b565b6106f781610e00565b604080515f8082526020820190925261071291839190610e08565b50565b5f818082036107375760405163ec73295960e01b815260040160405180910390fd5b5f610743878787610ac2565b9050836107503083610d83565b14979650505050505050565b5f610768848484610f72565b90505b9392505050565b61077a610fc4565b610784335f611054565b565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000000051630036107ce5760405162461bcd60e51b815260040161068d9061381f565b7f00000000000000000000000007633750000000000000000000000000000000056001600160a01b0316610800610de5565b6001600160a01b0316146108265760405162461bcd60e51b815260040161068d9061386b565b61082f82610e00565b61083b82826001610e08565b5050565b335f90815260ca602052604081205460ff1661086e57604051631f67751f60e01b815260040160405180910390fd5b61087a8585858561106d565b90505b949350505050565b5f306001600160a01b037f000000000000000000000000076337500000000000000000000000000000000516146109245760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c0000000000000000606482015260840161068d565b505f516020613dff5f395f51905f5290565b5f61063f33838461114f565b61094a610d29565b6107845f611228565b61095b611279565b610963611292565b610784336001611054565b5f610977610de5565b905090565b5f610991609754610100900460ff1660021490565b156109af5760405163bae6e2a960e01b815260040160405180910390fd5b60026109bd60975460ff1690565b60ff16036109de5760405163dfc60d8560e01b815260040160405180910390fd5b6097805460ff191660021790555f6109fb87878787876001611303565b90505f5b8151811015610a3e57610a2a828281518110610a1d57610a1d6138b7565b60200260200101516117bf565b610a3490846138df565b92506001016109ff565b50506097805460ff191660011790555b95945050505050565b6040516514d251d3905360d21b60208201526001600160c01b031960c085901b1660268201526bffffffffffffffffffffffff19606084901b16602e820152604281018290525f906062015b6040516020818303038152906040528051906020012090509392505050565b604080516001600160401b03808616602083015291810184905290821660608201525f90608001610aa3565b5f61076b468484610f72565b610b0885858585855f611303565b505050505050565b5f5f826001600160401b03165f03610b4c576001600160401b038086165f90815260c96020908152604080832088845290915290205416610b4e565b825b91506001600160401b03821615610b9c575f610b6b868685610ac2565b9050610b773082610d83565b91505f829003610b9a5760405163738afa0560e01b815260040160405180910390fd5b505b935093915050565b5f54610100900460ff1615808015610bc257505f54600160ff909116105b80610bdb5750303b158015610bdb57505f5460ff166001145b610c3e5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161068d565b5f805460ff191660011790558015610c5f575f805461ff0019166101001790555b610c698383611903565b8015610cae575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050565b610cbb610d29565b6001600160a01b038116610d205760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b606482015260840161068d565b61071281611228565b6033546001600160a01b031633146107845760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161068d565b5f826001600160a01b038116610dac5760405163538ba4f960e01b815260040160405180910390fd5b825f819003610dce5760405163ec73295960e01b815260040160405180910390fd5b5f610dda468787610a57565b549695505050505050565b5f516020613dff5f395f51905f52546001600160a01b031690565b610712610d29565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff1615610e3b57610cae8361193d565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015610e95575060408051601f3d908101601f19168201909252610e92918101906138f2565b60015b610ef85760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b606482015260840161068d565b5f516020613dff5f395f51905f528114610f665760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b606482015260840161068d565b50610cae8383836119d8565b5f610f7d8484611a02565b905081158015610f9457506001600160a01b038116155b1561076b57604051632b0d65db60e01b81526001600160401b03851660048201526024810184905260440161068d565b610fd8609754610100900460ff1660021490565b610ff55760405163bae6e2a960e01b815260040160405180910390fd5b6097805461010069ffffffffffffffffff001990911662010000426001600160401b031602171790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa906020015b60405180910390a1565b604051630c2b8f8f60e11b815260040160405180910390fd5b5f611079858585610ac2565b905061108630828461114f565b506001600160401b038581165f90815260c960209081526040808320888452909152902054818516911610156110f0576001600160401b038581165f90815260c9602090815260408083208884529091529020805467ffffffffffffffff19169185169190911790555b83836001600160401b0316866001600160401b03167fde247c825b1fb2d7ff9e0e771cba6f9e757ad04479fcdc135d88ae91fd50b37d858560405161113f929190918252602082015260400190565b60405180910390a4949350505050565b5f836001600160a01b0381166111785760405163538ba4f960e01b815260040160405180910390fd5b835f81900361119a5760405163ec73295960e01b815260040160405180910390fd5b835f8190036111bc5760405163ec73295960e01b815260040160405180910390fd5b6111c7468888610a57565b858155604080516001600160a01b038a16815260208101899052908101829052606081018790529094507f0ad2d108660a211f47bf7fb43a0443cae181624995d3d42b88ee6879d200e9739060800160405180910390a15050509392505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b60405163a87dd7cf60e01b815260040160405180910390fd5b6112a6609754610100900460ff1660021490565b156112c45760405163bae6e2a960e01b815260040160405180910390fd5b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200161104a565b6060856001600160a01b03811661132d5760405163538ba4f960e01b815260040160405180910390fd5b855f81900361134f5760405163ec73295960e01b815260040160405180910390fd5b5f61135c868801886139c6565b905080515f0361137f57604051630b92daef60e21b815260040160405180910390fd5b5f6001825161138e9190613b0c565b6001600160401b038111156113a5576113a561358c565b6040519080825280602002602001820160405280156113ce578160200160208202803683370190505b50905085156114615781516001600160401b038111156113f0576113f061358c565b60405190808252806020026020018201604052801561145d57816020015b61144a6040805160e0810182525f80825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b81526020019060019003908161140e5790505b5094505b8a8a8a805f611482856d7369676e616c5f7365727669636560901b8361075c565b9050306001600160a01b038216036114ad57604051637556223560e11b815260040160405180910390fd5b6114e56040805160c0810182525f80825260208201819052918101829052906060820190815260200160608152602001606081525090565b5f5f5f5f5b8b51811015611773578b8181518110611505576115056138b7565b602002602001015194505f5b8181101561156f57855f01516001600160401b03168c8281518110611538576115386138b7565b60200260200101516001600160401b031603611567576040516348362c2760e11b815260040160405180910390fd5b600101611511565b5061157e8a8a8a8a898b611aa4565b93508a518114915081156115be5784516001600160401b031646146115b6576040516338bf822760e21b815260040160405180910390fd5b309550611669565b845f01518b82815181106115d4576115d46138b7565b6001600160401b0392831660209182029290920101528551161580611602575084516001600160401b031646145b1561162057604051637556223560e11b815260040160405180910390fd5b845161163e906d7369676e616c5f7365727669636560901b5f61075c565b9550306001600160a01b0387160361166957604051637556223560e11b815260040160405180910390fd5b608085015151151592508f156116fd576040518060e00160405280866040015181526020018581526020018b6001600160401b0316815260200186602001516001600160401b0316815260200184151581526020018315158152602001866060015160038111156116dc576116dc613b1f565b8152508f82815181106116f1576116f16138b7565b60200260200101819052505b6117588a8461172c577fc6cdc4f2acf13acb10f410085b821f7b7113b303e9a4799023f928317396aaf561174e565b7f73e6d340850343cc6f001515dc593377337c95a6ffe034fe1e844d4dab5da1695b8760200151610ac2565b604086015186519b50969950975094955087946001016114ea565b5085158061178a57506117863088610d83565b8614155b156117a85760405163738afa0560e01b815260040160405180910390fd5b505050505050505050505050509695505050505050565b5f8060038360c0015160038111156117d9576117d9613b1f565b14806117fa575060028360c0015160038111156117f8576117f8613b1f565b145b905080801561180a575082608001515b801561181857508260a00151155b1561185a576001915061185883604001517f73e6d340850343cc6f001515dc593377337c95a6ffe034fe1e844d4dab5da1698560600151865f015161106d565b505b5f60038460c00151600381111561187357611873613b1f565b1480611894575060018460c00151600381111561189257611892613b1f565b145b90508080156118b057508360800151806118b057508360a00151155b156118fc576118c06001846138df565b92506118fa84604001517fc6cdc4f2acf13acb10f410085b821f7b7113b303e9a4799023f928317396aaf58660600151876020015161106d565b505b5050919050565b806001600160a01b03811661192b5760405163538ba4f960e01b815260040160405180910390fd5b61193483611b43565b610cae82611ba1565b6001600160a01b0381163b6119aa5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161068d565b5f516020613dff5f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b6119e183611c11565b5f825111806119ed5750805b15610cae576119fc8383611c50565b50505050565b6065545f906001600160a01b031680611a2e57604051638ed88b2560e01b815260040160405180910390fd5b604051630a3dc4f360e21b81526001600160401b0385166004820152602481018490526001600160a01b038216906328f713cc90604401602060405180830381865afa158015611a80573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061087d9190613b33565b5f856001600160a01b038116611acd5760405163538ba4f960e01b815260040160405180910390fd5b855f819003611aef5760405163ec73295960e01b815260040160405180910390fd5b855f819003611b115760405163ec73295960e01b815260040160405180910390fd5b611b35866040015186611b258d8d8d610a57565b8a8a608001518b60a00151611c75565b9a9950505050505050505050565b5f54610100900460ff16611b695760405162461bcd60e51b815260040161068d90613b4e565b611b71611d82565b611b8f6001600160a01b03821615611b895781611228565b33611228565b506097805461ff001916610100179055565b5f54610100900460ff16611bc75760405162461bcd60e51b815260040161068d90613b4e565b6001600160401b03461115611bef5760405163a12e8fa960e01b815260040160405180910390fd5b606580546001600160a01b0319166001600160a01b0392909216919091179055565b611c1a8161193d565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606061076b8383604051806060016040528060278152602001613e1f60279139611da8565b5f82515f14611d1d576040516bffffffffffffffffffffffff19606088901b1660208201525f90611cb990603401604051602081830303815290604052858a611e1c565b905080515f03611cdc57604051630414cd5b60e31b815260040160405180910390fd5b5f611ce682611e35565b9050611d0b81600281518110611cfe57611cfe6138b7565b6020026020010151611e48565b611d1490613b99565b92505050611d20565b50855b5f611d5786604051602001611d3791815260200190565b60408051601f19818403018152919052611d5087611ec7565b8585611eda565b905080611d7757604051638d9a4db360e01b815260040160405180910390fd5b509695505050505050565b5f54610100900460ff166107845760405162461bcd60e51b815260040161068d90613b4e565b60605f5f856001600160a01b031685604051611dc49190613be1565b5f60405180830381855af49150503d805f8114611dfc576040519150601f19603f3d011682016040523d82523d5f602084013e611e01565b606091505b5091509150611e1286838387611ef3565b9695505050505050565b60605f611e2885611f6b565b9050610a4e818585611f9d565b606061063f611e438361280b565b61285d565b60605f5f5f611e56856129db565b919450925090505f816001811115611e7057611e70613b1f565b14611e8e576040516307fe6cb960e21b815260040160405180910390fd5b611e9882846138df565b855114611eb857604051630b8aa6f760e31b815260040160405180910390fd5b610a4e85602001518484612cbe565b606061063f611ed583612d4e565b612e62565b5f5f611ee586611f6b565b9050611e1281868686612eba565b60608315611f615782515f03611f5a576001600160a01b0385163b611f5a5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161068d565b508161087d565b61087d8383612ee0565b60608180519060200120604051602001611f8791815260200190565b6040516020818303038152906040529050919050565b60605f845111611fe75760405162461bcd60e51b81526020600482015260156024820152744d65726b6c65547269653a20656d707479206b657960581b604482015260640161068d565b5f611ff184612f0a565b90505f611ffd86612fed565b90505f8460405160200161201391815260200190565b60408051601f1981840301815291905290505f805b84518110156127b4575f858281518110612044576120446138b7565b6020026020010151905084518311156120b65760405162461bcd60e51b815260206004820152602e60248201527f4d65726b6c65547269653a206b657920696e646578206578636565647320746f60448201526d0e8c2d840d6caf240d8cadccee8d60931b606482015260840161068d565b825f036121545780518051602091820120604051612103926120dd92910190815260200190565b604051602081830303815290604052858051602091820120825192909101919091201490565b61214f5760405162461bcd60e51b815260206004820152601d60248201527f4d65726b6c65547269653a20696e76616c696420726f6f742068617368000000604482015260640161068d565b61224a565b8051516020116121da578051805160209182012060405161217e926120dd92910190815260200190565b61214f5760405162461bcd60e51b815260206004820152602760248201527f4d65726b6c65547269653a20696e76616c6964206c6172676520696e7465726e6044820152660c2d840d0c2e6d60cb1b606482015260840161068d565b80518451602080870191909120825191909201201461224a5760405162461bcd60e51b815260206004820152602660248201527f4d65726b6c65547269653a20696e76616c696420696e7465726e616c206e6f646044820152650ca40d0c2e6d60d31b606482015260840161068d565b612256601060016138df565b816020015151036123ee5784518303612388576122838160200151601081518110611cfe57611cfe6138b7565b96505f8751116122fb5760405162461bcd60e51b815260206004820152603b60248201527f4d65726b6c65547269653a2076616c7565206c656e677468206d75737420626560448201527f2067726561746572207468616e207a65726f20286272616e6368290000000000606482015260840161068d565b600186516123099190613b0c565b821461237d5760405162461bcd60e51b815260206004820152603a60248201527f4d65726b6c65547269653a2076616c7565206e6f6465206d757374206265206c60448201527f617374206e6f646520696e2070726f6f6620286272616e636829000000000000606482015260840161068d565b50505050505061076b565b5f85848151811061239b5761239b6138b7565b602001015160f81c60f81b60f81c90505f82602001518260ff16815181106123c5576123c56138b7565b602002602001015190506123d88161304e565b95506123e56001866138df565b945050506127ab565b600281602001515103612752575f61240582613072565b90505f815f8151811061241a5761241a6138b7565b016020015160f81c90505f612430600283613c10565b61243b906002613c31565b90505f61244b848360ff16613095565b90505f6124588a89613095565b90505f61246583836130ca565b9050808351146124dd5760405162461bcd60e51b815260206004820152603a60248201527f4d65726b6c65547269653a20706174682072656d61696e646572206d7573742060448201527f736861726520616c6c206e6962626c65732077697468206b6579000000000000606482015260840161068d565b60ff8516600214806124f2575060ff85166003145b15612692578082511461256d5760405162461bcd60e51b815260206004820152603d60248201527f4d65726b6c65547269653a206b65792072656d61696e646572206d757374206260448201527f65206964656e746963616c20746f20706174682072656d61696e646572000000606482015260840161068d565b6125878760200151600181518110611cfe57611cfe6138b7565b9c505f8d51116125ff5760405162461bcd60e51b815260206004820152603960248201527f4d65726b6c65547269653a2076616c7565206c656e677468206d75737420626560448201527f2067726561746572207468616e207a65726f20286c6561662900000000000000606482015260840161068d565b60018c5161260d9190613b0c565b88146126815760405162461bcd60e51b815260206004820152603860248201527f4d65726b6c65547269653a2076616c7565206e6f6465206d757374206265206c60448201527f617374206e6f646520696e2070726f6f6620286c656166290000000000000000606482015260840161068d565b50505050505050505050505061076b565b60ff851615806126a5575060ff85166001145b156126e4576126d187602001516001815181106126c4576126c46138b7565b602002602001015161304e565b99506126dd818a6138df565b9850612747565b60405162461bcd60e51b815260206004820152603260248201527f4d65726b6c65547269653a2072656365697665642061206e6f64652077697468604482015271040c2dc40eadcd6dcdeeedc40e0e4caccd2f60731b606482015260840161068d565b5050505050506127ab565b60405162461bcd60e51b815260206004820152602860248201527f4d65726b6c65547269653a20726563656976656420616e20756e706172736561604482015267626c65206e6f646560c01b606482015260840161068d565b50600101612028565b5060405162461bcd60e51b815260206004820152602560248201527f4d65726b6c65547269653a2072616e206f7574206f662070726f6f6620656c656044820152646d656e747360d81b606482015260840161068d565b604080518082019091525f808252602082015281515f0361283f57604051635ab458fb60e01b815260040160405180910390fd5b50604080518082019091528151815260209182019181019190915290565b60605f5f5f61286b856129db565b91945092509050600181600181111561288657612886613b1f565b146128a4576040516325ce355f60e11b815260040160405180910390fd5b84516128b083856138df565b146128ce57604051630b8aa6f760e31b815260040160405180910390fd5b604080516020808252610420820190925290816020015b604080518082019091525f80825260208201528152602001906001900390816128e55790505093505f835b86518110156129cf575f5f6129546040518060400160405280858c5f01516129389190613b0c565b8152602001858c6020015161294d91906138df565b90526129db565b50915091506040518060400160405280838361297091906138df565b8152602001848b6020015161298591906138df565b81525088858151811061299a5761299a6138b7565b60209081029190910101526129b06001856138df565b93506129bc81836138df565b6129c690846138df565b92505050612910565b50845250919392505050565b5f5f5f835f01515f03612a0157604051635ab458fb60e01b815260040160405180910390fd5b602084015180515f1a607f8111612a23575f60015f9450945094505050612cb7565b60b78111612ab8575f612a37608083613b0c565b905080875f015111612a5c576040516366c9448560e01b815260040160405180910390fd5b6001838101516001600160f81b0319169082148015612a885750600160ff1b6001600160f81b03198216105b15612aa65760405163babb01dd60e01b815260040160405180910390fd5b506001955093505f9250612cb7915050565b60bf8111612b96575f612acc60b783613b0c565b905080875f015111612af1576040516366c9448560e01b815260040160405180910390fd5b60018301516001600160f81b0319165f819003612b215760405163babb01dd60e01b815260040160405180910390fd5b600184015160088302610100031c60378111612b505760405163babb01dd60e01b815260040160405180910390fd5b612b5a81846138df565b895111612b7a576040516366c9448560e01b815260040160405180910390fd5b612b858360016138df565b975095505f9450612cb79350505050565b60f78111612be0575f612baa60c083613b0c565b905080875f015111612bcf576040516366c9448560e01b815260040160405180910390fd5b600195509350849250612cb7915050565b5f612bec60f783613b0c565b905080875f015111612c11576040516366c9448560e01b815260040160405180910390fd5b60018301516001600160f81b0319165f819003612c415760405163babb01dd60e01b815260040160405180910390fd5b600184015160088302610100031c60378111612c705760405163babb01dd60e01b815260040160405180910390fd5b612c7a81846138df565b895111612c9a576040516366c9448560e01b815260040160405180910390fd5b612ca58360016138df565b9750955060019450612cb79350505050565b9193909250565b6060816001600160401b03811115612cd857612cd861358c565b6040519080825280601f01601f191660200182016040528015612d02576020820181803683370190505b509050811561076b575f612d1684866138df565b9050602082015f5b84811015612d36578281015182820152602001612d1e565b84811115612d44575f858301525b5050509392505050565b60605f82604051602001612d6491815260200190565b60408051601f1981840301815291905290505f5b6020811015612db057818181518110612d9357612d936138b7565b01602001516001600160f81b0319165f03612db057600101612d78565b612dbb816020613b0c565b6001600160401b03811115612dd257612dd261358c565b6040519080825280601f01601f191660200182016040528015612dfc576020820181803683370190505b5092505f5b83518110156118fa578282612e1581613c4a565b935081518110612e2757612e276138b7565b602001015160f81c60f81b848281518110612e4457612e446138b7565b60200101906001600160f81b03191690815f1a905350600101612e01565b606081516001148015612e8e57506080825f81518110612e8457612e846138b7565b016020015160f81c105b15612e97575090565b612ea38251608061314d565b82604051602001611f87929190613c62565b919050565b5f61087a84612eca878686611f9d565b8051602091820120825192909101919091201490565b815115612ef05781518083602001fd5b8060405162461bcd60e51b815260040161068d9190613c90565b8051606090806001600160401b03811115612f2757612f2761358c565b604051908082528060200260200182016040528015612f6c57816020015b6040805180820190915260608082526020820152815260200190600190039081612f455790505b5091505f5b818110156118fc576040518060400160405280858381518110612f9657612f966138b7565b60200260200101518152602001612fc5868481518110612fb857612fb86138b7565b6020026020010151611e35565b815250838281518110612fda57612fda6138b7565b6020908102919091010152600101612f71565b606080604051905082518060011b603f8101601f191683016040528083525060208401602083015f5b83811015613043578060011b8201818401515f1a8060041c8253600f811660018301535050600101613016565b509295945050505050565b60606020825f0151106130695761306482611e48565b61063f565b61063f826132f2565b606061063f61309083602001515f81518110611cfe57611cfe6138b7565b612fed565b6060825182106130b3575060408051602081019091525f815261063f565b61076b83838486516130c59190613b0c565b613306565b5f5f82518451106130dc5782516130df565b83515b90505b808210801561313657508282815181106130fe576130fe6138b7565b602001015160f81c60f81b6001600160f81b031916848381518110613125576131256138b7565b01602001516001600160f81b031916145b15613146578160010191506130e2565b5092915050565b606060388310156131b157604080516001808252818301909252906020820181803683370190505090506131818284613cc2565b60f81b815f81518110613196576131966138b7565b60200101906001600160f81b03191690815f1a90535061063f565b5f60015b6131bf8186613cdb565b156131e557816131ce81613c4a565b92506131de905061010082613cee565b90506131b5565b6131f08260016138df565b6001600160401b038111156132075761320761358c565b6040519080825280601f01601f191660200182016040528015613231576020820181803683370190505b50925061323e8483613cc2565b613249906037613cc2565b60f81b835f8151811061325e5761325e6138b7565b60200101906001600160f81b03191690815f1a905350600190505b8181116132ea5761010061328d8284613b0c565b61329990610100613de0565b6132a39087613cdb565b6132ad9190613deb565b60f81b8382815181106132c2576132c26138b7565b60200101906001600160f81b03191690815f1a905350806132e281613c4a565b915050613279565b505092915050565b606061063f82602001515f845f0151612cbe565b60608182601f01101561334c5760405162461bcd60e51b815260206004820152600e60248201526d736c6963655f6f766572666c6f7760901b604482015260640161068d565b82828401101561338f5760405162461bcd60e51b815260206004820152600e60248201526d736c6963655f6f766572666c6f7760901b604482015260640161068d565b818301845110156133d65760405162461bcd60e51b8152602060048201526011602482015270736c6963655f6f75744f66426f756e647360781b604482015260640161068d565b6060821580156133f45760405191505f82526020820160405261343e565b6040519150601f8416801560200281840101858101878315602002848b0101015b8183101561342d578051835260209283019201613415565b5050858452601f01601f1916604052505b50949350505050565b6001600160a01b0381168114610712575f5ffd5b80358015158114612eb5575f5ffd5b5f5f6040838503121561347b575f5ffd5b823561348681613447565b91506134946020840161345b565b90509250929050565b5f5f604083850312156134ae575f5ffd5b82356134b981613447565b946020939093013593505050565b80356001600160401b0381168114612eb5575f5ffd5b5f5f604083850312156134ee575f5ffd5b6134b9836134c7565b5f60208284031215613507575f5ffd5b813561076b81613447565b5f5f5f5f60808587031215613525575f5ffd5b61352e856134c7565b935060208501359250613543604086016134c7565b9396929550929360600135925050565b5f5f5f60608486031215613565575f5ffd5b61356e846134c7565b9250602084013591506135836040850161345b565b90509250925092565b634e487b7160e01b5f52604160045260245ffd5b60405160c081016001600160401b03811182821017156135c2576135c261358c565b60405290565b604051601f8201601f191681016001600160401b03811182821017156135f0576135f061358c565b604052919050565b5f82601f830112613607575f5ffd5b81356001600160401b038111156136205761362061358c565b613633601f8201601f19166020016135c8565b818152846020838601011115613647575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f5f60408385031215613674575f5ffd5b823561367f81613447565b915060208301356001600160401b03811115613699575f5ffd5b6136a5858286016135f8565b9150509250929050565b5f602082840312156136bf575f5ffd5b5035919050565b5f5f5f5f5f608086880312156136da575f5ffd5b6136e3866134c7565b945060208601356136f381613447565b93506040860135925060608601356001600160401b03811115613714575f5ffd5b8601601f81018813613724575f5ffd5b80356001600160401b03811115613739575f5ffd5b88602082840101111561374a575f5ffd5b959894975092955050506020019190565b5f5f5f6060848603121561376d575f5ffd5b613776846134c7565b9250602084013561378681613447565b929592945050506040919091013590565b5f5f5f606084860312156137a9575f5ffd5b6137b2846134c7565b925060208401359150613583604085016134c7565b5f5f604083850312156137d8575f5ffd5b823591506134946020840161345b565b5f5f604083850312156137f9575f5ffd5b823561380481613447565b9150602083013561381481613447565b809150509250929050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b8082018082111561063f5761063f6138cb565b5f60208284031215613902575f5ffd5b5051919050565b5f6001600160401b038211156139215761392161358c565b5060051b60200190565b803560048110612eb5575f5ffd5b5f82601f830112613948575f5ffd5b813561395b61395682613909565b6135c8565b8082825260208201915060208360051b86010192508583111561397c575f5ffd5b602085015b838110156139bc5780356001600160401b0381111561399e575f5ffd5b6139ad886020838a01016135f8565b84525060209283019201613981565b5095945050505050565b5f602082840312156139d6575f5ffd5b81356001600160401b038111156139eb575f5ffd5b8201601f810184136139fb575f5ffd5b8035613a0961395682613909565b8082825260208201915060208360051b850101925086831115613a2a575f5ffd5b602084015b83811015611d775780356001600160401b03811115613a4c575f5ffd5b850160c0818a03601f19011215613a61575f5ffd5b613a696135a0565b613a75602083016134c7565b8152613a83604083016134c7565b602082015260608201356040820152613a9e6080830161392b565b606082015260a08201356001600160401b03811115613abb575f5ffd5b613aca8b602083860101613939565b60808301525060c08201356001600160401b03811115613ae8575f5ffd5b613af78b602083860101613939565b60a08301525084525060209283019201613a2f565b8181038181111561063f5761063f6138cb565b634e487b7160e01b5f52602160045260245ffd5b5f60208284031215613b43575f5ffd5b815161076b81613447565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b80516020808301519190811015613bb9575f198160200360031b1b821691505b50919050565b5f5b83811015613bd9578181015183820152602001613bc1565b50505f910152565b5f8251613bf2818460208701613bbf565b9190910192915050565b634e487b7160e01b5f52601260045260245ffd5b5f60ff831680613c2257613c22613bfc565b8060ff84160691505092915050565b60ff828116828216039081111561063f5761063f6138cb565b5f60018201613c5b57613c5b6138cb565b5060010190565b5f8351613c73818460208801613bbf565b835190830190613c87818360208801613bbf565b01949350505050565b602081525f8251806020840152613cae816040850160208701613bbf565b601f01601f19169190910160400192915050565b60ff818116838216019081111561063f5761063f6138cb565b5f82613ce957613ce9613bfc565b500490565b808202811582820484141761063f5761063f6138cb565b6001815b6001841115610b9c57808504811115613d2457613d246138cb565b6001841615613d3257908102905b60019390931c928002613d09565b5f82613d4e5750600161063f565b81613d5a57505f61063f565b8160018114613d705760028114613d7a57613d96565b600191505061063f565b60ff841115613d8b57613d8b6138cb565b50506001821b61063f565b5060208310610133831016604e8410600b8410161715613db9575081810a61063f565b613dc55f198484613d05565b805f1904821115613dd857613dd86138cb565b029392505050565b5f61076b8383613d40565b5f82613df957613df9613bfc565b50069056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220b90f6e8c0e4ec748267987489a0257e6c604f10243c2b2ba479a7d319700a34464736f6c634300081b0033", + "code": "0x608060405260043610610195575f3560e01c80635c975abb116100e7578063910af6ed11610087578063ce9d082011610062578063ce9d082014610497578063dfc8ff1d146104b6578063f2fde38b146104f4578063fe9fbb8014610513575f5ffd5b8063910af6ed1461043a57806391f3f74b146104595780639b527cfa14610478575f5ffd5b80638456cb59116100c25780638456cb59146103d65780638abf6077146103ea5780638da5cb5b146103fe5780638e899f801461041b575f5ffd5b80635c975abb1461038357806366ca2bc0146103a3578063715018a6146103c2575f5ffd5b80633659cfe6116101525780633f4ba83a1161012d5780633f4ba83a1461031b5780634f1ef2861461032f5780634f90a6741461034257806352d1902d1461036f575f5ffd5b80633659cfe6146102be5780633b78c865146102dd5780633ced0e08146102fc575f5ffd5b806304f3bcec1461019957806319ab453c146101e45780632d1fb389146102055780633075db561461022457806332676bc614610248578063355bcc3d14610267575b5f5ffd5b3480156101a4575f5ffd5b507f00000000000000000000000076337500000000000000000000000000000000065b6040516001600160a01b0390911681526020015b60405180910390f35b3480156101ef575f5ffd5b506102036101fe366004613525565b610541565b005b348015610210575f5ffd5b5061020361021f366004613540565b610653565b34801561022f575f5ffd5b506102386106f9565b60405190151581526020016101db565b348015610253575f5ffd5b5061023861026236600461357b565b610711565b348015610272575f5ffd5b506102a66102813660046135bb565b60c960209081525f92835260408084209091529082529020546001600160401b031681565b6040516001600160401b0390911681526020016101db565b3480156102c9575f5ffd5b506102036102d8366004613525565b610727565b3480156102e8575f5ffd5b506102036102f73660046135d5565b6107ee565b348015610307575f5ffd5b50610238610316366004613644565b6108c2565b348015610326575f5ffd5b50610203610909565b61020361033d36600461375c565b610995565b34801561034d575f5ffd5b5061036161035c366004613644565b610a4a565b6040519081526020016101db565b34801561037a575f5ffd5b50610361610a90565b34801561038e575f5ffd5b50610238609754610100900460ff1660021490565b3480156103ae575f5ffd5b506103616103bd3660046137a8565b610b41565b3480156103cd575f5ffd5b50610203610b4d565b3480156103e1575f5ffd5b50610203610b5e565b3480156103f5575f5ffd5b506101c7610be5565b348015610409575f5ffd5b506033546001600160a01b03166101c7565b348015610426575f5ffd5b506102386104353660046137a8565b610bf3565b348015610445575f5ffd5b506103616104543660046137bf565b610c04565b348015610464575f5ffd5b50610361610473366004613854565b610cdf565b348015610483575f5ffd5b50610361610492366004613890565b610d4b565b3480156104a2575f5ffd5b506102036104b13660046137bf565b610d77565b3480156104c1575f5ffd5b506104d56104d0366004613890565b610d8d565b604080516001600160401b0390931683526020830191909152016101db565b3480156104ff575f5ffd5b5061020361050e366004613525565b610e21565b34801561051e575f5ffd5b5061023861052d366004613525565b60ca6020525f908152604090205460ff1681565b5f54610100900460ff161580801561055f57505f54600160ff909116105b806105785750303b15801561057857505f5460ff166001145b6105e05760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b5f805460ff191660011790558015610601575f805461ff0019166101001790555b61060a82610e97565b801561064f575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b61065b610ef5565b6001600160a01b0382165f90815260ca602052604090205481151560ff90911615150361069b576040516398f26f4560e01b815260040160405180910390fd5b6001600160a01b0382165f81815260ca6020908152604091829020805460ff191685151590811790915591519182527f4c0079b9bcd37cd5d29a13938effd97c881798cbc6bd52a3026a29d94b27d1bf910160405180910390a25050565b5f600261070860975460ff1690565b60ff1614905090565b5f61071c8383610f4f565b151590505b92915050565b6001600160a01b037f000000000000000000000000076337500000000000000000000000000000000516300361076f5760405162461bcd60e51b81526004016105d7906138c9565b7f00000000000000000000000007633750000000000000000000000000000000056001600160a01b03166107a1610fbc565b6001600160a01b0316146107c75760405162461bcd60e51b81526004016105d790613915565b6107d081610fd7565b604080515f808252602082019092526107eb91839190610fdf565b50565b647461696b6f60d81b61080281600161114e565b6001600160a01b0316336001600160a01b031614610833576040516395383ea160e01b815260040160405180910390fd5b5f5b8281101561088357600160cb5f86868581811061085457610854613961565b602090810292909201358352508101919091526040015f20805460ff1916911515919091179055600101610835565b507f8e7daa0b2b1abdb036d272b0c35976e908cfd7ae752bc13c70dfa049830b8d9b83836040516108b5929190613975565b60405180910390a1505050565b5f818082036108e45760405163ec73295960e01b815260040160405180910390fd5b5f6108f0878787610d4b565b9050836108fd3083610f4f565b14979650505050505050565b61091d609754610100900460ff1660021490565b61093a5760405163bae6e2a960e01b815260040160405180910390fd5b6109426111e7565b6109566097805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610993335f611200565b565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000000051630036109dd5760405162461bcd60e51b81526004016105d7906138c9565b7f00000000000000000000000007633750000000000000000000000000000000056001600160a01b0316610a0f610fbc565b6001600160a01b031614610a355760405162461bcd60e51b81526004016105d790613915565b610a3e82610fd7565b61064f82826001610fdf565b335f90815260ca602052604081205460ff16610a7957604051631f67751f60e01b815260040160405180910390fd5b610a8585858585611219565b90505b949350505050565b5f306001600160a01b037f00000000000000000000000007633750000000000000000000000000000000051614610b2f5760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c000000000000000060648201526084016105d7565b505f516020613ee05f395f51905f5290565b5f6107213383846112fb565b610b55610ef5565b6109935f6113d4565b610b72609754610100900460ff1660021490565b15610b905760405163bae6e2a960e01b815260040160405180910390fd5b610b986111e7565b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a1610993336001611200565b5f610bee610fbc565b905090565b5f610bfc825490565b151592915050565b5f610c19609754610100900460ff1660021490565b15610c375760405163bae6e2a960e01b815260040160405180910390fd5b6002610c4560975460ff1690565b60ff1603610c665760405163dfc60d8560e01b815260040160405180910390fd5b6097805460ff191660021790555f610c8387878787876001611425565b90505f5b8151811015610cc657610cb2828281518110610ca557610ca5613961565b6020026020010151611999565b610cbc90846139c0565b9250600101610c87565b50506097805460ff191660011790555b95945050505050565b6040516514d251d3905360d21b60208201526001600160c01b031960c085901b1660268201526bffffffffffffffffffffffff19606084901b16602e820152604281018290525f906062015b6040516020818303038152906040528051906020012090505b9392505050565b604080516001600160401b03808616602083015291810184905290821660608201525f90608001610d2b565b610d8585858585855f611425565b505050505050565b5f5f826001600160401b03165f03610dc9576001600160401b038086165f90815260c96020908152604080832088845290915290205416610dcb565b825b91506001600160401b03821615610e19575f610de8868685610d4b565b9050610df43082610f4f565b91505f829003610e175760405163738afa0560e01b815260040160405180910390fd5b505b935093915050565b610e29610ef5565b6001600160a01b038116610e8e5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016105d7565b6107eb816113d4565b5f54610100900460ff16610ebd5760405162461bcd60e51b81526004016105d7906139d3565b610ec5611add565b610ee36001600160a01b03821615610edd57816113d4565b336113d4565b506097805461ff001916610100179055565b6033546001600160a01b031633146109935760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016105d7565b5f826001600160a01b038116610f785760405163538ba4f960e01b815260040160405180910390fd5b825f819003610f9a5760405163ec73295960e01b815260040160405180910390fd5b5f610fa6468787610cdf565b9050610fb0815490565b9350505b505092915050565b5f516020613ee05f395f51905f52546001600160a01b031690565b6107eb610ef5565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156110175761101283611b03565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611071575060408051601f3d908101601f1916820190925261106e91810190613a1e565b60015b6110d45760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b60648201526084016105d7565b5f516020613ee05f395f51905f5281146111425760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b60648201526084016105d7565b50611012838383611b9e565b5f7f0000000000000000000000007633750000000000000000000000000000000006604051633632b1fb60e11b81524660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa1580156111c3573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d449190613a35565b60405163a87dd7cf60e01b815260040160405180910390fd5b604051630c2b8f8f60e11b815260040160405180910390fd5b5f611225858585610d4b565b90506112323082846112fb565b506001600160401b038581165f90815260c9602090815260408083208884529091529020548185169116101561129c576001600160401b038581165f90815260c9602090815260408083208884529091529020805467ffffffffffffffff19169185169190911790555b83836001600160401b0316866001600160401b03167fde247c825b1fb2d7ff9e0e771cba6f9e757ad04479fcdc135d88ae91fd50b37d85856040516112eb929190918252602082015260400190565b60405180910390a4949350505050565b5f836001600160a01b0381166113245760405163538ba4f960e01b815260040160405180910390fd5b835f8190036113465760405163ec73295960e01b815260040160405180910390fd5b835f8190036113685760405163ec73295960e01b815260040160405180910390fd5b611373468888610cdf565b858155604080516001600160a01b038a16815260208101899052908101829052606081018790529094507f0ad2d108660a211f47bf7fb43a0443cae181624995d3d42b88ee6879d200e9739060800160405180910390a15050509392505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b6060856001600160a01b03811661144f5760405163538ba4f960e01b815260040160405180910390fd5b855f8190036114715760405163ec73295960e01b815260040160405180910390fd5b5f8590036115235760cb5f6114878b8b8b610cdf565b815260208101919091526040015f205460ff166114b757604051632213945760e11b815260040160405180910390fd5b604080515f808252602082019092529061151b565b6115086040805160e0810182525f80825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b8152602001906001900390816114cc5790505b50925061198d565b5f61153086880188613b0d565b905080515f0361155357604051630b92daef60e21b815260040160405180910390fd5b5f600182516115629190613c53565b6001600160401b0381111561157957611579613685565b6040519080825280602002602001820160405280156115a2578160200160208202803683370190505b509050856115b0575f6115b3565b81515b6001600160401b038111156115ca576115ca613685565b60405190808252806020026020018201604052801561163757816020015b6116246040805160e0810182525f80825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b8152602001906001900390816115e85790505b5094508a8a8a805f61165b856d7369676e616c5f7365727669636560901b83611bc8565b9050306001600160a01b0382160361168657604051637556223560e11b815260040160405180910390fd5b6116be6040805160c0810182525f80825260208201819052918101829052906060820190815260200160608152602001606081525090565b5f5f5f5f5b8b5181101561194c578b81815181106116de576116de613961565b602002602001015194505f5b8181101561174857855f01516001600160401b03168c828151811061171157611711613961565b60200260200101516001600160401b031603611740576040516348362c2760e11b815260040160405180910390fd5b6001016116ea565b506117578a8a8a8a898b611c6a565b93508a518114915081156117975784516001600160401b0316461461178f576040516338bf822760e21b815260040160405180910390fd5b309550611842565b845f01518b82815181106117ad576117ad613961565b6001600160401b03928316602091820292909201015285511615806117db575084516001600160401b031646145b156117f957604051637556223560e11b815260040160405180910390fd5b8451611817906d7369676e616c5f7365727669636560901b5f611bc8565b9550306001600160a01b0387160361184257604051637556223560e11b815260040160405180910390fd5b608085015151151592508f156118d6576040518060e00160405280866040015181526020018581526020018b6001600160401b0316815260200186602001516001600160401b0316815260200184151581526020018315158152602001866060015160038111156118b5576118b5613c66565b8152508f82815181106118ca576118ca613961565b60200260200101819052505b6119318a84611905577fc6cdc4f2acf13acb10f410085b821f7b7113b303e9a4799023f928317396aaf5611927565b7f73e6d340850343cc6f001515dc593377337c95a6ffe034fe1e844d4dab5da1695b8760200151610d4b565b604086015186519b50969950975094955087946001016116c3565b50851580611963575061195f3088610f4f565b8614155b156119815760405163738afa0560e01b815260040160405180910390fd5b50505050505050505050505b50509695505050505050565b5f8060038360c0015160038111156119b3576119b3613c66565b14806119d4575060028360c0015160038111156119d2576119d2613c66565b145b90508080156119e4575082608001515b80156119f257508260a00151155b15611a345760019150611a3283604001517f73e6d340850343cc6f001515dc593377337c95a6ffe034fe1e844d4dab5da1698560600151865f0151611219565b505b5f60038460c001516003811115611a4d57611a4d613c66565b1480611a6e575060018460c001516003811115611a6c57611a6c613c66565b145b9050808015611a8a5750836080015180611a8a57508360a00151155b15611ad657611a9a6001846139c0565b9250611ad484604001517fc6cdc4f2acf13acb10f410085b821f7b7113b303e9a4799023f928317396aaf586606001518760200151611219565b505b5050919050565b5f54610100900460ff166109935760405162461bcd60e51b81526004016105d7906139d3565b6001600160a01b0381163b611b705760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016105d7565b5f516020613ee05f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b611ba783611d09565b5f82511180611bb35750805b1561101257611bc28383611d48565b50505050565b5f7f0000000000000000000000007633750000000000000000000000000000000006604051633632b1fb60e11b81526001600160401b03861660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa158015611c46573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a889190613a35565b5f856001600160a01b038116611c935760405163538ba4f960e01b815260040160405180910390fd5b855f819003611cb55760405163ec73295960e01b815260040160405180910390fd5b855f819003611cd75760405163ec73295960e01b815260040160405180910390fd5b611cfb866040015186611ceb8d8d8d610cdf565b8a8a608001518b60a00151611d6d565b9a9950505050505050505050565b611d1281611b03565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060610d448383604051806060016040528060278152602001613f0060279139611e7a565b5f82515f14611e15576040516bffffffffffffffffffffffff19606088901b1660208201525f90611db190603401604051602081830303815290604052858a611eee565b905080515f03611dd457604051630414cd5b60e31b815260040160405180910390fd5b5f611dde82611f07565b9050611e0381600281518110611df657611df6613961565b6020026020010151611f1a565b611e0c90613c7a565b92505050611e18565b50855b5f611e4f86604051602001611e2f91815260200190565b60408051601f19818403018152919052611e4887611f99565b8585611fac565b905080611e6f57604051638d9a4db360e01b815260040160405180910390fd5b509695505050505050565b60605f5f856001600160a01b031685604051611e969190613cc2565b5f60405180830381855af49150503d805f8114611ece576040519150601f19603f3d011682016040523d82523d5f602084013e611ed3565b606091505b5091509150611ee486838387611fc5565b9695505050505050565b60605f611efa8561203d565b9050610cd681858561206f565b6060610721611f15836128dd565b61292f565b60605f5f5f611f2885612aad565b919450925090505f816001811115611f4257611f42613c66565b14611f60576040516307fe6cb960e21b815260040160405180910390fd5b611f6a82846139c0565b855114611f8a57604051630b8aa6f760e31b815260040160405180910390fd5b610cd685602001518484612d90565b6060610721611fa783612e20565b612f34565b5f5f611fb78661203d565b9050611ee481868686612f8c565b606083156120335782515f0361202c576001600160a01b0385163b61202c5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016105d7565b5081610a88565b610a888383612fb2565b6060818051906020012060405160200161205991815260200190565b6040516020818303038152906040529050919050565b60605f8451116120b95760405162461bcd60e51b81526020600482015260156024820152744d65726b6c65547269653a20656d707479206b657960581b60448201526064016105d7565b5f6120c384612fdc565b90505f6120cf866130bf565b90505f846040516020016120e591815260200190565b60408051601f1981840301815291905290505f805b8451811015612886575f85828151811061211657612116613961565b6020026020010151905084518311156121885760405162461bcd60e51b815260206004820152602e60248201527f4d65726b6c65547269653a206b657920696e646578206578636565647320746f60448201526d0e8c2d840d6caf240d8cadccee8d60931b60648201526084016105d7565b825f0361222657805180516020918201206040516121d5926121af92910190815260200190565b604051602081830303815290604052858051602091820120825192909101919091201490565b6122215760405162461bcd60e51b815260206004820152601d60248201527f4d65726b6c65547269653a20696e76616c696420726f6f74206861736800000060448201526064016105d7565b61231c565b8051516020116122ac5780518051602091820120604051612250926121af92910190815260200190565b6122215760405162461bcd60e51b815260206004820152602760248201527f4d65726b6c65547269653a20696e76616c6964206c6172676520696e7465726e6044820152660c2d840d0c2e6d60cb1b60648201526084016105d7565b80518451602080870191909120825191909201201461231c5760405162461bcd60e51b815260206004820152602660248201527f4d65726b6c65547269653a20696e76616c696420696e7465726e616c206e6f646044820152650ca40d0c2e6d60d31b60648201526084016105d7565b612328601060016139c0565b816020015151036124c0578451830361245a576123558160200151601081518110611df657611df6613961565b96505f8751116123cd5760405162461bcd60e51b815260206004820152603b60248201527f4d65726b6c65547269653a2076616c7565206c656e677468206d75737420626560448201527f2067726561746572207468616e207a65726f20286272616e636829000000000060648201526084016105d7565b600186516123db9190613c53565b821461244f5760405162461bcd60e51b815260206004820152603a60248201527f4d65726b6c65547269653a2076616c7565206e6f6465206d757374206265206c60448201527f617374206e6f646520696e2070726f6f6620286272616e63682900000000000060648201526084016105d7565b505050505050610d44565b5f85848151811061246d5761246d613961565b602001015160f81c60f81b60f81c90505f82602001518260ff168151811061249757612497613961565b602002602001015190506124aa81613120565b95506124b76001866139c0565b9450505061287d565b600281602001515103612824575f6124d782613144565b90505f815f815181106124ec576124ec613961565b016020015160f81c90505f612502600283613cf1565b61250d906002613d12565b90505f61251d848360ff16613167565b90505f61252a8a89613167565b90505f612537838361319c565b9050808351146125af5760405162461bcd60e51b815260206004820152603a60248201527f4d65726b6c65547269653a20706174682072656d61696e646572206d7573742060448201527f736861726520616c6c206e6962626c65732077697468206b657900000000000060648201526084016105d7565b60ff8516600214806125c4575060ff85166003145b15612764578082511461263f5760405162461bcd60e51b815260206004820152603d60248201527f4d65726b6c65547269653a206b65792072656d61696e646572206d757374206260448201527f65206964656e746963616c20746f20706174682072656d61696e64657200000060648201526084016105d7565b6126598760200151600181518110611df657611df6613961565b9c505f8d51116126d15760405162461bcd60e51b815260206004820152603960248201527f4d65726b6c65547269653a2076616c7565206c656e677468206d75737420626560448201527f2067726561746572207468616e207a65726f20286c656166290000000000000060648201526084016105d7565b60018c516126df9190613c53565b88146127535760405162461bcd60e51b815260206004820152603860248201527f4d65726b6c65547269653a2076616c7565206e6f6465206d757374206265206c60448201527f617374206e6f646520696e2070726f6f6620286c65616629000000000000000060648201526084016105d7565b505050505050505050505050610d44565b60ff85161580612777575060ff85166001145b156127b6576127a3876020015160018151811061279657612796613961565b6020026020010151613120565b99506127af818a6139c0565b9850612819565b60405162461bcd60e51b815260206004820152603260248201527f4d65726b6c65547269653a2072656365697665642061206e6f64652077697468604482015271040c2dc40eadcd6dcdeeedc40e0e4caccd2f60731b60648201526084016105d7565b50505050505061287d565b60405162461bcd60e51b815260206004820152602860248201527f4d65726b6c65547269653a20726563656976656420616e20756e706172736561604482015267626c65206e6f646560c01b60648201526084016105d7565b506001016120fa565b5060405162461bcd60e51b815260206004820152602560248201527f4d65726b6c65547269653a2072616e206f7574206f662070726f6f6620656c656044820152646d656e747360d81b60648201526084016105d7565b604080518082019091525f808252602082015281515f0361291157604051635ab458fb60e01b815260040160405180910390fd5b50604080518082019091528151815260209182019181019190915290565b60605f5f5f61293d85612aad565b91945092509050600181600181111561295857612958613c66565b14612976576040516325ce355f60e11b815260040160405180910390fd5b845161298283856139c0565b146129a057604051630b8aa6f760e31b815260040160405180910390fd5b604080516020808252610420820190925290816020015b604080518082019091525f80825260208201528152602001906001900390816129b75790505093505f835b8651811015612aa1575f5f612a266040518060400160405280858c5f0151612a0a9190613c53565b8152602001858c60200151612a1f91906139c0565b9052612aad565b509150915060405180604001604052808383612a4291906139c0565b8152602001848b60200151612a5791906139c0565b815250888581518110612a6c57612a6c613961565b6020908102919091010152612a826001856139c0565b9350612a8e81836139c0565b612a9890846139c0565b925050506129e2565b50845250919392505050565b5f5f5f835f01515f03612ad357604051635ab458fb60e01b815260040160405180910390fd5b602084015180515f1a607f8111612af5575f60015f9450945094505050612d89565b60b78111612b8a575f612b09608083613c53565b905080875f015111612b2e576040516366c9448560e01b815260040160405180910390fd5b6001838101516001600160f81b0319169082148015612b5a5750600160ff1b6001600160f81b03198216105b15612b785760405163babb01dd60e01b815260040160405180910390fd5b506001955093505f9250612d89915050565b60bf8111612c68575f612b9e60b783613c53565b905080875f015111612bc3576040516366c9448560e01b815260040160405180910390fd5b60018301516001600160f81b0319165f819003612bf35760405163babb01dd60e01b815260040160405180910390fd5b600184015160088302610100031c60378111612c225760405163babb01dd60e01b815260040160405180910390fd5b612c2c81846139c0565b895111612c4c576040516366c9448560e01b815260040160405180910390fd5b612c578360016139c0565b975095505f9450612d899350505050565b60f78111612cb2575f612c7c60c083613c53565b905080875f015111612ca1576040516366c9448560e01b815260040160405180910390fd5b600195509350849250612d89915050565b5f612cbe60f783613c53565b905080875f015111612ce3576040516366c9448560e01b815260040160405180910390fd5b60018301516001600160f81b0319165f819003612d135760405163babb01dd60e01b815260040160405180910390fd5b600184015160088302610100031c60378111612d425760405163babb01dd60e01b815260040160405180910390fd5b612d4c81846139c0565b895111612d6c576040516366c9448560e01b815260040160405180910390fd5b612d778360016139c0565b9750955060019450612d899350505050565b9193909250565b6060816001600160401b03811115612daa57612daa613685565b6040519080825280601f01601f191660200182016040528015612dd4576020820181803683370190505b5090508115610d44575f612de884866139c0565b9050602082015f5b84811015612e08578281015182820152602001612df0565b84811115612e16575f858301525b5050509392505050565b60605f82604051602001612e3691815260200190565b60408051601f1981840301815291905290505f5b6020811015612e8257818181518110612e6557612e65613961565b01602001516001600160f81b0319165f03612e8257600101612e4a565b612e8d816020613c53565b6001600160401b03811115612ea457612ea4613685565b6040519080825280601f01601f191660200182016040528015612ece576020820181803683370190505b5092505f5b8351811015611ad4578282612ee781613d2b565b935081518110612ef957612ef9613961565b602001015160f81c60f81b848281518110612f1657612f16613961565b60200101906001600160f81b03191690815f1a905350600101612ed3565b606081516001148015612f6057506080825f81518110612f5657612f56613961565b016020015160f81c105b15612f69575090565b612f758251608061321f565b82604051602001612059929190613d43565b919050565b5f610a8584612f9c87868661206f565b8051602091820120825192909101919091201490565b815115612fc25781518083602001fd5b8060405162461bcd60e51b81526004016105d79190613d71565b8051606090806001600160401b03811115612ff957612ff9613685565b60405190808252806020026020018201604052801561303e57816020015b60408051808201909152606080825260208201528152602001906001900390816130175790505b5091505f5b81811015611ad657604051806040016040528085838151811061306857613068613961565b6020026020010151815260200161309786848151811061308a5761308a613961565b6020026020010151611f07565b8152508382815181106130ac576130ac613961565b6020908102919091010152600101613043565b606080604051905082518060011b603f8101601f191683016040528083525060208401602083015f5b83811015613115578060011b8201818401515f1a8060041c8253600f8116600183015350506001016130e8565b509295945050505050565b60606020825f01511061313b5761313682611f1a565b610721565b610721826133bc565b606061072161316283602001515f81518110611df657611df6613961565b6130bf565b606082518210613185575060408051602081019091525f8152610721565b610d4483838486516131979190613c53565b6133d0565b5f5f82518451106131ae5782516131b1565b83515b90505b808210801561320857508282815181106131d0576131d0613961565b602001015160f81c60f81b6001600160f81b0319168483815181106131f7576131f7613961565b01602001516001600160f81b031916145b15613218578160010191506131b4565b5092915050565b6060603883101561328357604080516001808252818301909252906020820181803683370190505090506132538284613da3565b60f81b815f8151811061326857613268613961565b60200101906001600160f81b03191690815f1a905350610721565b5f60015b6132918186613dbc565b156132b757816132a081613d2b565b92506132b0905061010082613dcf565b9050613287565b6132c28260016139c0565b6001600160401b038111156132d9576132d9613685565b6040519080825280601f01601f191660200182016040528015613303576020820181803683370190505b5092506133108483613da3565b61331b906037613da3565b60f81b835f8151811061333057613330613961565b60200101906001600160f81b03191690815f1a905350600190505b818111610fb45761010061335f8284613c53565b61336b90610100613ec1565b6133759087613dbc565b61337f9190613ecc565b60f81b83828151811061339457613394613961565b60200101906001600160f81b03191690815f1a905350806133b481613d2b565b91505061334b565b606061072182602001515f845f0151612d90565b60608182601f0110156134165760405162461bcd60e51b815260206004820152600e60248201526d736c6963655f6f766572666c6f7760901b60448201526064016105d7565b8282840110156134595760405162461bcd60e51b815260206004820152600e60248201526d736c6963655f6f766572666c6f7760901b60448201526064016105d7565b818301845110156134a05760405162461bcd60e51b8152602060048201526011602482015270736c6963655f6f75744f66426f756e647360781b60448201526064016105d7565b6060821580156134be5760405191505f825260208201604052613508565b6040519150601f8416801560200281840101858101878315602002848b0101015b818310156134f75780518352602092830192016134df565b5050858452601f01601f1916604052505b50949350505050565b6001600160a01b03811681146107eb575f5ffd5b5f60208284031215613535575f5ffd5b8135610d4481613511565b5f5f60408385031215613551575f5ffd5b823561355c81613511565b915060208301358015158114613570575f5ffd5b809150509250929050565b5f5f6040838503121561358c575f5ffd5b823561359781613511565b946020939093013593505050565b80356001600160401b0381168114612f87575f5ffd5b5f5f604083850312156135cc575f5ffd5b613597836135a5565b5f5f602083850312156135e6575f5ffd5b82356001600160401b038111156135fb575f5ffd5b8301601f8101851361360b575f5ffd5b80356001600160401b03811115613620575f5ffd5b8560208260051b8401011115613634575f5ffd5b6020919091019590945092505050565b5f5f5f5f60808587031215613657575f5ffd5b613660856135a5565b935060208501359250613675604086016135a5565b9396929550929360600135925050565b634e487b7160e01b5f52604160045260245ffd5b60405160c081016001600160401b03811182821017156136bb576136bb613685565b60405290565b604051601f8201601f191681016001600160401b03811182821017156136e9576136e9613685565b604052919050565b5f82601f830112613700575f5ffd5b81356001600160401b0381111561371957613719613685565b61372c601f8201601f19166020016136c1565b818152846020838601011115613740575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f5f6040838503121561376d575f5ffd5b823561377881613511565b915060208301356001600160401b03811115613792575f5ffd5b61379e858286016136f1565b9150509250929050565b5f602082840312156137b8575f5ffd5b5035919050565b5f5f5f5f5f608086880312156137d3575f5ffd5b6137dc866135a5565b945060208601356137ec81613511565b93506040860135925060608601356001600160401b0381111561380d575f5ffd5b8601601f8101881361381d575f5ffd5b80356001600160401b03811115613832575f5ffd5b886020828401011115613843575f5ffd5b959894975092955050506020019190565b5f5f5f60608486031215613866575f5ffd5b61386f846135a5565b9250602084013561387f81613511565b929592945050506040919091013590565b5f5f5f606084860312156138a2575f5ffd5b6138ab846135a5565b9250602084013591506138c0604085016135a5565b90509250925092565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b634e487b7160e01b5f52603260045260245ffd5b602080825281018290525f6001600160fb1b03831115613993575f5ffd5b8260051b80856040850137919091016040019392505050565b634e487b7160e01b5f52601160045260245ffd5b80820180821115610721576107216139ac565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f60208284031215613a2e575f5ffd5b5051919050565b5f60208284031215613a45575f5ffd5b8151610d4481613511565b5f6001600160401b03821115613a6857613a68613685565b5060051b60200190565b803560048110612f87575f5ffd5b5f82601f830112613a8f575f5ffd5b8135613aa2613a9d82613a50565b6136c1565b8082825260208201915060208360051b860101925085831115613ac3575f5ffd5b602085015b83811015613b035780356001600160401b03811115613ae5575f5ffd5b613af4886020838a01016136f1565b84525060209283019201613ac8565b5095945050505050565b5f60208284031215613b1d575f5ffd5b81356001600160401b03811115613b32575f5ffd5b8201601f81018413613b42575f5ffd5b8035613b50613a9d82613a50565b8082825260208201915060208360051b850101925086831115613b71575f5ffd5b602084015b83811015611e6f5780356001600160401b03811115613b93575f5ffd5b850160c0818a03601f19011215613ba8575f5ffd5b613bb0613699565b613bbc602083016135a5565b8152613bca604083016135a5565b602082015260608201356040820152613be560808301613a72565b606082015260a08201356001600160401b03811115613c02575f5ffd5b613c118b602083860101613a80565b60808301525060c08201356001600160401b03811115613c2f575f5ffd5b613c3e8b602083860101613a80565b60a08301525084525060209283019201613b76565b81810381811115610721576107216139ac565b634e487b7160e01b5f52602160045260245ffd5b80516020808301519190811015613c9a575f198160200360031b1b821691505b50919050565b5f5b83811015613cba578181015183820152602001613ca2565b50505f910152565b5f8251613cd3818460208701613ca0565b9190910192915050565b634e487b7160e01b5f52601260045260245ffd5b5f60ff831680613d0357613d03613cdd565b8060ff84160691505092915050565b60ff8281168282160390811115610721576107216139ac565b5f60018201613d3c57613d3c6139ac565b5060010190565b5f8351613d54818460208801613ca0565b835190830190613d68818360208801613ca0565b01949350505050565b602081525f8251806020840152613d8f816040850160208701613ca0565b601f01601f19169190910160400192915050565b60ff8181168382160190811115610721576107216139ac565b5f82613dca57613dca613cdd565b500490565b8082028115828204841417610721576107216139ac565b6001815b6001841115610e1957808504811115613e0557613e056139ac565b6001841615613e1357908102905b60019390931c928002613dea565b5f82613e2f57506001610721565b81613e3b57505f610721565b8160018114613e515760028114613e5b57613e77565b6001915050610721565b60ff841115613e6c57613e6c6139ac565b50506001821b610721565b5060208310610133831016604e8410600b8410161715613e9a575081810a610721565b613ea65f198484613de6565b805f1904821115613eb957613eb96139ac565b029392505050565b5f610d448383613e21565b5f82613eda57613eda613cdd565b50069056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122089edef9ed2078a856f2d9e19fbb38b0896c907be029eadbb45db523628a6830564736f6c634300081b0033", "balance": "0x0" }, "0x7633750000000000000000000000000000000005": { @@ -203,46 +201,44 @@ "0x0000000000000000000000000000000000000000000000000000000000000097": "0x0000000000000000000000000000000000000000000000000000000000000101", "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000003bc256069ff9af461f3e04494a3ece3f62f183fc", - "0x0000000000000000000000000000000000000000000000000000000000000065": "0x0000000000000000000000007633750000000000000000000000000000000006", "0x4ab8a6e697ab18b6f6787a1384fd4c7858d254f9f7632cd895cc7e98eea59e36": "0x0000000000000000000000000000000000000000000000000000000000000001", "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0763375000000000000000000000000000000005" }, - "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea26469706673582212205339f45a82df35ebd66aff7c8a93c20457e46388cef179bebf6908c53edcb0ec64736f6c634300081b0033", + "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033", "balance": "0x0" }, "0x0763375000000000000000000000000000010001": { - "contractName": "TaikoL2Impl", + "contractName": "TaikoAnchorImpl", "storage": { "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000003bc256069ff9af461f3e04494a3ece3f62f183fc" }, - "code": "0x6080604052600436106101e6575f3560e01c8063715018a611610108578063a86f9d9e1161009d578063dac5df781161006d578063dac5df7814610587578063e07baba61461059c578063ee82ac5e146105c1578063f2fde38b146105e0578063f940e385146105ff575f5ffd5b8063a86f9d9e146104f2578063b8c7b30c14610511578063c3f909d414610530578063da69d3db14610568575f5ffd5b80638da5cb5b116100d85780638da5cb5b1461046b5780639ee512f214610488578063a7137c0f146104ad578063a7e022d1146104d3575f5ffd5b8063715018a6146103f3578063730298e3146104075780638456cb59146104435780638abf607714610457575f5ffd5b80633ab76e9f1161017e57806352d1902d1161014e57806352d1902d1461036c578063539b8ade1461038e5780635950f9f1146103b45780635c975abb146103d3575f5ffd5b80633ab76e9f146102ef5780633eb6b8cf146103265780633f4ba83a146103455780634f1ef28614610359575f5ffd5b80633075db56116101b95780633075db561461028457806333d5ac9b14610298578063356aec04146102be5780633659cfe6146102d0575f5ffd5b80630a3c9c20146101ea57806312622e5b1461020b578063136dc4a8146102475780632f98047314610266575b5f5ffd5b3480156101f5575f5ffd5b50610209610204366004612532565b61061e565b005b348015610216575f5ffd5b5060cc5461022a906001600160401b031681565b6040516001600160401b0390911681526020015b60405180910390f35b348015610252575f5ffd5b5061022a61026136600461257e565b610a88565b348015610271575f5ffd5b505f5b604051901515815260200161023e565b34801561028f575f5ffd5b50610274610a9e565b3480156102a3575f5ffd5b5060cb5461022a90600160401b90046001600160401b031681565b3480156102c9575f5ffd5b505f61022a565b3480156102db575f5ffd5b506102096102ea3660046125d2565b610ab6565b3480156102fa575f5ffd5b5060655461030e906001600160a01b031681565b6040516001600160a01b03909116815260200161023e565b348015610331575f5ffd5b5061030e6103403660046125fa565b610b86565b348015610350575f5ffd5b50610209610b92565b61020961036736600461264b565b610ba6565b348015610377575f5ffd5b50610380610c5f565b60405190815260200161023e565b348015610399575f5ffd5b5060cb5461022a90600160801b90046001600160401b031681565b3480156103bf575f5ffd5b506102096103ce36600461270e565b610d10565b3480156103de575f5ffd5b50610274609754610100900460ff1660021490565b3480156103fe575f5ffd5b50610209610f19565b348015610412575f5ffd5b50610426610421366004612758565b610f2a565b604080519283526001600160401b0390911660208301520161023e565b34801561044e575f5ffd5b5061020961100c565b348015610462575f5ffd5b5061030e611027565b348015610476575f5ffd5b506033546001600160a01b031661030e565b348015610493575f5ffd5b5061030e71777735367b36bc9b61c50022d9d0700db4ec81565b3480156104b8575f5ffd5b5060cb5461022a90600160c01b90046001600160401b031681565b3480156104de575f5ffd5b506104266104ed3660046127a0565b611035565b3480156104fd575f5ffd5b5061030e61050c3660046127d1565b6110ab565b34801561051c575f5ffd5b5060cb5461022a906001600160401b031681565b34801561053b575f5ffd5b506105446110c0565b60408051825163ffffffff16815260209283015160ff16928101929092520161023e565b348015610573575f5ffd5b506102096105823660046127ff565b6110f1565b348015610592575f5ffd5b5061038060ca5481565b3480156105a7575f5ffd5b5060975461022a906201000090046001600160401b031681565b3480156105cc575f5ffd5b506103806105db366004612837565b6111c1565b3480156105eb575f5ffd5b506102096105fa3660046125d2565b6111f9565b34801561060a575f5ffd5b5061020961061936600461284e565b61126f565b825f8190036106405760405163ec73295960e01b815260040160405180910390fd5b846001600160401b0316805f0361066a5760405163ec73295960e01b815260040160405180910390fd5b61067a606084016040850161287a565b63ffffffff16805f036106a05760405163ec73295960e01b815260040160405180910390fd5b6106ad6020850185612893565b60ff16805f036106d05760405163ec73295960e01b815260040160405180910390fd5b3371777735367b36bc9b61c50022d9d0700db4ec1461070257604051636494e9f760e01b815260040160405180910390fd5b600261071060975460ff1690565b60ff16036107315760405163dfc60d8560e01b815260040160405180910390fd5b61073b600261142e565b5f6107476001436128c7565b90505f5f61075483611444565b915091508160ca541461077a5760405163d719258d60e01b815260040160405180910390fd5b60ca55505f61078c6020880188612893565b60ff1661079f6060890160408a0161287a565b63ffffffff166107af91906128da565b60cb549091506001600160401b03808316600160c01b909204161461084a5760cb54600160c01b90046001600160401b0316156108295760cb54610807906001600160401b0380821691600160c01b90041683610a88565b60cb805467ffffffffffffffff19166001600160401b03929092169190911790555b60cb80546001600160c01b0316600160c01b6001600160401b038416021790555b5060cb545f90819061088490899061087290600160801b90046001600160401b0316426128c7565b60cb546001600160401b03168c610f2a565b9150915061088f5f90565b15801561089c5750814814155b156108ba576040516336d54d4f60e11b815260040160405180910390fd5b60cb805467ffffffffffffffff19166001600160401b039283161790819055600160401b90048116908b16111590506109df576109086d7369676e616c5f7365727669636560901b5f6110ab565b60cc546040516313e4299d60e21b81526001600160401b0391821660048201527f73e6d340850343cc6f001515dc593377337c95a6ffe034fe1e844d4dab5da1696024820152908b166044820152606481018a90526001600160a01b039190911690634f90a674906084016020604051808303815f875af115801561098f573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109b39190612903565b5060cb80546fffffffffffffffff00000000000000001916600160401b6001600160401b038c16021790555b5f81815260c960205260409081902082409081905560cb80546001600160401b03428116600160801b0267ffffffffffffffff60801b1983168117909355935192937f41c3f410f5c8ac36bb46b1dccef0de0f964087c9e688795fa02ecfa2c20b3fe493610a6a938693908316921691909117909182526001600160401b0316602082015260400190565b60405180910390a15050610a7e600161142e565b5050505050505050565b5f610a948484846114d4565b90505b9392505050565b5f6002610aad60975460ff1690565b60ff1614905090565b6001600160a01b037f0000000000000000000000000763375000000000000000000000000000010001163003610b075760405162461bcd60e51b8152600401610afe9061291a565b60405180910390fd5b7f00000000000000000000000007633750000000000000000000000000000100016001600160a01b0316610b3961158f565b6001600160a01b031614610b5f5760405162461bcd60e51b8152600401610afe90612966565b610b68816115aa565b604080515f80825260208201909252610b83918391906115b2565b50565b5f610a9484848461171c565b610b9a61176e565b610ba4335f6117fe565b565b6001600160a01b037f0000000000000000000000000763375000000000000000000000000000010001163003610bee5760405162461bcd60e51b8152600401610afe9061291a565b7f00000000000000000000000007633750000000000000000000000000000100016001600160a01b0316610c2061158f565b6001600160a01b031614610c465760405162461bcd60e51b8152600401610afe90612966565b610c4f826115aa565b610c5b828260016115b2565b5050565b5f306001600160a01b037f00000000000000000000000007633750000000000000000000000000000100011614610cfe5760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610afe565b505f516020612b535f395f51905f5290565b5f54610100900460ff1615808015610d2e57505f54600160ff909116105b80610d475750303b158015610d4757505f5460ff166001145b610daa5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610afe565b5f805460ff191660011790558015610dcb575f805461ff0019166101001790555b610dd58585611806565b6001600160401b0383161580610df3575046836001600160401b0316145b15610e11576040516308279a2560e31b815260040160405180910390fd5b600146111580610e2757506001600160401b0346115b15610e4557604051638f972ecb60e01b815260040160405180910390fd5b4315610e8f5743600103610e76575f610e5f6001436128c7565b5f81815260c9602052604090209040905550610e8f565b604051635a0f9e4160e11b815260040160405180910390fd5b60cc80546001600160401b0380861667ffffffffffffffff199283161790925560cb805492851692909116919091179055610ec943611444565b5060ca558015610f12575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b610f21611840565b610ba45f61189a565b5f8080610f3d606088016040890161287a565b610f4d9063ffffffff16876128da565b9050610f5f60a08801608089016129b2565b6001600160401b031615801590610f965750610f8160a08801608089016129b2565b6001600160401b0316816001600160401b0316115b15610fae57610fab60a08801608089016129b2565b90505b5f610fbc6020890189612893565b60ff16610fcf60608a0160408b0161287a565b63ffffffff16610fdf91906129cb565b9050610ffd81878488610ff860808e0160608f016129b2565b6118eb565b93509350505094509492505050565b611014611974565b61101c61198d565b610ba43360016117fe565b5f61103061158f565b905090565b5f5f5f6110406110c0565b905061109f816020015160ff16825f015163ffffffff1661106191906129cb565b60cb5483516001600160401b038083169263ffffffff9092169161108e91600160401b909104168a6129e2565b61109891906128da565b875f6118eb565b90969095509350505050565b5f6110b746848461171c565b90505b92915050565b604080518082019091525f808252602082015261103060408051808201909152630393870081526008602082015290565b825f8190036111135760405163ec73295960e01b815260040160405180910390fd5b826001600160401b0316805f0361113d5760405163ec73295960e01b815260040160405180910390fd5b3371777735367b36bc9b61c50022d9d0700db4ec1461116f57604051636494e9f760e01b815260040160405180910390fd5b600261117d60975460ff1690565b60ff160361119e5760405163dfc60d8560e01b815260040160405180910390fd5b6111a8600261142e565b604051631799c89b60e01b815260040160405180910390fd5b5f4382106111d057505f919050565b436111dd83610100612a01565b106111e757504090565b505f90815260c9602052604090205490565b611201611840565b6001600160a01b0381166112665760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610afe565b610b838161189a565b611283609754610100900460ff1660021490565b156112a15760405163bae6e2a960e01b815260040160405180910390fd5b693bb4ba34323930bbb2b960b11b6112c16033546001600160a01b031690565b6001600160a01b0316336001600160a01b0316141580156112fe57506112e88160016110ab565b6001600160a01b0316336001600160a01b031614155b1561131c57604051630d85cccf60e11b815260040160405180910390fd5b600261132a60975460ff1690565b60ff160361134b5760405163dfc60d8560e01b815260040160405180910390fd5b611355600261142e565b6001600160a01b03821661137c5760405163053fd54760e01b815260040160405180910390fd5b6001600160a01b0383166113a25761139d6001600160a01b038316476119fe565b61141f565b6040516370a0823160e01b815230600482015261141f9083906001600160a01b038616906370a0823190602401602060405180830381865afa1580156113ea573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061140e9190612903565b6001600160a01b0386169190611a09565b611429600161142e565b505050565b6097805460ff191660ff92909216919091179055565b5f5f61144e6124d4565b5f5b60ff811080156114635750806001018510155b15611494575f198186030180408360ff8306610100811061148657611486612a28565b602002015250600101611450565b5046611fe0820152612000812092508340816114b160ff87612a3c565b61010081106114c2576114c2612a28565b60200201526120009020919391925050565b5f826001600160401b03165f036114ec575080610a97565b670de0b6b3a76400005f6001600160401b038086169061150e908616846129cb565b6115189190612a4f565b90506001600160ff1b0381111561154257604051636296f1b960e11b815260040160405180910390fd5b5f61154c82611a5b565b90505f828802828702015f81126001811461156b57858204925061156f565b5f92505b506115839050816001600160401b03611c78565b98975050505050505050565b5f516020612b535f395f51905f52546001600160a01b031690565b610b83611840565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156115e55761142983611c8c565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa92505050801561163f575060408051601f3d908101601f1916820190925261163c91810190612903565b60015b6116a25760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610afe565b5f516020612b535f395f51905f5281146117105760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610afe565b50611429838383611d27565b5f6117278484611d51565b90508115801561173e57506001600160a01b038116155b15610a9757604051632b0d65db60e01b81526001600160401b038516600482015260248101849052604401610afe565b611782609754610100900460ff1660021490565b61179f5760405163bae6e2a960e01b815260040160405180910390fd5b6097805461010069ffffffffffffffffff001990911662010000426001600160401b031602171790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa906020015b60405180910390a1565b610c5b611840565b806001600160a01b03811661182e5760405163538ba4f960e01b815260040160405180910390fd5b61183783611dfb565b61142982611e59565b6033546001600160a01b03163314610ba45760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610afe565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f808061190763ffffffff86166001600160401b038916612a01565b9050856001600160401b03168111611920576001611933565b6119336001600160401b038716826128c7565b90506119526001600160401b0361194c83878316611ec9565b90611c78565b9150611967826001600160401b031689611ede565b9250509550959350505050565b60405163a87dd7cf60e01b815260040160405180910390fd5b6119a1609754610100900460ff1660021490565b156119bf5760405163bae6e2a960e01b815260040160405180910390fd5b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258906020016117f4565b610c5b82825a611f0d565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b179052611429908490611f50565b6fffffffffffffffffffffffffffffffff811160071b81811c6001600160401b031060061b1781811c63ffffffff1060051b1781811c61ffff1060041b1781811c60ff1060031b175f8213611ab757631615e6385f526004601cfd5b7ff8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff6f8421084210842108cc6318c6db6d54be83831c1c601f161a1890811b609f90811c6c465772b2bbbb5f824b15207a3081018102606090811d6d0388eaa27412d5aca026815d636e018202811d6d0df99ac502031bf953eff472fdcc018202811d6d13cdffb29d51d99322bdff5f2211018202811d6d0a0f742023def783a307a986912e018202811d6d01920d8043ca89b5239253284e42018202811d6c0b7a86d7375468fac667a0a527016c29508e458543d8aa4df2abee7883018302821d6d0139601a2efabe717e604cbb4894018302821d6d02247f7a7b6594320649aa03aba1018302821d6c8c3f38e95a6b1ff2ab1c3b343619018302821d6d02384773bdf1ac5676facced60901901830290911d6cb9a025d814b29c212b8b1a07cd1901909102780a09507084cc699bb0e71ea869ffffffffffffffffffffffff190105711340daa0d5f769dba1915cef59f0815a5506029190037d0267a36c0c95b3975ab3ee5b203a7614a3f75373f047d803ae7b6687f2b302017d57115e47018c7177eebf7cd370a3356a1b7863008a5ae8028c72b88642840160ae1d90565b5f818311611c8657826110b7565b50919050565b6001600160a01b0381163b611cf95760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610afe565b5f516020612b535f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b611d3083612023565b5f82511180611d3c5750805b1561142957611d4b8383612062565b50505050565b6065545f906001600160a01b031680611d7d57604051638ed88b2560e01b815260040160405180910390fd5b604051630a3dc4f360e21b81526001600160401b0385166004820152602481018490526001600160a01b038216906328f713cc90604401602060405180830381865afa158015611dcf573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611df39190612a62565b949350505050565b5f54610100900460ff16611e215760405162461bcd60e51b8152600401610afe90612a7d565b611e29612087565b611e476001600160a01b03821615611e41578161189a565b3361189a565b506097805461ff001916610100179055565b5f54610100900460ff16611e7f5760405162461bcd60e51b8152600401610afe90612a7d565b6001600160401b03461115611ea75760405163a12e8fa960e01b815260040160405180910390fd5b606580546001600160a01b0319166001600160a01b0392909216919091179055565b5f818311611ed757816110b7565b5090919050565b5f5f82611eeb85856120ad565b611ef59190612a4f565b90508015611f035780611df3565b5060019392505050565b815f03611f1957505050565b611f3383838360405180602001604052805f815250612127565b61142957604051634c67134d60e11b815260040160405180910390fd5b5f611fa4826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166121649092919063ffffffff16565b905080515f1480611fc4575080806020019051810190611fc49190612ac8565b6114295760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610afe565b61202c81611c8c565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606110b78383604051806060016040528060278152602001612b7360279139612172565b5f54610100900460ff16610ba45760405162461bcd60e51b8152600401610afe90612a7d565b5f815f036120ce57604051636296f1b960e11b815260040160405180910390fd5b5f826120e285670de0b6b3a76400006129cb565b6120ec9190612a4f565b9050680755bf798b4a1bf1e481111561210b5750680755bf798b4a1bf1e45b670de0b6b3a764000061211d826121e6565b611df39190612a4f565b5f6001600160a01b03851661214f57604051634c67134d60e11b815260040160405180910390fd5b5f5f835160208501878988f195945050505050565b6060610a9484845f85612360565b60605f5f856001600160a01b03168560405161218e9190612b05565b5f60405180830381855af49150503d805f81146121c6576040519150601f19603f3d011682016040523d82523d5f602084013e6121cb565b606091505b50915091506121dc86838387612437565b9695505050505050565b5f68023f2fa8f6da5b9d281982136121fd57919050565b680755bf798b4a1bf1e5821261221a5763a37bfec95f526004601cfd5b6503782dace9d9604e83901b0591505f60606bb17217f7d1cf79abc9e3b39884821b056001605f1b01901d6bb17217f7d1cf79abc9e3b39881029093036c240c330e9fb2d9cbaf0fd5aafb1981018102606090811d6d0277594991cfc85f6e2461837cd9018202811d6d1a521255e34f6a5061b25ef1c9c319018202811d6db1bbb201f443cf962f1a1d3db4a5018202811d6e02c72388d9f74f51a9331fed693f1419018202811d6e05180bb14799ab47a8a8cb2a527d57016d02d16720577bd19bf614176fe9ea6c10fe68e7fd37d0007b713f765084018402831d9081019084016d01d3967ed30fc4f89c02bab5708119010290911d6e0587f503bb6ea29d25fcb740196450019091026d360d7aeea093263ecc6e0ecb291760621b010574029d9dc38563c32e5c2f6dc192ee70ef65f9978af30260c3939093039290921c92915050565b6060824710156123c15760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610afe565b5f5f866001600160a01b031685876040516123dc9190612b05565b5f6040518083038185875af1925050503d805f8114612416576040519150601f19603f3d011682016040523d82523d5f602084013e61241b565b606091505b509150915061242c87838387612437565b979650505050505050565b606083156124a55782515f0361249e576001600160a01b0385163b61249e5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610afe565b5081611df3565b611df383838151156124ba5781518083602001fd5b8060405162461bcd60e51b8152600401610afe9190612b20565b604051806120000160405280610100906020820280368337509192915050565b80356001600160401b038116811461250a575f5ffd5b919050565b803563ffffffff8116811461250a575f5ffd5b5f60a08284031215611c86575f5ffd5b5f5f5f5f6101008587031215612546575f5ffd5b61254f856124f4565b9350602085013592506125646040860161250f565b91506125738660608701612522565b905092959194509250565b5f5f5f60608486031215612590575f5ffd5b612599846124f4565b92506125a7602085016124f4565b91506125b5604085016124f4565b90509250925092565b6001600160a01b0381168114610b83575f5ffd5b5f602082840312156125e2575f5ffd5b8135610a97816125be565b8015158114610b83575f5ffd5b5f5f5f6060848603121561260c575f5ffd5b612615846124f4565b925060208401359150604084013561262c816125ed565b809150509250925092565b634e487b7160e01b5f52604160045260245ffd5b5f5f6040838503121561265c575f5ffd5b8235612667816125be565b915060208301356001600160401b03811115612681575f5ffd5b8301601f81018513612691575f5ffd5b80356001600160401b038111156126aa576126aa612637565b604051601f8201601f19908116603f011681016001600160401b03811182821017156126d8576126d8612637565b6040528181528282016020018710156126ef575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f5f5f5f60808587031215612721575f5ffd5b843561272c816125be565b9350602085013561273c816125be565b925061274a604086016124f4565b9150612573606086016124f4565b5f5f5f5f610100858703121561276c575f5ffd5b6127768686612522565b935061278460a086016124f4565b925061279260c086016124f4565b915061257360e0860161250f565b5f5f604083850312156127b1575f5ffd5b6127ba836124f4565b91506127c86020840161250f565b90509250929050565b5f5f604083850312156127e2575f5ffd5b8235915060208301356127f4816125ed565b809150509250929050565b5f5f5f5f60808587031215612812575f5ffd5b8435935060208501359250612829604086016124f4565b91506125736060860161250f565b5f60208284031215612847575f5ffd5b5035919050565b5f5f6040838503121561285f575f5ffd5b823561286a816125be565b915060208301356127f4816125be565b5f6020828403121561288a575f5ffd5b6110b78261250f565b5f602082840312156128a3575f5ffd5b813560ff81168114610a97575f5ffd5b634e487b7160e01b5f52601160045260245ffd5b818103818111156110ba576110ba6128b3565b6001600160401b0381811683821602908116908181146128fc576128fc6128b3565b5092915050565b5f60208284031215612913575f5ffd5b5051919050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b5f602082840312156129c2575f5ffd5b6110b7826124f4565b80820281158282048414176110ba576110ba6128b3565b6001600160401b0382811682821603908111156110ba576110ba6128b3565b808201808211156110ba576110ba6128b3565b634e487b7160e01b5f52601260045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b5f82612a4a57612a4a612a14565b500690565b5f82612a5d57612a5d612a14565b500490565b5f60208284031215612a72575f5ffd5b8151610a97816125be565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f60208284031215612ad8575f5ffd5b8151610a97816125ed565b5f5b83811015612afd578181015183820152602001612ae5565b50505f910152565b5f8251612b16818460208701612ae3565b9190910192915050565b602081525f8251806020840152612b3e816040850160208701612ae3565b601f01601f1916919091016040019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122002c15fc7dd0ed0910b9e5acf1b12c883f4ea39c7673d453c6f06bbbfda24cec764736f6c634300081b0033", + "code": "0x6080604052600436106101e6575f3560e01c8063893f546011610108578063b8c7b30c1161009d578063e902461a1161006d578063e902461a146105ae578063ee82ac5e146105c8578063f2fde38b146105e7578063f940e38514610606578063fd85eb2d14610625575f5ffd5b8063b8c7b30c14610528578063ba9f41e814610547578063da69d3db1461057a578063dac5df7814610599575f5ffd5b8063a4b23554116100d8578063a4b235541461028b578063a7137c0f146104a7578063a7e022d1146104cd578063b310e9e914610509575f5ffd5b8063893f54601461040d5780638abf6077146104515780638da5cb5b146104655780639ee512f214610482575f5ffd5b806348080a451161017e5780635c975abb1161014e5780635c975abb1461039257806362d09453146103b2578063715018a6146103e55780638456cb59146103f9575f5ffd5b806348080a45146103185780634f1ef2861461033757806352d1902d1461034a578063539b8ade1461036c575f5ffd5b80633075db56116101b95780633075db56146102a957806333d5ac9b146102bd5780633659cfe6146102e35780633f4ba83a14610304575f5ffd5b806304f3bcec146101ea57806312622e5b14610235578063136dc4a81461026c5780632f9804731461028b575b5f5ffd5b3480156101f5575f5ffd5b507f00000000000000000000000076337500000000000000000000000000000000065b6040516001600160a01b0390911681526020015b60405180910390f35b348015610240575f5ffd5b5060cc54610254906001600160401b031681565b6040516001600160401b03909116815260200161022c565b348015610277575f5ffd5b50610254610286366004612575565b610644565b348015610296575f5ffd5b505f5b604051901515815260200161022c565b3480156102b4575f5ffd5b5061029961065e565b3480156102c8575f5ffd5b5060cb5461025490600160401b90046001600160401b031681565b3480156102ee575f5ffd5b506103026102fd3660046125c9565b610676565b005b34801561030f575f5ffd5b50610302610746565b348015610323575f5ffd5b5061030261033236600461260e565b6107d2565b6103026103453660046126ce565b61098d565b348015610355575f5ffd5b5061035e610a46565b60405190815260200161022c565b348015610377575f5ffd5b5060cb5461025490600160801b90046001600160401b031681565b34801561039d575f5ffd5b50610299609754610100900460ff1660021490565b3480156103bd575f5ffd5b506102187f000000000000000000000000763375000000000000000000000000000000000581565b3480156103f0575f5ffd5b50610302610af7565b348015610404575f5ffd5b50610302610b08565b348015610418575f5ffd5b5061042c610427366004612791565b610b8f565b604080519384526001600160401b03928316602085015291169082015260600161022c565b34801561045c575f5ffd5b50610218610cbb565b348015610470575f5ffd5b506033546001600160a01b0316610218565b34801561048d575f5ffd5b5061021871777735367b36bc9b61c50022d9d0700db4ec81565b3480156104b2575f5ffd5b5060cb5461025490600160c01b90046001600160401b031681565b3480156104d8575f5ffd5b506104ec6104e73660046127c9565b610cc9565b604080519283526001600160401b0390911660208301520161022c565b348015610514575f5ffd5b506103026105233660046127fa565b610ce4565b348015610533575f5ffd5b5060cb54610254906001600160401b031681565b348015610552575f5ffd5b506102547f000000000000000000000000000000000000000000000000000000000000000081565b348015610585575f5ffd5b50610302610594366004612817565b610f16565b3480156105a4575f5ffd5b5061035e60ca5481565b3480156105b9575f5ffd5b506104ec6104e736600461285a565b3480156105d3575f5ffd5b5061035e6105e23660046128a2565b610f2f565b3480156105f2575f5ffd5b506103026106013660046125c9565b610f67565b348015610611575f5ffd5b506103026106203660046128b9565b610fdd565b348015610630575f5ffd5b5061030261063f3660046128f0565b611194565b5f6040516372c0090b60e11b815260040160405180910390fd5b5f600261066d60975460ff1690565b60ff1614905090565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000100011630036106c75760405162461bcd60e51b81526004016106be90612931565b60405180910390fd5b7f00000000000000000000000007633750000000000000000000000000000100016001600160a01b03166106f9611339565b6001600160a01b03161461071f5760405162461bcd60e51b81526004016106be9061297d565b61072881611354565b604080515f808252602082019092526107439183919061135c565b50565b61075a609754610100900460ff1660021490565b6107775760405163bae6e2a960e01b815260040160405180910390fd5b61077f6114cb565b6107936097805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a16107d0335f6114e4565b565b845f8190036107f45760405163ec73295960e01b815260040160405180910390fd5b866001600160401b0316805f0361081e5760405163ec73295960e01b815260040160405180910390fd5b3371777735367b36bc9b61c50022d9d0700db4ec1461085057604051636494e9f760e01b815260040160405180910390fd5b600261085e60975460ff1690565b60ff160361087f5760405163dfc60d8560e01b815260040160405180910390fd5b61088960026114ec565b7f00000000000000000000000000000000000000000000000000000000000000006001600160401b03164310156108d357604051631799c89b60e01b815260040160405180910390fd5b5f6108df6001436129dd565b90506108ea81611502565b6108f4898961153a565b6108fd81611658565b604051633b78c86560e01b81526001600160a01b037f00000000000000000000000076337500000000000000000000000000000000051690633b78c8659061094b90889088906004016129f0565b5f604051808303815f87803b158015610962575f5ffd5b505af1158015610974573d5f5f3e3d5ffd5b505050505061098360016114ec565b5050505050505050565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000100011630036109d55760405162461bcd60e51b81526004016106be90612931565b7f00000000000000000000000007633750000000000000000000000000000100016001600160a01b0316610a07611339565b6001600160a01b031614610a2d5760405162461bcd60e51b81526004016106be9061297d565b610a3682611354565b610a428282600161135c565b5050565b5f306001600160a01b037f00000000000000000000000007633750000000000000000000000000000100011614610ae55760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c000000000000000060648201526084016106be565b505f516020612c595f395f51905f5290565b610aff6116ef565b6107d05f611749565b610b1c609754610100900460ff1660021490565b15610b3a5760405163bae6e2a960e01b815260040160405180910390fd5b610b426114cb565b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a16107d03360016114e4565b5f808080610ba06020860186612a27565b60ff16610bb36060870160408801612a47565b63ffffffff16610bc39190612a60565b60cb54909150610be7906001600160401b03600160c01b820481169184911661179a565b90935091505f610bfd6060870160408801612a47565b63ffffffff1660cb60109054906101000a90046001600160401b031688610c249190612a89565b610c2e9190612a60565b9050610c4060a0870160808801612a47565b63ffffffff1615801590610c715750610c5f60a0870160808801612a47565b63ffffffff16816001600160401b0316115b15610c8f57610c8660a0870160808801612a47565b63ffffffff1690505b610cab8484838b610ca660808c0160608d01612aa8565b6118b2565b9099949850965092945050505050565b5f610cc4611339565b905090565b5f5f6040516372c0090b60e11b815260040160405180910390fd5b5f54610100900460ff1615808015610d0257505f54600160ff909116105b80610d1b5750303b158015610d1b57505f5460ff166001145b610d7e5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016106be565b5f805460ff191660011790558015610d9f575f805461ff0019166101001790555b610da884611932565b826001600160401b03165f03610dd1576040516308279a2560e31b815260040160405180910390fd5b46836001600160401b031603610dfa576040516308279a2560e31b815260040160405180910390fd5b60014611610e1b57604051638f972ecb60e01b815260040160405180910390fd5b6001600160401b03461115610e4357604051638f972ecb60e01b815260040160405180910390fd5b4315610e8d5743600103610e74575f610e5d6001436129dd565b5f81815260c9602052604090209040905550610e8d565b604051635a0f9e4160e11b815260040160405180910390fd5b60cc80546001600160401b0380861667ffffffffffffffff199283161790925560cb805492851692909116919091179055610ec743611990565b5060ca558015610f10575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050565b6040516372c0090b60e11b815260040160405180910390fd5b5f438210610f3e57505f919050565b43610f4b83610100612ac1565b10610f5557504090565b505f90815260c9602052604090205490565b610f6f6116ef565b6001600160a01b038116610fd45760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016106be565b61074381611749565b806001600160a01b0381166110055760405163538ba4f960e01b815260040160405180910390fd5b611019609754610100900460ff1660021490565b156110375760405163bae6e2a960e01b815260040160405180910390fd5b693bb4ba34323930bbb2b960b11b6110576033546001600160a01b031690565b6001600160a01b0316336001600160a01b03161480611091575061107c816001611a20565b6001600160a01b0316336001600160a01b0316145b6110ae576040516395383ea160e01b815260040160405180910390fd5b60026110bc60975460ff1690565b60ff16036110dd5760405163dfc60d8560e01b815260040160405180910390fd5b6110e760026114ec565b6001600160a01b03841661110d576111086001600160a01b03841647611ac2565b61118a565b6040516370a0823160e01b815230600482015261118a9084906001600160a01b038716906370a0823190602401602060405180830381865afa158015611155573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111799190612ad4565b6001600160a01b0387169190611acd565b610f1060016114ec565b825f8190036111b65760405163ec73295960e01b815260040160405180910390fd5b846001600160401b0316805f036111e05760405163ec73295960e01b815260040160405180910390fd5b6111f06060840160408501612a47565b63ffffffff16805f036112165760405163ec73295960e01b815260040160405180910390fd5b6112236020850185612a27565b60ff16805f036112465760405163ec73295960e01b815260040160405180910390fd5b3371777735367b36bc9b61c50022d9d0700db4ec1461127857604051636494e9f760e01b815260040160405180910390fd5b600261128660975460ff1690565b60ff16036112a75760405163dfc60d8560e01b815260040160405180910390fd5b6112b160026114ec565b7f00000000000000000000000000000000000000000000000000000000000000006001600160401b031643106112fa57604051631799c89b60e01b815260040160405180910390fd5b5f6113066001436129dd565b905061131181611502565b61131b8787611b1f565b611325898961153a565b61132e81611658565b5061098360016114ec565b5f516020612c595f395f51905f52546001600160a01b031690565b6107436116ef565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156113945761138f83611c06565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156113ee575060408051601f3d908101601f191682019092526113eb91810190612ad4565b60015b6114515760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b60648201526084016106be565b5f516020612c595f395f51905f5281146114bf5760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b60648201526084016106be565b5061138f838383611ca1565b60405163a87dd7cf60e01b815260040160405180910390fd5b610a426116ef565b6097805460ff191660ff92909216919091179055565b5f5f61150d83611990565b915091508160ca54146115335760405163d719258d60e01b815260040160405180910390fd5b60ca555050565b60cb546001600160401b03600160401b90910481169083161161155b575050565b60cc546040516313e4299d60e21b81526001600160401b0391821660048201527f73e6d340850343cc6f001515dc593377337c95a6ffe034fe1e844d4dab5da16960248201529083166044820152606481018290527f00000000000000000000000076337500000000000000000000000000000000056001600160a01b031690634f90a674906084016020604051808303815f875af1158015611600573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116249190612ad4565b505060cb80546001600160401b03909216600160401b026fffffffffffffffff000000000000000019909216919091179055565b5f81815260c960205260409081902082409081905560cb80546001600160401b03428116600160801b0267ffffffffffffffff60801b1983168117909355935192937f41c3f410f5c8ac36bb46b1dccef0de0f964087c9e688795fa02ecfa2c20b3fe4936116e3938693908316921691909117909182526001600160401b0316602082015260400190565b60405180910390a15050565b6033546001600160a01b031633146107d05760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016106be565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f80670de0b6b3a76400006001600160401b03861682036117c157848492509250506118aa565b6001600160401b03851615806117e85750846001600160401b0316866001600160401b0316145b8061180657506117f9815f19612aff565b856001600160401b031610155b1561181757858492509250506118aa565b5f866001600160401b0316866001600160401b0316836118379190612b12565b6118419190612aff565b905080158061185657506001600160ff1b0381115b156118685785859350935050506118aa565b5f61187282611cc5565b90505f828702828902015f811260018114611891578582049250611895565b5f92505b5050876118a182611ee2565b95509550505050505b935093915050565b5f80806118ce63ffffffff86166001600160401b038916612ac1565b9050856001600160401b031681116118e75760016118fa565b6118fa6001600160401b038716826129dd565b90506119196001600160401b0361191383878316611ef4565b90611f09565b91506119258883611f1d565b9250509550959350505050565b5f54610100900460ff166119585760405162461bcd60e51b81526004016106be90612b29565b611960611f5f565b61197e6001600160a01b038216156119785781611749565b33611749565b506097805461ff001916610100179055565b5f5f61199a61253a565b46611fe08201525f5b60ff811080156119b65750806001018510155b156119e7575f198186030180408360ff830661010081106119d9576119d9612b74565b6020020152506001016119a3565b50612000812092508340816119fd60ff87612b88565b6101008110611a0e57611a0e612b74565b60200201526120009020919391925050565b5f7f0000000000000000000000007633750000000000000000000000000000000006604051633632b1fb60e11b81524660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa158015611a95573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611ab99190612b9b565b90505b92915050565b610a4282825a611f85565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b17905261138f908490611fc8565b5f5f5f611b2d854286610b8f565b92509250925082481480611b3e57505f5b611b5b576040516336d54d4f60e11b815260040160405180910390fd5b60cb5460408051600160c01b83046001600160401b039081168252858116602083015292831681830152918316606083015260808201859052517f781ae5c2215806150d5c71a4ed5336e5dc3ad32aef04fc0f626a6ee0c2f8d1c89181900360a00190a160cb805477ffffffffffffffffffffffffffffffff000000000000000016600160c01b6001600160401b039485160267ffffffffffffffff19161791909216179055505050565b6001600160a01b0381163b611c735760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016106be565b5f516020612c595f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b611caa8361209b565b5f82511180611cb65750805b1561138f57610f1083836120da565b6fffffffffffffffffffffffffffffffff811160071b81811c6001600160401b031060061b1781811c63ffffffff1060051b1781811c61ffff1060041b1781811c60ff1060031b175f8213611d2157631615e6385f526004601cfd5b7ff8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff6f8421084210842108cc6318c6db6d54be83831c1c601f161a1890811b609f90811c6c465772b2bbbb5f824b15207a3081018102606090811d6d0388eaa27412d5aca026815d636e018202811d6d0df99ac502031bf953eff472fdcc018202811d6d13cdffb29d51d99322bdff5f2211018202811d6d0a0f742023def783a307a986912e018202811d6d01920d8043ca89b5239253284e42018202811d6c0b7a86d7375468fac667a0a527016c29508e458543d8aa4df2abee7883018302821d6d0139601a2efabe717e604cbb4894018302821d6d02247f7a7b6594320649aa03aba1018302821d6c8c3f38e95a6b1ff2ab1c3b343619018302821d6d02384773bdf1ac5676facced60901901830290911d6cb9a025d814b29c212b8b1a07cd1901909102780a09507084cc699bb0e71ea869ffffffffffffffffffffffff190105711340daa0d5f769dba1915cef59f0815a5506029190037d0267a36c0c95b3975ab3ee5b203a7614a3f75373f047d803ae7b6687f2b302017d57115e47018c7177eebf7cd370a3356a1b7863008a5ae8028c72b88642840160ae1d90565b5f611abc826001600160401b03611f09565b5f818311611f025781611ab9565b5090919050565b5f818311611f175782611ab9565b50919050565b5f826001600160401b03165f03611f3657506001611abc565b611ab96001846001600160401b0316611f4f86866120ff565b611f599190612aff565b90611ef4565b5f54610100900460ff166107d05760405162461bcd60e51b81526004016106be90612b29565b815f03611f9157505050565b611fab83838360405180602001604052805f81525061218d565b61138f57604051634c67134d60e11b815260040160405180910390fd5b5f61201c826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166121ca9092919063ffffffff16565b905080515f148061203c57508080602001905181019061203c9190612bb6565b61138f5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016106be565b6120a481611c06565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060611ab98383604051806060016040528060278152602001612c79602791396121d8565b5f826001600160401b03165f0361211857612118612bd5565b5f836001600160401b0316836001600160401b0316670de0b6b3a76400006121409190612b12565b61214a9190612aff565b9050680755bf798b4a1bf1e48111156121695750680755bf798b4a1bf1e45b670de0b6b3a764000061217b8261224c565b6121859190612aff565b949350505050565b5f6001600160a01b0385166121b557604051634c67134d60e11b815260040160405180910390fd5b5f5f835160208501878988f195945050505050565b606061218584845f856123c6565b60605f5f856001600160a01b0316856040516121f49190612c0b565b5f60405180830381855af49150503d805f811461222c576040519150601f19603f3d011682016040523d82523d5f602084013e612231565b606091505b50915091506122428683838761249d565b9695505050505050565b5f68023f2fa8f6da5b9d2819821361226357919050565b680755bf798b4a1bf1e582126122805763a37bfec95f526004601cfd5b6503782dace9d9604e83901b0591505f60606bb17217f7d1cf79abc9e3b39884821b056001605f1b01901d6bb17217f7d1cf79abc9e3b39881029093036c240c330e9fb2d9cbaf0fd5aafb1981018102606090811d6d0277594991cfc85f6e2461837cd9018202811d6d1a521255e34f6a5061b25ef1c9c319018202811d6db1bbb201f443cf962f1a1d3db4a5018202811d6e02c72388d9f74f51a9331fed693f1419018202811d6e05180bb14799ab47a8a8cb2a527d57016d02d16720577bd19bf614176fe9ea6c10fe68e7fd37d0007b713f765084018402831d9081019084016d01d3967ed30fc4f89c02bab5708119010290911d6e0587f503bb6ea29d25fcb740196450019091026d360d7aeea093263ecc6e0ecb291760621b010574029d9dc38563c32e5c2f6dc192ee70ef65f9978af30260c3939093039290921c92915050565b6060824710156124275760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016106be565b5f5f866001600160a01b031685876040516124429190612c0b565b5f6040518083038185875af1925050503d805f811461247c576040519150601f19603f3d011682016040523d82523d5f602084013e612481565b606091505b50915091506124928783838761249d565b979650505050505050565b6060831561250b5782515f03612504576001600160a01b0385163b6125045760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016106be565b5081612185565b61218583838151156125205781518083602001fd5b8060405162461bcd60e51b81526004016106be9190612c26565b604051806120000160405280610100906020820280368337509192915050565b80356001600160401b0381168114612570575f5ffd5b919050565b5f5f5f60608486031215612587575f5ffd5b6125908461255a565b925061259e6020850161255a565b91506125ac6040850161255a565b90509250925092565b6001600160a01b0381168114610743575f5ffd5b5f602082840312156125d9575f5ffd5b81356125e4816125b5565b9392505050565b803563ffffffff81168114612570575f5ffd5b5f60a08284031215611f17575f5ffd5b5f5f5f5f5f5f6101208789031215612624575f5ffd5b61262d8761255a565b955060208701359450612642604088016125eb565b935061265188606089016125fe565b92506101008701356001600160401b0381111561266c575f5ffd5b8701601f8101891361267c575f5ffd5b80356001600160401b03811115612691575f5ffd5b8960208260051b84010111156126a5575f5ffd5b60208201935080925050509295509295509295565b634e487b7160e01b5f52604160045260245ffd5b5f5f604083850312156126df575f5ffd5b82356126ea816125b5565b915060208301356001600160401b03811115612704575f5ffd5b8301601f81018513612714575f5ffd5b80356001600160401b0381111561272d5761272d6126ba565b604051601f8201601f19908116603f011681016001600160401b038111828210171561275b5761275b6126ba565b604052818152828201602001871015612772575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f5f5f60e084860312156127a3575f5ffd5b6127ac846125eb565b92506127ba6020850161255a565b91506125ac85604086016125fe565b5f5f604083850312156127da575f5ffd5b6127e38361255a565b91506127f1602084016125eb565b90509250929050565b5f5f5f6060848603121561280c575f5ffd5b8335612590816125b5565b5f5f5f5f6080858703121561282a575f5ffd5b84359350602085013592506128416040860161255a565b915061284f606086016125eb565b905092959194509250565b5f5f5f5f610100858703121561286e575f5ffd5b61287886866125fe565b935061288660a0860161255a565b925061289460c0860161255a565b915061284f60e086016125eb565b5f602082840312156128b2575f5ffd5b5035919050565b5f5f604083850312156128ca575f5ffd5b82356128d5816125b5565b915060208301356128e5816125b5565b809150509250929050565b5f5f5f5f6101008587031215612904575f5ffd5b61290d8561255a565b935060208501359250612922604086016125eb565b915061284f86606087016125fe565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b634e487b7160e01b5f52601160045260245ffd5b81810381811115611abc57611abc6129c9565b602080825281018290525f6001600160fb1b03831115612a0e575f5ffd5b8260051b80856040850137919091016040019392505050565b5f60208284031215612a37575f5ffd5b813560ff811681146125e4575f5ffd5b5f60208284031215612a57575f5ffd5b611ab9826125eb565b6001600160401b038181168382160290811690818114612a8257612a826129c9565b5092915050565b6001600160401b038281168282160390811115611abc57611abc6129c9565b5f60208284031215612ab8575f5ffd5b611ab98261255a565b80820180821115611abc57611abc6129c9565b5f60208284031215612ae4575f5ffd5b5051919050565b634e487b7160e01b5f52601260045260245ffd5b5f82612b0d57612b0d612aeb565b500490565b8082028115828204841417611abc57611abc6129c9565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b634e487b7160e01b5f52603260045260245ffd5b5f82612b9657612b96612aeb565b500690565b5f60208284031215612bab575f5ffd5b81516125e4816125b5565b5f60208284031215612bc6575f5ffd5b815180151581146125e4575f5ffd5b634e487b7160e01b5f52600160045260245ffd5b5f5b83811015612c03578181015183820152602001612beb565b50505f910152565b5f8251612c1c818460208701612be9565b9190910192915050565b602081525f8251806020840152612c44816040850160208701612be9565b601f01601f1916919091016040019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122044bce95f3f6ba1c5d77c9095c73e47e11bc01ce565e64d24e2a94bfe5c11c8d264736f6c634300081b0033", "balance": "0x0" }, "0x7633750000000000000000000000000000010001": { - "contractName": "TaikoL2", + "contractName": "TaikoAnchor", "storage": { "0x0000000000000000000000000000000000000000000000000000000000000097": "0x0000000000000000000000000000000000000000000000000000000000000101", "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000003bc256069ff9af461f3e04494a3ece3f62f183fc", - "0x0000000000000000000000000000000000000000000000000000000000000065": "0x0000000000000000000000007633750000000000000000000000000000010002", "0x00000000000000000000000000000000000000000000000000000000000000cc": "0x0000000000000000000000000000000000000000000000000000000000088bb0", "0x00000000000000000000000000000000000000000000000000000000000000cb": "0x0000000000000000000000000000000000000000000000000000000000000001", "0x00000000000000000000000000000000000000000000000000000000000000ca": "0xf2af5147f7a25c79422b8437b3f634b696a055d2241b124683de530b3d7acf6a", "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0763375000000000000000000000000000010001" }, - "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea26469706673582212205339f45a82df35ebd66aff7c8a93c20457e46388cef179bebf6908c53edcb0ec64736f6c634300081b0033", + "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033", "balance": "0x0" }, "0x0763375000000000000000000000000000010002": { - "contractName": "RollupAddressManagerImpl", + "contractName": "RollupResolverImpl", "storage": { "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000003bc256069ff9af461f3e04494a3ece3f62f183fc" }, - "code": "0x608060405260043610610110575f3560e01c806352d1902d1161009d5780638da5cb5b116100625780638da5cb5b146102d7578063a86f9d9e146102f4578063d8f4648f14610313578063e07baba614610332578063f2fde38b14610371575f5ffd5b806352d1902d146102595780635c975abb1461027b578063715018a61461029b5780638456cb59146102af5780638abf6077146102c3575f5ffd5b80633659cfe6116100e35780633659cfe6146101d55780633ab76e9f146101f45780633eb6b8cf146102135780633f4ba83a146102325780634f1ef28614610246575f5ffd5b8063069489a21461011457806319ab453c1461012a57806328f713cc146101495780633075db56146101b1575b5f5ffd5b34801561011f575f5ffd5b50610128610390565b005b348015610135575f5ffd5b5061012861014436600461105d565b610440565b348015610154575f5ffd5b50610194610163366004611094565b67ffffffffffffffff919091165f90815260c96020908152604080832093835292905220546001600160a01b031690565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156101bc575f5ffd5b506101c5610518565b60405190151581526020016101a8565b3480156101e0575f5ffd5b506101286101ef36600461105d565b610530565b3480156101ff575f5ffd5b50606554610194906001600160a01b031681565b34801561021e575f5ffd5b5061019461022d3660046110cb565b6105f7565b34801561023d575f5ffd5b5061012861060d565b610128610254366004611118565b610621565b348015610264575f5ffd5b5061026d6106d6565b6040519081526020016101a8565b348015610286575f5ffd5b506101c5609754610100900460ff1660021490565b3480156102a6575f5ffd5b50610128610787565b3480156102ba575f5ffd5b50610128610798565b3480156102ce575f5ffd5b506101946107b3565b3480156102e2575f5ffd5b506033546001600160a01b0316610194565b3480156102ff575f5ffd5b5061019461030e3660046111de565b6107c1565b34801561031e575f5ffd5b5061012861032d366004611208565b6107cd565b34801561033d575f5ffd5b506097546103589062010000900467ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016101a8565b34801561037c575f5ffd5b5061012861038b36600461105d565b6108ab565b610398610921565b5f54600290610100900460ff161580156103b857505f5460ff8083169116105b6103dd5760405162461bcd60e51b81526004016103d490611245565b60405180910390fd5b5f8054606580546001600160a01b0319163017905561ffff191660ff83169081176101001761ff0019169091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a150565b5f54610100900460ff161580801561045e57505f54600160ff909116105b806104775750303b15801561047757505f5460ff166001145b6104935760405162461bcd60e51b81526004016103d490611245565b5f805460ff1916600117905580156104b4575f805461ff0019166101001790555b6104bd8261097b565b606580546001600160a01b031916301790558015610514575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b5f600261052760975460ff1690565b60ff1614905090565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000100021630036105785760405162461bcd60e51b81526004016103d490611293565b7f00000000000000000000000007633750000000000000000000000000000100026001600160a01b03166105aa6109d9565b6001600160a01b0316146105d05760405162461bcd60e51b81526004016103d4906112df565b6105d9816109f4565b604080515f808252602082019092526105f4918391906109fc565b50565b5f610603848484610b6b565b90505b9392505050565b610615610bbe565b61061f335f610c4f565b565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000100021630036106695760405162461bcd60e51b81526004016103d490611293565b7f00000000000000000000000007633750000000000000000000000000000100026001600160a01b031661069b6109d9565b6001600160a01b0316146106c15760405162461bcd60e51b81526004016103d4906112df565b6106ca826109f4565b610514828260016109fc565b5f306001600160a01b037f000000000000000000000000076337500000000000000000000000000001000216146107755760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c000000000000000060648201526084016103d4565b505f5160206114185f395f51905f5290565b61078f610921565b61061f5f610c68565b6107a0610cb9565b6107a8610cd2565b61061f336001610c4f565b5f6107bc6109d9565b905090565b5f610606468484610b6b565b6107d5610921565b67ffffffffffffffff83165f90815260c9602090815260408083208584529091529020546001600160a01b039081169082168190036108275760405163a883089360e01b815260040160405180910390fd5b67ffffffffffffffff84165f81815260c96020908152604080832087845282529182902080546001600160a01b0319166001600160a01b038781169182179092558351908152908516918101919091528592917f500dcd607a98daece9bccc2511bf6032471252929de73caf507aae0e082f8453910160405180910390a350505050565b6108b3610921565b6001600160a01b0381166109185760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016103d4565b6105f481610c68565b6033546001600160a01b0316331461061f5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103d4565b5f54610100900460ff166109a15760405162461bcd60e51b81526004016103d49061132b565b6109a9610d43565b6109c76001600160a01b038216156109c15781610c68565b33610c68565b506097805461ff001916610100179055565b5f5160206114185f395f51905f52546001600160a01b031690565b6105f4610921565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff1615610a3457610a2f83610d69565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015610a8e575060408051601f3d908101601f19168201909252610a8b91810190611376565b60015b610af15760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b60648201526084016103d4565b5f5160206114185f395f51905f528114610b5f5760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b60648201526084016103d4565b50610a2f838383610e04565b5f610b768484610e2e565b905081158015610b8d57506001600160a01b038116155b1561060657604051632b0d65db60e01b815267ffffffffffffffff85166004820152602481018490526044016103d4565b610bd2609754610100900460ff1660021490565b610bef5760405163bae6e2a960e01b815260040160405180910390fd5b6097805461010069ffffffffffffffffff0019909116620100004267ffffffffffffffff1602171790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa906020015b60405180910390a1565b604051630c2b8f8f60e11b815260040160405180910390fd5b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b60405163a87dd7cf60e01b815260040160405180910390fd5b610ce6609754610100900460ff1660021490565b15610d045760405163bae6e2a960e01b815260040160405180910390fd5b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a25890602001610c45565b5f54610100900460ff1661061f5760405162461bcd60e51b81526004016103d49061132b565b6001600160a01b0381163b610dd65760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016103d4565b5f5160206114185f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b610e0d83610ed9565b5f82511180610e195750805b15610a2f57610e288383610f18565b50505050565b6065545f906001600160a01b031680610e5a57604051638ed88b2560e01b815260040160405180910390fd5b604051630a3dc4f360e21b815267ffffffffffffffff85166004820152602481018490526001600160a01b038216906328f713cc90604401602060405180830381865afa158015610ead573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ed1919061138d565b949350505050565b610ee281610d69565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606061060683836040518060600160405280602781526020016114386027913960605f5f856001600160a01b031685604051610f5491906113ca565b5f60405180830381855af49150503d805f8114610f8c576040519150601f19603f3d011682016040523d82523d5f602084013e610f91565b606091505b5091509150610fa286838387610fac565b9695505050505050565b6060831561101a5782515f03611013576001600160a01b0385163b6110135760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016103d4565b5081610ed1565b610ed1838381511561102f5781518083602001fd5b8060405162461bcd60e51b81526004016103d491906113e5565b6001600160a01b03811681146105f4575f5ffd5b5f6020828403121561106d575f5ffd5b813561060681611049565b803567ffffffffffffffff8116811461108f575f5ffd5b919050565b5f5f604083850312156110a5575f5ffd5b6110ae83611078565b946020939093013593505050565b8035801515811461108f575f5ffd5b5f5f5f606084860312156110dd575f5ffd5b6110e684611078565b9250602084013591506110fb604085016110bc565b90509250925092565b634e487b7160e01b5f52604160045260245ffd5b5f5f60408385031215611129575f5ffd5b823561113481611049565b9150602083013567ffffffffffffffff81111561114f575f5ffd5b8301601f8101851361115f575f5ffd5b803567ffffffffffffffff81111561117957611179611104565b604051601f8201601f19908116603f0116810167ffffffffffffffff811182821017156111a8576111a8611104565b6040528181528282016020018710156111bf575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f5f604083850312156111ef575f5ffd5b823591506111ff602084016110bc565b90509250929050565b5f5f5f6060848603121561121a575f5ffd5b61122384611078565b925060208401359150604084013561123a81611049565b809150509250925092565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f60208284031215611386575f5ffd5b5051919050565b5f6020828403121561139d575f5ffd5b815161060681611049565b5f5b838110156113c25781810151838201526020016113aa565b50505f910152565b5f82516113db8184602087016113a8565b9190910192915050565b602081525f82518060208401526114038160408501602087016113a8565b601f01601f1916919091016040019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220e7291c88c86669de0606134d51e7ba077712eb05e1336d7678ac3d3e5bf2591464736f6c634300081b0033", + "code": "0x6080604052600436106100e4575f3560e01c80635c975abb116100875780638abf6077116100575780638abf6077146102275780638da5cb5b1461023b578063b490d87f14610258578063f2fde38b14610277575f5ffd5b80635c975abb146101c05780636c6563f6146101e0578063715018a6146101ff5780638456cb5914610213575f5ffd5b80633659cfe6116100c25780633659cfe6146101585780633f4ba83a146101775780634f1ef2861461018b57806352d1902d1461019e575f5ffd5b806304f3bcec146100e857806319ab453c146101135780633075db5614610134575b5f5ffd5b3480156100f3575f5ffd5b50305b6040516001600160a01b0390911681526020015b60405180910390f35b34801561011e575f5ffd5b5061013261012d366004610db7565b610296565b005b34801561013f575f5ffd5b506101486103a8565b604051901515815260200161010a565b348015610163575f5ffd5b50610132610172366004610db7565b6103c0565b348015610182575f5ffd5b50610132610487565b610132610199366004610de4565b610513565b3480156101a9575f5ffd5b506101b26105c8565b60405190815260200161010a565b3480156101cb575f5ffd5b50610148609754610100900460ff1660021490565b3480156101eb575f5ffd5b506100f66101fa366004610ea8565b610679565b34801561020a575f5ffd5b506101326106ca565b34801561021e575f5ffd5b506101326106db565b348015610232575f5ffd5b506100f6610762565b348015610246575f5ffd5b506033546001600160a01b03166100f6565b348015610263575f5ffd5b50610132610272366004610ee2565b610770565b348015610282575f5ffd5b50610132610291366004610db7565b6107f1565b5f54610100900460ff16158080156102b457505f54600160ff909116105b806102cd5750303b1580156102cd57505f5460ff166001145b6103355760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b5f805460ff191660011790558015610356575f805461ff0019166101001790555b61035f82610867565b80156103a4575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b5f60026103b760975460ff1690565b60ff1614905090565b6001600160a01b037f00000000000000000000000007633750000000000000000000000000000100021630036104085760405162461bcd60e51b815260040161032c90610f14565b7f00000000000000000000000007633750000000000000000000000000000100026001600160a01b031661043a6108c5565b6001600160a01b0316146104605760405162461bcd60e51b815260040161032c90610f60565b610469816108e0565b604080515f80825260208201909252610484918391906108e8565b50565b61049b609754610100900460ff1660021490565b6104b85760405163bae6e2a960e01b815260040160405180910390fd5b6104c0610a57565b6104d46097805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610511335f610a70565b565b6001600160a01b037f000000000000000000000000076337500000000000000000000000000001000216300361055b5760405162461bcd60e51b815260040161032c90610f14565b7f00000000000000000000000007633750000000000000000000000000000100026001600160a01b031661058d6108c5565b6001600160a01b0316146105b35760405162461bcd60e51b815260040161032c90610f60565b6105bc826108e0565b6103a4828260016108e8565b5f306001600160a01b037f000000000000000000000000076337500000000000000000000000000001000216146106675760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c0000000000000000606482015260840161032c565b505f51602061107e5f395f51905f5290565b5f83815260c9602090815260408083208584529091529020546001600160a01b0316801515806106a65750815b6106c357604051631692906160e11b815260040160405180910390fd5b9392505050565b6106d2610a89565b6105115f610ae3565b6106ef609754610100900460ff1660021490565b1561070d5760405163bae6e2a960e01b815260040160405180910390fd5b610715610a57565b6097805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a1610511336001610a70565b5f61076b6108c5565b905090565b610778610a89565b5f83815260c96020908152604080832085845282529182902080546001600160a01b038581166001600160a01b0319831681179093558451928352169181018290529091849186917f3fd0559a7b01eb7106f9d9ce79ec76bb44f608a295878cce50856e54dba83d35910160405180910390a350505050565b6107f9610a89565b6001600160a01b03811661085e5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b606482015260840161032c565b61048481610ae3565b5f54610100900460ff1661088d5760405162461bcd60e51b815260040161032c90610fac565b610895610b34565b6108b36001600160a01b038216156108ad5781610ae3565b33610ae3565b506097805461ff001916610100179055565b5f51602061107e5f395f51905f52546001600160a01b031690565b610484610a89565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156109205761091b83610b5a565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa92505050801561097a575060408051601f3d908101601f1916820190925261097791810190610ff7565b60015b6109dd5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b606482015260840161032c565b5f51602061107e5f395f51905f528114610a4b5760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b606482015260840161032c565b5061091b838383610bf5565b60405163a87dd7cf60e01b815260040160405180910390fd5b604051630c2b8f8f60e11b815260040160405180910390fd5b6033546001600160a01b031633146105115760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161032c565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f54610100900460ff166105115760405162461bcd60e51b815260040161032c90610fac565b6001600160a01b0381163b610bc75760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161032c565b5f51602061107e5f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b610bfe83610c1f565b5f82511180610c0a5750805b1561091b57610c198383610c5e565b50505050565b610c2881610b5a565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606106c3838360405180606001604052806027815260200161109e6027913960605f5f856001600160a01b031685604051610c9a9190611030565b5f60405180830381855af49150503d805f8114610cd2576040519150601f19603f3d011682016040523d82523d5f602084013e610cd7565b606091505b5091509150610ce886838387610cf2565b9695505050505050565b60608315610d605782515f03610d59576001600160a01b0385163b610d595760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161032c565b5081610d6a565b610d6a8383610d72565b949350505050565b815115610d825781518083602001fd5b8060405162461bcd60e51b815260040161032c919061104b565b80356001600160a01b0381168114610db2575f5ffd5b919050565b5f60208284031215610dc7575f5ffd5b6106c382610d9c565b634e487b7160e01b5f52604160045260245ffd5b5f5f60408385031215610df5575f5ffd5b610dfe83610d9c565b9150602083013567ffffffffffffffff811115610e19575f5ffd5b8301601f81018513610e29575f5ffd5b803567ffffffffffffffff811115610e4357610e43610dd0565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610e7257610e72610dd0565b604052818152828201602001871015610e89575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f5f5f60608486031215610eba575f5ffd5b833592506020840135915060408401358015158114610ed7575f5ffd5b809150509250925092565b5f5f5f60608486031215610ef4575f5ffd5b8335925060208401359150610f0b60408501610d9c565b90509250925092565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f60208284031215611007575f5ffd5b5051919050565b5f5b83811015611028578181015183820152602001611010565b50505f910152565b5f825161104181846020870161100e565b9190910192915050565b602081525f825180602084015261106981604085016020870161100e565b601f01601f1916919091016040019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212205acfcd4888784dee86ffa66457557af2e86169bc3c0d96c2503aa0ac7da2852064736f6c634300081b0033", "balance": "0x0" }, "0x7633750000000000000000000000000000010002": { - "contractName": "RollupAddressManager", + "contractName": "RollupResolver", "storage": { "0x0000000000000000000000000000000000000000000000000000000000000097": "0x0000000000000000000000000000000000000000000000000000000000000101", "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", @@ -252,24 +248,85 @@ "0x7da623f15797b8335d6fba9f2f694e35991c1e2148958bac1cc5e662082f63c7": "0x0000000000000000000000007633750000000000000000000000000000000005", "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0763375000000000000000000000000000010002" }, - "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea26469706673582212205339f45a82df35ebd66aff7c8a93c20457e46388cef179bebf6908c53edcb0ec64736f6c634300081b0033", + "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033", "balance": "0x0" }, "0x89f660beDa861f737D78c5f1Bb147C7a7C73bF69": { "contractName": "LibNetwork", "storage": {}, - "code": "0x730000000000000000000000000000000000000000301460806040525f5ffdfea2646970667358221220376414b9f116b5f5550c1bdc03eb4a6e15cae1b533bce5a708fbacecafd0ff2964736f6c634300081b0033", + "code": "0x730000000000000000000000000000000000000000301460806040525f5ffdfea264697066735822122008786afaa7838306279a36a27fd913c83c70dbae721d3ffc1d65aa720b01259364736f6c634300081b0033", "balance": "0x0" }, - "0x0763375000000000000000000000000000010099": { - "contractName": "RegularERC20", - "storage": { - "0x0000000000000000000000000000000000000000000000000000000000000003": "0x526567756c617245524332300000000000000000000000000000000000000018", - "0x0000000000000000000000000000000000000000000000000000000000000004": "0x52474c0000000000000000000000000000000000000000000000000000000006", - "0x0000000000000000000000000000000000000000000000000000000000000002": "0x00000000000000000000000000000000000000000000000000000000000fa000", - "0x81f7d5a07137cb6deeb3f423eea3ad6e1dae342c0f221da250152b8d009f0097": "0x00000000000000000000000000000000000000000000000000000000000fa000" - }, - "code": "0x608060405234801561000f575f5ffd5b50600436106100a6575f3560e01c8063395093511161006e578063395093511461011f57806370a082311461013257806395d89b411461015a578063a457c2d714610162578063a9059cbb14610175578063dd62ed3e14610188575f5ffd5b806306fdde03146100aa578063095ea7b3146100c857806318160ddd146100eb57806323b872dd146100fd578063313ce56714610110575b5f5ffd5b6100b261019b565b6040516100bf919061068a565b60405180910390f35b6100db6100d63660046106f0565b61022b565b60405190151581526020016100bf565b6002545b6040519081526020016100bf565b6100db61010b366004610718565b610244565b604051601281526020016100bf565b6100db61012d3660046106f0565b610267565b6100ef610140366004610752565b6001600160a01b03165f9081526020819052604090205490565b6100b2610288565b6100db6101703660046106f0565b610297565b6100db6101833660046106f0565b610316565b6100ef610196366004610772565b610323565b6060600380546101aa906107a3565b80601f01602080910402602001604051908101604052809291908181526020018280546101d6906107a3565b80156102215780601f106101f857610100808354040283529160200191610221565b820191905f5260205f20905b81548152906001019060200180831161020457829003601f168201915b5050505050905090565b5f3361023881858561034d565b60019150505b92915050565b5f33610251858285610470565b61025c8585856104e8565b506001949350505050565b5f336102388185856102798383610323565b61028391906107db565b61034d565b6060600480546101aa906107a3565b5f33816102a48286610323565b9050838110156103095760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b61025c828686840361034d565b5f336102388185856104e8565b6001600160a01b039182165f90815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166103af5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610300565b6001600160a01b0382166104105760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610300565b6001600160a01b038381165f8181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b5f61047b8484610323565b90505f1981146104e257818110156104d55760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610300565b6104e2848484840361034d565b50505050565b6001600160a01b03831661054c5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610300565b6001600160a01b0382166105ae5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610300565b6001600160a01b0383165f90815260208190526040902054818110156106255760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610300565b6001600160a01b038481165f81815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a36104e2565b602081525f82518060208401525f5b818110156106b65760208186018101516040868401015201610699565b505f604082850101526040601f19601f83011684010191505092915050565b80356001600160a01b03811681146106eb575f5ffd5b919050565b5f5f60408385031215610701575f5ffd5b61070a836106d5565b946020939093013593505050565b5f5f5f6060848603121561072a575f5ffd5b610733846106d5565b9250610741602085016106d5565b929592945050506040919091013590565b5f60208284031215610762575f5ffd5b61076b826106d5565b9392505050565b5f5f60408385031215610783575f5ffd5b61078c836106d5565b915061079a602084016106d5565b90509250929050565b600181811c908216806107b757607f821691505b6020821081036107d557634e487b7160e01b5f52602260045260245ffd5b50919050565b8082018082111561023e57634e487b7160e01b5f52601160045260245ffdfea2646970667358221220a2d510f3e3b97dc37742483584d58b9ce6a475c863bdf8ce7d20a867d41e3f3564736f6c634300081b0033", + "0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99": { + "contractName": "CompatibilityFallbackHandler", + "storage": {}, + "code": "0x608060405234801561001057600080fd5b50600436106100b35760003560e01c8063230316401161007157806323031640146106535780636ac24784146107a7578063b2494df314610896578063bc197c81146108f5578063bd61951d14610a8b578063f23a6e6114610b9d576100b3565b806223de29146100b857806301ffc9a7146101f05780630a1028c414610253578063150b7a02146103225780631626ba7e1461041857806320c13b0b146104ce575b600080fd5b6101ee600480360360c08110156100ce57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561015557600080fd5b82018360208201111561016757600080fd5b8035906020019184600183028401116401000000008311171561018957600080fd5b9091929391929390803590602001906401000000008111156101aa57600080fd5b8201836020820111156101bc57600080fd5b803590602001918460018302840111640100000000831117156101de57600080fd5b9091929391929390505050610c9d565b005b61023b6004803603602081101561020657600080fd5b8101908080357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19169060200190929190505050610ca7565b60405180821515815260200191505060405180910390f35b61030c6004803603602081101561026957600080fd5b810190808035906020019064010000000081111561028657600080fd5b82018360208201111561029857600080fd5b803590602001918460018302840111640100000000831117156102ba57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610de1565b6040518082815260200191505060405180910390f35b6103e36004803603608081101561033857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561039f57600080fd5b8201836020820111156103b157600080fd5b803590602001918460018302840111640100000000831117156103d357600080fd5b9091929391929390505050610df4565b60405180827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200191505060405180910390f35b6104996004803603604081101561042e57600080fd5b81019080803590602001909291908035906020019064010000000081111561045557600080fd5b82018360208201111561046757600080fd5b8035906020019184600183028401116401000000008311171561048957600080fd5b9091929391929390505050610e09565b60405180827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200191505060405180910390f35b61061e600480360360408110156104e457600080fd5b810190808035906020019064010000000081111561050157600080fd5b82018360208201111561051357600080fd5b8035906020019184600183028401116401000000008311171561053557600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192908035906020019064010000000081111561059857600080fd5b8201836020820111156105aa57600080fd5b803590602001918460018302840111640100000000831117156105cc57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610fc1565b60405180827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200191505060405180910390f35b61072c6004803603604081101561066957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001906401000000008111156106a657600080fd5b8201836020820111156106b857600080fd5b803590602001918460018302840111640100000000831117156106da57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050611249565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561076c578082015181840152602081019050610751565b50505050905090810190601f1680156107995780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610880600480360360408110156107bd57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001906401000000008111156107fa57600080fd5b82018360208201111561080c57600080fd5b8035906020019184600183028401116401000000008311171561082e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506113b5565b6040518082815260200191505060405180910390f35b61089e6113d0565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b838110156108e15780820151818401526020810190506108c6565b505050509050019250505060405180910390f35b610a56600480360360a081101561090b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019064010000000081111561096857600080fd5b82018360208201111561097a57600080fd5b8035906020019184602083028401116401000000008311171561099c57600080fd5b9091929391929390803590602001906401000000008111156109bd57600080fd5b8201836020820111156109cf57600080fd5b803590602001918460208302840111640100000000831117156109f157600080fd5b909192939192939080359060200190640100000000811115610a1257600080fd5b820183602082011115610a2457600080fd5b80359060200191846001830284011164010000000083111715610a4657600080fd5b9091929391929390505050611537565b60405180827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200191505060405180910390f35b610b2260048036036040811015610aa157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610ade57600080fd5b820183602082011115610af057600080fd5b80359060200191846001830284011164010000000083111715610b1257600080fd5b909192939192939050505061154f565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610b62578082015181840152602081019050610b47565b50505050905090810190601f168015610b8f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610c68600480360360a0811015610bb357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019092919080359060200190640100000000811115610c2457600080fd5b820183602082011115610c3657600080fd5b80359060200191846001830284011164010000000083111715610c5857600080fd5b90919293919293905050506115b9565b60405180827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200191505060405180910390f35b5050505050505050565b60007f4e2312e0000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161480610d7257507f150b7a02000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b80610dda57507f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b9050919050565b6000610ded33836113b5565b9050919050565b600063150b7a0260e01b905095945050505050565b60008033905060008173ffffffffffffffffffffffffffffffffffffffff166320c13b0b876040516020018082815260200191505060405160208183030381529060405287876040518463ffffffff1660e01b8152600401808060200180602001838103835286818151815260200191508051906020019080838360005b83811015610ea2578082015181840152602081019050610e87565b50505050905090810190601f168015610ecf5780820380516001836020036101000a031916815260200191505b508381038252858582818152602001925080828437600081840152601f19601f8201169050808301925050509550505050505060206040518083038186803b158015610f1a57600080fd5b505afa158015610f2e573d6000803e3d6000fd5b505050506040513d6020811015610f4457600080fd5b810190808051906020019092919050505090506320c13b0b60e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614610fad57600060e01b610fb6565b631626ba7e60e01b5b925050509392505050565b6000803390506000610fd38286611249565b90506000818051906020012090506000855114156110f25760008373ffffffffffffffffffffffffffffffffffffffff16635ae6bd37836040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b15801561103e57600080fd5b505afa158015611052573d6000803e3d6000fd5b505050506040513d602081101561106857600080fd5b810190808051906020019092919050505014156110ed576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260118152602001807f48617368206e6f7420617070726f76656400000000000000000000000000000081525060200191505060405180910390fd5b611236565b8273ffffffffffffffffffffffffffffffffffffffff1663934f3a118284886040518463ffffffff1660e01b8152600401808481526020018060200180602001838103835285818151815260200191508051906020019080838360005b8381101561116a57808201518184015260208101905061114f565b50505050905090810190601f1680156111975780820380516001836020036101000a031916815260200191505b50838103825284818151815260200191508051906020019080838360005b838110156111d05780820151818401526020810190506111b5565b50505050905090810190601f1680156111fd5780820380516001836020036101000a031916815260200191505b509550505050505060006040518083038186803b15801561121d57600080fd5b505afa158015611231573d6000803e3d6000fd5b505050505b6320c13b0b60e01b935050505092915050565b606060007f60b3cbf8b4a223d68d641b3b6ddf9a298e7f33710cf3d3a9d1146b5a6150fbca60001b83805190602001206040516020018083815260200182815260200192505050604051602081830303815290604052805190602001209050601960f81b600160f81b8573ffffffffffffffffffffffffffffffffffffffff1663f698da256040518163ffffffff1660e01b815260040160206040518083038186803b1580156112f857600080fd5b505afa15801561130c573d6000803e3d6000fd5b505050506040513d602081101561132257600080fd5b81019080805190602001909291905050508360405160200180857effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152600101847effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260010183815260200182815260200194505050505060405160208183030381529060405291505092915050565b60006113c18383611249565b80519060200120905092915050565b6060600033905060008173ffffffffffffffffffffffffffffffffffffffff1663cc2f84526001600a6040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060006040518083038186803b15801561144a57600080fd5b505afa15801561145e573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f82011682018060405250604081101561148857600080fd5b81019080805160405193929190846401000000008211156114a857600080fd5b838201915060208201858111156114be57600080fd5b82518660208202830111640100000000821117156114db57600080fd5b8083526020830192505050908051906020019060200280838360005b838110156115125780820151818401526020810190506114f7565b5050505090500160405260200180519060200190929190505050509050809250505090565b600063bc197c8160e01b905098975050505050505050565b60606040517fb4faba09000000000000000000000000000000000000000000000000000000008152600436036004808301376020600036836000335af15060203d036040519250808301604052806020843e6000516115b057825160208401fd5b50509392505050565b600063f23a6e6160e01b9050969550505050505056fea26469706673582212201b4a724e94687b6683f72064694993023a1b3dbf13a3418c0926ebb253c030fe64736f6c63430007060033", + "balance": "0x0" + }, + "0x41675C099F32341bf84BFc5382aF534df5C7461a": { + "contractName": "Safe", + "storage": {}, + "code": "0x6080604052600436106101d15760003560e01c8063affed0e0116100f7578063e19a9dd911610095578063f08a032311610064578063f08a03231461156b578063f698da25146115bc578063f8dc5dd9146115e7578063ffa1ad741461166257610226565b8063e19a9dd9146112bf578063e318b52b14611310578063e75235b8146113a1578063e86637db146113cc57610226565b8063cc2f8452116100d1578063cc2f84521461100c578063d4d9bdcd146110d9578063d8d11f7814611114578063e009cfde1461124e57610226565b8063affed0e014610d89578063b4faba0914610db4578063b63e800d14610e9c57610226565b80635624b25b1161016f5780636a7612021161013e5780636a761202146109895780637d83297414610b45578063934f3a1114610bb4578063a0e67e2b14610d1d57610226565b80635624b25b146107f05780635ae6bd37146108ae578063610b5925146108fd578063694e80c31461094e57610226565b80632f54bf6e116101ab5780632f54bf6e146104c85780633408e4701461052f578063468721a71461055a5780635229073f1461066f57610226565b80630d582f131461029357806312fb68e0146102ee5780632d9ad53d1461046157610226565b36610226573373ffffffffffffffffffffffffffffffffffffffff167f3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d346040518082815260200191505060405180910390a2005b34801561023257600080fd5b5060007f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d560001b905080548061026757600080f35b36600080373360601b365260008060143601600080855af13d6000803e8061028e573d6000fd5b3d6000f35b34801561029f57600080fd5b506102ec600480360360408110156102b657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506116f2565b005b3480156102fa57600080fd5b5061045f6004803603608081101561031157600080fd5b81019080803590602001909291908035906020019064010000000081111561033857600080fd5b82018360208201111561034a57600080fd5b8035906020019184600183028401116401000000008311171561036c57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803590602001906401000000008111156103cf57600080fd5b8201836020820111156103e157600080fd5b8035906020019184600183028401116401000000008311171561040357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190505050611ad8565b005b34801561046d57600080fd5b506104b06004803603602081101561048457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506123d6565b60405180821515815260200191505060405180910390f35b3480156104d457600080fd5b50610517600480360360208110156104eb57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506124a8565b60405180821515815260200191505060405180910390f35b34801561053b57600080fd5b5061054461257a565b6040518082815260200191505060405180910390f35b34801561056657600080fd5b506106576004803603608081101561057d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156105c457600080fd5b8201836020820111156105d657600080fd5b803590602001918460018302840111640100000000831117156105f857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803560ff169060200190929190505050612587565b60405180821515815260200191505060405180910390f35b34801561067b57600080fd5b5061076c6004803603608081101561069257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156106d957600080fd5b8201836020820111156106eb57600080fd5b8035906020019184600183028401116401000000008311171561070d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803560ff16906020019092919050505061278d565b60405180831515815260200180602001828103825283818151815260200191508051906020019080838360005b838110156107b4578082015181840152602081019050610799565b50505050905090810190601f1680156107e15780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b3480156107fc57600080fd5b506108336004803603604081101561081357600080fd5b8101908080359060200190929190803590602001909291905050506127c3565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610873578082015181840152602081019050610858565b50505050905090810190601f1680156108a05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156108ba57600080fd5b506108e7600480360360208110156108d157600080fd5b810190808035906020019092919050505061284a565b6040518082815260200191505060405180910390f35b34801561090957600080fd5b5061094c6004803603602081101561092057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612862565b005b34801561095a57600080fd5b506109876004803603602081101561097157600080fd5b8101908080359060200190929190505050612bea565b005b610b2d60048036036101408110156109a057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156109e757600080fd5b8201836020820111156109f957600080fd5b80359060200191846001830284011164010000000083111715610a1b57600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610aa757600080fd5b820183602082011115610ab957600080fd5b80359060200191846001830284011164010000000083111715610adb57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050612d24565b60405180821515815260200191505060405180910390f35b348015610b5157600080fd5b50610b9e60048036036040811015610b6857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050613253565b6040518082815260200191505060405180910390f35b348015610bc057600080fd5b50610d1b60048036036060811015610bd757600080fd5b810190808035906020019092919080359060200190640100000000811115610bfe57600080fd5b820183602082011115610c1057600080fd5b80359060200191846001830284011164010000000083111715610c3257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190640100000000811115610c9557600080fd5b820183602082011115610ca757600080fd5b80359060200191846001830284011164010000000083111715610cc957600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050613278565b005b348015610d2957600080fd5b50610d32613307565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b83811015610d75578082015181840152602081019050610d5a565b505050509050019250505060405180910390f35b348015610d9557600080fd5b50610d9e6134b0565b6040518082815260200191505060405180910390f35b348015610dc057600080fd5b50610e9a60048036036040811015610dd757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610e1457600080fd5b820183602082011115610e2657600080fd5b80359060200191846001830284011164010000000083111715610e4857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506134b6565b005b348015610ea857600080fd5b5061100a6004803603610100811015610ec057600080fd5b8101908080359060200190640100000000811115610edd57600080fd5b820183602082011115610eef57600080fd5b80359060200191846020830284011164010000000083111715610f1157600080fd5b909192939192939080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610f5c57600080fd5b820183602082011115610f6e57600080fd5b80359060200191846001830284011164010000000083111715610f9057600080fd5b9091929391929390803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506134d8565b005b34801561101857600080fd5b506110656004803603604081101561102f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050613696565b60405180806020018373ffffffffffffffffffffffffffffffffffffffff168152602001828103825284818151815260200191508051906020019060200280838360005b838110156110c45780820151818401526020810190506110a9565b50505050905001935050505060405180910390f35b3480156110e557600080fd5b50611112600480360360208110156110fc57600080fd5b81019080803590602001909291905050506139f9565b005b34801561112057600080fd5b50611238600480360361014081101561113857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561117f57600080fd5b82018360208201111561119157600080fd5b803590602001918460018302840111640100000000831117156111b357600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050613b98565b6040518082815260200191505060405180910390f35b34801561125a57600080fd5b506112bd6004803603604081101561127157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613bc5565b005b3480156112cb57600080fd5b5061130e600480360360208110156112e257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613f4c565b005b34801561131c57600080fd5b5061139f6004803603606081101561133357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050614138565b005b3480156113ad57600080fd5b506113b6614796565b6040518082815260200191505060405180910390f35b3480156113d857600080fd5b506114f060048036036101408110156113f057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561143757600080fd5b82018360208201111561144957600080fd5b8035906020019184600183028401116401000000008311171561146b57600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506147a0565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015611530578082015181840152602081019050611515565b50505050905090810190601f16801561155d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561157757600080fd5b506115ba6004803603602081101561158e57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050614948565b005b3480156115c857600080fd5b506115d161499f565b6040518082815260200191505060405180910390f35b3480156115f357600080fd5b506116606004803603606081101561160a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050614a1d565b005b34801561166e57600080fd5b50611677614e46565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156116b757808201518184015260208101905061169c565b50505050905090810190601f1680156116e45780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6116fa614e7f565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156117645750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b801561179c57503073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b61180e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461190f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600081548092919060010191905055508173ffffffffffffffffffffffffffffffffffffffff167f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea2660405160405180910390a28060045414611ad457611ad381612bea565b5b5050565b611aec604182614f2290919063ffffffff16565b82511015611b62576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6000808060008060005b868110156123ca57611b7e8882614f5c565b80945081955082965050505060008460ff1614156120035789898051906020012014611c12576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323700000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8260001c9450611c2c604188614f2290919063ffffffff16565b8260001c1015611ca4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8751611cbd60208460001c614f8b90919063ffffffff16565b1115611d31576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60006020838a01015190508851611d6782611d5960208760001c614f8b90919063ffffffff16565b614f8b90919063ffffffff16565b1115611ddb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60606020848b010190506320c13b0b60e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168773ffffffffffffffffffffffffffffffffffffffff166320c13b0b8d846040518363ffffffff1660e01b8152600401808060200180602001838103835285818151815260200191508051906020019080838360005b83811015611e7d578082015181840152602081019050611e62565b50505050905090810190601f168015611eaa5780820380516001836020036101000a031916815260200191505b50838103825284818151815260200191508051906020019080838360005b83811015611ee3578082015181840152602081019050611ec8565b50505050905090810190601f168015611f105780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b158015611f2f57600080fd5b505afa158015611f43573d6000803e3d6000fd5b505050506040513d6020811015611f5957600080fd5b81019080805190602001909291905050507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614611ffc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5050612248565b60018460ff161415612117578260001c94508473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806120a057506000600860008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008c81526020019081526020016000205414155b612112576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b612247565b601e8460ff1611156121df5760018a60405160200180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c018281526020019150506040516020818303038152906040528051906020012060048603858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa1580156121ce573d6000803e3d6000fd5b505050602060405103519450612246565b60018a85858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015612239573d6000803e3d6000fd5b5050506020604051035194505b5b5b8573ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1611801561230f5750600073ffffffffffffffffffffffffffffffffffffffff16600260008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b80156123485750600173ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614155b6123ba576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323600000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8495508080600101915050611b6c565b50505050505050505050565b60008173ffffffffffffffffffffffffffffffffffffffff16600173ffffffffffffffffffffffffffffffffffffffff16141580156124a15750600073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b9050919050565b6000600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156125735750600073ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b9050919050565b6000804690508091505090565b6000600173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141580156126525750600073ffffffffffffffffffffffffffffffffffffffff16600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b6126c4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6126f1858585857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff614faa565b90508015612741573373ffffffffffffffffffffffffffffffffffffffff167f6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb860405160405180910390a2612785565b3373ffffffffffffffffffffffffffffffffffffffff167facd2c8702804128fdb0db2bb49f6d127dd0181c13fd45dbfe16de0930e2bd37560405160405180910390a25b949350505050565b6000606061279d86868686612587565b915060405160203d0181016040523d81523d6000602083013e8091505094509492505050565b606060006020830267ffffffffffffffff811180156127e157600080fd5b506040519080825280601f01601f1916602001820160405280156128145781602001600182028036833780820191505090505b50905060005b8381101561283f5780850154806020830260208501015250808060010191505061281a565b508091505092915050565b60076020528060005260406000206000915090505481565b61286a614e7f565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156128d45750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b612946576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614612a47576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff167fecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f844060405160405180910390a250565b612bf2614e7f565b600354811115612c6a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001811015612ce1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b806004819055507f610f7ff2b304ae8903c3de74c60c6ab1f7d6226b3f52c5161905bb5ad4039c936004546040518082815260200191505060405180910390a150565b6000806000612d3e8e8e8e8e8e8e8e8e8e8e6005546147a0565b905060056000815480929190600101919050555080805190602001209150612d67828286613278565b506000612d72614ff6565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614612f58578073ffffffffffffffffffffffffffffffffffffffff166375f0bb528f8f8f8f8f8f8f8f8f8f8f336040518d63ffffffff1660e01b8152600401808d73ffffffffffffffffffffffffffffffffffffffff1681526020018c8152602001806020018a6001811115612e1557fe5b81526020018981526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff168152602001806020018473ffffffffffffffffffffffffffffffffffffffff16815260200183810383528d8d82818152602001925080828437600081840152601f19601f820116905080830192505050838103825285818151815260200191508051906020019080838360005b83811015612ee7578082015181840152602081019050612ecc565b50505050905090810190601f168015612f145780820380516001836020036101000a031916815260200191505b509e505050505050505050505050505050600060405180830381600087803b158015612f3f57600080fd5b505af1158015612f53573d6000803e3d6000fd5b505050505b6101f4612f7f6109c48b01603f60408d0281612f7057fe5b0461502790919063ffffffff16565b015a1015612ff5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60005a905061305e8f8f8f8f8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e60008d14613053578e613059565b6109c45a035b614faa565b93506130735a8261504190919063ffffffff16565b90508380613082575060008a14155b8061308e575060008814155b613100576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60008089111561311a57613117828b8b8b8b615061565b90505b841561315d57837f442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e826040518082815260200191505060405180910390a2613196565b837f23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d23826040518082815260200191505060405180910390a25b5050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614613242578073ffffffffffffffffffffffffffffffffffffffff16639327136883856040518363ffffffff1660e01b815260040180838152602001821515815260200192505050600060405180830381600087803b15801561322957600080fd5b505af115801561323d573d6000803e3d6000fd5b505050505b50509b9a5050505050505050505050565b6008602052816000526040600020602052806000526040600020600091509150505481565b60006004549050600081116132f5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b61330184848484611ad8565b50505050565b6060600060035467ffffffffffffffff8111801561332457600080fd5b506040519080825280602002602001820160405280156133535781602001602082028036833780820191505090505b50905060008060026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146134a757808383815181106133fe57fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600260008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905081806001019250506133bd565b82935050505090565b60055481565b600080825160208401855af4806000523d6020523d600060403e60403d016000fd5b6135238a8a80806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f8201169050808301925050505050505089615267565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16146135615761356084615767565b5b6135af8787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050615838565b60008211156135c9576135c782600060018685615061565b505b3373ffffffffffffffffffffffffffffffffffffffff167f141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a88b8b8b8b8960405180806020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281038252878782818152602001925060200280828437600081840152601f19601f820116905080830192505050965050505050505060405180910390a250505050505050505050565b60606000600173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1614806136da57506136d9846123d6565b5b61374c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600083116137c2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303600000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8267ffffffffffffffff811180156137d957600080fd5b506040519080825280602002602001820160405280156138085781602001602082028036833780820191505090505b5091506000600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1691505b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156138da5750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b80156138e557508381105b156139a057818382815181106138f757fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1691508080600101915050613870565b600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16146139ee578260018203815181106139e357fe5b602002602001015191505b808352509250929050565b600073ffffffffffffffffffffffffffffffffffffffff16600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415613afb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330333000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001600860003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000838152602001908152602001600020819055503373ffffffffffffffffffffffffffffffffffffffff16817ff2a0eb156472d1440255b0d7c1e19cc07115d1051fe605b0dce69acfec884d9c60405160405180910390a350565b6000613bad8c8c8c8c8c8c8c8c8c8c8c6147a0565b8051906020012090509b9a5050505050505050505050565b613bcd614e7f565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614158015613c375750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b613ca9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614613da9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff167faab4fa2b463f581b2b32cb3b7e3b704b9ce37cc209b5fb4d77e593ace405427660405160405180910390a25050565b613f54614e7f565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146140c6578073ffffffffffffffffffffffffffffffffffffffff166301ffc9a77fe6d7a83a000000000000000000000000000000000000000000000000000000006040518263ffffffff1660e01b815260040180827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200191505060206040518083038186803b15801561401857600080fd5b505afa15801561402c573d6000803e3d6000fd5b505050506040513d602081101561404257600080fd5b81019080805190602001909291905050506140c5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475333303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b60007f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c860001b90508181558173ffffffffffffffffffffffffffffffffffffffff167f1151116914515bc0891ff9047a6cb32cf902546f83066499bcf8ba33d2353fa260405160405180910390a25050565b614140614e7f565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156141aa5750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b80156141e257503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b614254576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614614355576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156143bf5750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b614431576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614614531576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff167ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf60405160405180910390a28073ffffffffffffffffffffffffffffffffffffffff167f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea2660405160405180910390a2505050565b6000600454905090565b606060007fbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d860001b8d8d8d8d60405180838380828437808301925050509250505060405180910390208c8c8c8c8c8c8c604051602001808c81526020018b73ffffffffffffffffffffffffffffffffffffffff1681526020018a815260200189815260200188600181111561483157fe5b81526020018781526020018681526020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019b505050505050505050505050604051602081830303815290604052805190602001209050601960f81b600160f81b6148bd61499f565b8360405160200180857effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152600101847effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526001018381526020018281526020019450505050506040516020818303038152906040529150509b9a5050505050505050505050565b614950614e7f565b61495981615767565b8073ffffffffffffffffffffffffffffffffffffffff167f5ac6c46c93c8d0e53714ba3b53db3e7c046da994313d7ed0d192028bc7c228b060405160405180910390a250565b60007f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a7946921860001b6149cd61257a565b30604051602001808481526020018381526020018273ffffffffffffffffffffffffffffffffffffffff168152602001935050505060405160208183030381529060405280519060200120905090565b614a25614e7f565b806001600354031015614aa0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614158015614b0a5750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b614b7c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614614c7c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600360008154809291906001900391905055508173ffffffffffffffffffffffffffffffffffffffff167ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf60405160405180910390a28060045414614e4157614e4081612bea565b5b505050565b6040518060400160405280600581526020017f312e342e3100000000000000000000000000000000000000000000000000000081525081565b3073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614614f20576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330333100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b565b600080831415614f355760009050614f56565b6000828402905082848281614f4657fe5b0414614f5157600080fd5b809150505b92915050565b60008060008360410260208101860151925060408101860151915060ff60418201870151169350509250925092565b600080828401905083811015614fa057600080fd5b8091505092915050565b6000600180811115614fb857fe5b836001811115614fc457fe5b1415614fdd576000808551602087018986f49050614fed565b600080855160208701888a87f190505b95945050505050565b6000807f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c860001b9050805491505090565b6000818310156150375781615039565b825b905092915050565b60008282111561505057600080fd5b600082840390508091505092915050565b600080600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161461509e57826150a0565b325b9050600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1614156151b85761510a3a86106150e7573a6150e9565b855b6150fc888a614f8b90919063ffffffff16565b614f2290919063ffffffff16565b91508073ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f193505050506151b3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b61525d565b6151dd856151cf888a614f8b90919063ffffffff16565b614f2290919063ffffffff16565b91506151ea848284615b0e565b61525c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b5095945050505050565b6000600454146152df576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8151811115615356576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60018110156153cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60006001905060005b83518110156156d35760008482815181106153ed57fe5b60200260200101519050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156154615750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561549957503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b80156154d157508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614155b615543576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614615644576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b80600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508092505080806001019150506153d6565b506001600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550825160038190555081600481905550505050565b3073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415615809576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475334303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60007f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d560001b90508181555050565b600073ffffffffffffffffffffffffffffffffffffffff1660016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461593a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001806000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614615b0a576159f682615bd2565b615a68576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b615a978260008360017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff614faa565b615b09576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b5050565b60008063a9059cbb8484604051602401808373ffffffffffffffffffffffffffffffffffffffff168152602001828152602001925050506040516020818303038152906040529060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050602060008251602084016000896127105a03f13d60008114615bb55760208114615bbd5760009350615bc8565b819350615bc8565b600051158215171593505b5050509392505050565b600080823b90506000811191505091905056fea264697066735822122057398fa72884cf9a6cb78aab2fb58a6b927f0e9d97d75b015daaee0959a153bf64736f6c63430007060033", + "balance": "0x0" + }, + "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762": { + "contractName": "SafeL2", + "storage": {}, + "code": "0x6080604052600436106101d15760003560e01c8063affed0e0116100f7578063e19a9dd911610095578063f08a032311610064578063f08a03231461156b578063f698da25146115bc578063f8dc5dd9146115e7578063ffa1ad741461166257610226565b8063e19a9dd9146112bf578063e318b52b14611310578063e75235b8146113a1578063e86637db146113cc57610226565b8063cc2f8452116100d1578063cc2f84521461100c578063d4d9bdcd146110d9578063d8d11f7814611114578063e009cfde1461124e57610226565b8063affed0e014610d89578063b4faba0914610db4578063b63e800d14610e9c57610226565b80635624b25b1161016f5780636a7612021161013e5780636a761202146109895780637d83297414610b45578063934f3a1114610bb4578063a0e67e2b14610d1d57610226565b80635624b25b146107f05780635ae6bd37146108ae578063610b5925146108fd578063694e80c31461094e57610226565b80632f54bf6e116101ab5780632f54bf6e146104c85780633408e4701461052f578063468721a71461055a5780635229073f1461066f57610226565b80630d582f131461029357806312fb68e0146102ee5780632d9ad53d1461046157610226565b36610226573373ffffffffffffffffffffffffffffffffffffffff167f3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d346040518082815260200191505060405180910390a2005b34801561023257600080fd5b5060007f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d560001b905080548061026757600080f35b36600080373360601b365260008060143601600080855af13d6000803e8061028e573d6000fd5b3d6000f35b34801561029f57600080fd5b506102ec600480360360408110156102b657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506116f2565b005b3480156102fa57600080fd5b5061045f6004803603608081101561031157600080fd5b81019080803590602001909291908035906020019064010000000081111561033857600080fd5b82018360208201111561034a57600080fd5b8035906020019184600183028401116401000000008311171561036c57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803590602001906401000000008111156103cf57600080fd5b8201836020820111156103e157600080fd5b8035906020019184600183028401116401000000008311171561040357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190505050611ad8565b005b34801561046d57600080fd5b506104b06004803603602081101561048457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506123d6565b60405180821515815260200191505060405180910390f35b3480156104d457600080fd5b50610517600480360360208110156104eb57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506124a8565b60405180821515815260200191505060405180910390f35b34801561053b57600080fd5b5061054461257a565b6040518082815260200191505060405180910390f35b34801561056657600080fd5b506106576004803603608081101561057d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156105c457600080fd5b8201836020820111156105d657600080fd5b803590602001918460018302840111640100000000831117156105f857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803560ff169060200190929190505050612587565b60405180821515815260200191505060405180910390f35b34801561067b57600080fd5b5061076c6004803603608081101561069257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156106d957600080fd5b8201836020820111156106eb57600080fd5b8035906020019184600183028401116401000000008311171561070d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803560ff169060200190929190505050612692565b60405180831515815260200180602001828103825283818151815260200191508051906020019080838360005b838110156107b4578082015181840152602081019050610799565b50505050905090810190601f1680156107e15780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b3480156107fc57600080fd5b506108336004803603604081101561081357600080fd5b8101908080359060200190929190803590602001909291905050506126c8565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610873578082015181840152602081019050610858565b50505050905090810190601f1680156108a05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156108ba57600080fd5b506108e7600480360360208110156108d157600080fd5b810190808035906020019092919050505061274f565b6040518082815260200191505060405180910390f35b34801561090957600080fd5b5061094c6004803603602081101561092057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612767565b005b34801561095a57600080fd5b506109876004803603602081101561097157600080fd5b8101908080359060200190929190505050612aef565b005b610b2d60048036036101408110156109a057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156109e757600080fd5b8201836020820111156109f957600080fd5b80359060200191846001830284011164010000000083111715610a1b57600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610aa757600080fd5b820183602082011115610ab957600080fd5b80359060200191846001830284011164010000000083111715610adb57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050612c29565b60405180821515815260200191505060405180910390f35b348015610b5157600080fd5b50610b9e60048036036040811015610b6857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050612e68565b6040518082815260200191505060405180910390f35b348015610bc057600080fd5b50610d1b60048036036060811015610bd757600080fd5b810190808035906020019092919080359060200190640100000000811115610bfe57600080fd5b820183602082011115610c1057600080fd5b80359060200191846001830284011164010000000083111715610c3257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190640100000000811115610c9557600080fd5b820183602082011115610ca757600080fd5b80359060200191846001830284011164010000000083111715610cc957600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050612e8d565b005b348015610d2957600080fd5b50610d32612f1c565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b83811015610d75578082015181840152602081019050610d5a565b505050509050019250505060405180910390f35b348015610d9557600080fd5b50610d9e6130c5565b6040518082815260200191505060405180910390f35b348015610dc057600080fd5b50610e9a60048036036040811015610dd757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610e1457600080fd5b820183602082011115610e2657600080fd5b80359060200191846001830284011164010000000083111715610e4857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506130cb565b005b348015610ea857600080fd5b5061100a6004803603610100811015610ec057600080fd5b8101908080359060200190640100000000811115610edd57600080fd5b820183602082011115610eef57600080fd5b80359060200191846020830284011164010000000083111715610f1157600080fd5b909192939192939080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610f5c57600080fd5b820183602082011115610f6e57600080fd5b80359060200191846001830284011164010000000083111715610f9057600080fd5b9091929391929390803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506130ed565b005b34801561101857600080fd5b506110656004803603604081101561102f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506132ab565b60405180806020018373ffffffffffffffffffffffffffffffffffffffff168152602001828103825284818151815260200191508051906020019060200280838360005b838110156110c45780820151818401526020810190506110a9565b50505050905001935050505060405180910390f35b3480156110e557600080fd5b50611112600480360360208110156110fc57600080fd5b810190808035906020019092919050505061360e565b005b34801561112057600080fd5b50611238600480360361014081101561113857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561117f57600080fd5b82018360208201111561119157600080fd5b803590602001918460018302840111640100000000831117156111b357600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506137ad565b6040518082815260200191505060405180910390f35b34801561125a57600080fd5b506112bd6004803603604081101561127157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506137da565b005b3480156112cb57600080fd5b5061130e600480360360208110156112e257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613b61565b005b34801561131c57600080fd5b5061139f6004803603606081101561133357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613d4d565b005b3480156113ad57600080fd5b506113b66143ab565b6040518082815260200191505060405180910390f35b3480156113d857600080fd5b506114f060048036036101408110156113f057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561143757600080fd5b82018360208201111561144957600080fd5b8035906020019184600183028401116401000000008311171561146b57600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506143b5565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015611530578082015181840152602081019050611515565b50505050905090810190601f16801561155d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561157757600080fd5b506115ba6004803603602081101561158e57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061455d565b005b3480156115c857600080fd5b506115d16145b4565b6040518082815260200191505060405180910390f35b3480156115f357600080fd5b506116606004803603606081101561160a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050614632565b005b34801561166e57600080fd5b50611677614a5b565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156116b757808201518184015260208101905061169c565b50505050905090810190601f1680156116e45780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6116fa614a94565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156117645750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b801561179c57503073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b61180e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461190f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600081548092919060010191905055508173ffffffffffffffffffffffffffffffffffffffff167f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea2660405160405180910390a28060045414611ad457611ad381612aef565b5b5050565b611aec604182614b3790919063ffffffff16565b82511015611b62576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6000808060008060005b868110156123ca57611b7e8882614b71565b80945081955082965050505060008460ff1614156120035789898051906020012014611c12576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323700000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8260001c9450611c2c604188614b3790919063ffffffff16565b8260001c1015611ca4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8751611cbd60208460001c614ba090919063ffffffff16565b1115611d31576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60006020838a01015190508851611d6782611d5960208760001c614ba090919063ffffffff16565b614ba090919063ffffffff16565b1115611ddb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60606020848b010190506320c13b0b60e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168773ffffffffffffffffffffffffffffffffffffffff166320c13b0b8d846040518363ffffffff1660e01b8152600401808060200180602001838103835285818151815260200191508051906020019080838360005b83811015611e7d578082015181840152602081019050611e62565b50505050905090810190601f168015611eaa5780820380516001836020036101000a031916815260200191505b50838103825284818151815260200191508051906020019080838360005b83811015611ee3578082015181840152602081019050611ec8565b50505050905090810190601f168015611f105780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b158015611f2f57600080fd5b505afa158015611f43573d6000803e3d6000fd5b505050506040513d6020811015611f5957600080fd5b81019080805190602001909291905050507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614611ffc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5050612248565b60018460ff161415612117578260001c94508473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806120a057506000600860008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008c81526020019081526020016000205414155b612112576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b612247565b601e8460ff1611156121df5760018a60405160200180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c018281526020019150506040516020818303038152906040528051906020012060048603858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa1580156121ce573d6000803e3d6000fd5b505050602060405103519450612246565b60018a85858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015612239573d6000803e3d6000fd5b5050506020604051035194505b5b5b8573ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1611801561230f5750600073ffffffffffffffffffffffffffffffffffffffff16600260008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b80156123485750600173ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614155b6123ba576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323600000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8495508080600101915050611b6c565b50505050505050505050565b60008173ffffffffffffffffffffffffffffffffffffffff16600173ffffffffffffffffffffffffffffffffffffffff16141580156124a15750600073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b9050919050565b6000600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156125735750600073ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b9050919050565b6000804690508091505090565b60007fb648d3644f584ed1c2232d53c46d87e693586486ad0d1175f8656013110b714e3386868686604051808673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff1681526020018481526020018060200183600181111561260157fe5b8152602001828103825284818151815260200191508051906020019080838360005b8381101561263e578082015181840152602081019050612623565b50505050905090810190601f16801561266b5780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390a161268885858585614bbf565b9050949350505050565b600060606126a286868686612587565b915060405160203d0181016040523d81523d6000602083013e8091505094509492505050565b606060006020830267ffffffffffffffff811180156126e657600080fd5b506040519080825280601f01601f1916602001820160405280156127195781602001600182028036833780820191505090505b50905060005b838110156127445780850154806020830260208501015250808060010191505061271f565b508091505092915050565b60076020528060005260406000206000915090505481565b61276f614a94565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156127d95750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b61284b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461294c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff167fecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f844060405160405180910390a250565b612af7614a94565b600354811115612b6f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001811015612be6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b806004819055507f610f7ff2b304ae8903c3de74c60c6ab1f7d6226b3f52c5161905bb5ad4039c936004546040518082815260200191505060405180910390a150565b6000606060055433600454604051602001808481526020018373ffffffffffffffffffffffffffffffffffffffff168152602001828152602001935050505060405160208183030381529060405290507f66753cd2356569ee081232e3be8909b950e0a76c1f8460c3a5e3c2be32b11bed8d8d8d8d8d8d8d8d8d8d8d8c604051808d73ffffffffffffffffffffffffffffffffffffffff1681526020018c8152602001806020018a6001811115612cdc57fe5b81526020018981526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff168152602001806020018060200184810384528e8e82818152602001925080828437600081840152601f19601f820116905080830192505050848103835286818151815260200191508051906020019080838360005b83811015612d96578082015181840152602081019050612d7b565b50505050905090810190601f168015612dc35780820380516001836020036101000a031916815260200191505b50848103825285818151815260200191508051906020019080838360005b83811015612dfc578082015181840152602081019050612de1565b50505050905090810190601f168015612e295780820380516001836020036101000a031916815260200191505b509f5050505050505050505050505050505060405180910390a1612e568d8d8d8d8d8d8d8d8d8d8d614dc5565b9150509b9a5050505050505050505050565b6008602052816000526040600020602052806000526040600020600091509150505481565b6000600454905060008111612f0a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b612f1684848484611ad8565b50505050565b6060600060035467ffffffffffffffff81118015612f3957600080fd5b50604051908082528060200260200182016040528015612f685781602001602082028036833780820191505090505b50905060008060026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146130bc578083838151811061301357fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600260008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508180600101925050612fd2565b82935050505090565b60055481565b600080825160208401855af4806000523d6020523d600060403e60403d016000fd5b6131388a8a80806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f82011690508083019250505050505050896152f4565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161461317657613175846157f4565b5b6131c48787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506158c5565b60008211156131de576131dc82600060018685615b9b565b505b3373ffffffffffffffffffffffffffffffffffffffff167f141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a88b8b8b8b8960405180806020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281038252878782818152602001925060200280828437600081840152601f19601f820116905080830192505050965050505050505060405180910390a250505050505050505050565b60606000600173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1614806132ef57506132ee846123d6565b5b613361576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600083116133d7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303600000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8267ffffffffffffffff811180156133ee57600080fd5b5060405190808252806020026020018201604052801561341d5781602001602082028036833780820191505090505b5091506000600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1691505b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156134ef5750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b80156134fa57508381105b156135b5578183828151811061350c57fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1691508080600101915050613485565b600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614613603578260018203815181106135f857fe5b602002602001015191505b808352509250929050565b600073ffffffffffffffffffffffffffffffffffffffff16600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415613710576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330333000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001600860003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000838152602001908152602001600020819055503373ffffffffffffffffffffffffffffffffffffffff16817ff2a0eb156472d1440255b0d7c1e19cc07115d1051fe605b0dce69acfec884d9c60405160405180910390a350565b60006137c28c8c8c8c8c8c8c8c8c8c8c6143b5565b8051906020012090509b9a5050505050505050505050565b6137e2614a94565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415801561384c5750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b6138be576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146139be576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff167faab4fa2b463f581b2b32cb3b7e3b704b9ce37cc209b5fb4d77e593ace405427660405160405180910390a25050565b613b69614a94565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614613cdb578073ffffffffffffffffffffffffffffffffffffffff166301ffc9a77fe6d7a83a000000000000000000000000000000000000000000000000000000006040518263ffffffff1660e01b815260040180827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200191505060206040518083038186803b158015613c2d57600080fd5b505afa158015613c41573d6000803e3d6000fd5b505050506040513d6020811015613c5757600080fd5b8101908080519060200190929190505050613cda576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475333303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b60007f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c860001b90508181558173ffffffffffffffffffffffffffffffffffffffff167f1151116914515bc0891ff9047a6cb32cf902546f83066499bcf8ba33d2353fa260405160405180910390a25050565b613d55614a94565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614158015613dbf5750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b8015613df757503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b613e69576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614613f6a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614158015613fd45750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b614046576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614614146576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff167ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf60405160405180910390a28073ffffffffffffffffffffffffffffffffffffffff167f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea2660405160405180910390a2505050565b6000600454905090565b606060007fbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d860001b8d8d8d8d60405180838380828437808301925050509250505060405180910390208c8c8c8c8c8c8c604051602001808c81526020018b73ffffffffffffffffffffffffffffffffffffffff1681526020018a815260200189815260200188600181111561444657fe5b81526020018781526020018681526020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019b505050505050505050505050604051602081830303815290604052805190602001209050601960f81b600160f81b6144d26145b4565b8360405160200180857effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152600101847effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526001018381526020018281526020019450505050506040516020818303038152906040529150509b9a5050505050505050505050565b614565614a94565b61456e816157f4565b8073ffffffffffffffffffffffffffffffffffffffff167f5ac6c46c93c8d0e53714ba3b53db3e7c046da994313d7ed0d192028bc7c228b060405160405180910390a250565b60007f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a7946921860001b6145e261257a565b30604051602001808481526020018381526020018273ffffffffffffffffffffffffffffffffffffffff168152602001935050505060405160208183030381529060405280519060200120905090565b61463a614a94565b8060016003540310156146b5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415801561471f5750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b614791576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614614891576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600360008154809291906001900391905055508173ffffffffffffffffffffffffffffffffffffffff167ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf60405160405180910390a28060045414614a5657614a5581612aef565b5b505050565b6040518060400160405280600581526020017f312e342e3100000000000000000000000000000000000000000000000000000081525081565b3073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614614b35576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330333100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b565b600080831415614b4a5760009050614b6b565b6000828402905082848281614b5b57fe5b0414614b6657600080fd5b809150505b92915050565b60008060008360410260208101860151925060408101860151915060ff60418201870151169350509250925092565b600080828401905083811015614bb557600080fd5b8091505092915050565b6000600173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614158015614c8a5750600073ffffffffffffffffffffffffffffffffffffffff16600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b614cfc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b614d29858585857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff615da1565b90508015614d79573373ffffffffffffffffffffffffffffffffffffffff167f6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb860405160405180910390a2614dbd565b3373ffffffffffffffffffffffffffffffffffffffff167facd2c8702804128fdb0db2bb49f6d127dd0181c13fd45dbfe16de0930e2bd37560405160405180910390a25b949350505050565b6000806000614ddf8e8e8e8e8e8e8e8e8e8e6005546143b5565b905060056000815480929190600101919050555080805190602001209150614e08828286612e8d565b506000614e13615ded565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614614ff9578073ffffffffffffffffffffffffffffffffffffffff166375f0bb528f8f8f8f8f8f8f8f8f8f8f336040518d63ffffffff1660e01b8152600401808d73ffffffffffffffffffffffffffffffffffffffff1681526020018c8152602001806020018a6001811115614eb657fe5b81526020018981526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff168152602001806020018473ffffffffffffffffffffffffffffffffffffffff16815260200183810383528d8d82818152602001925080828437600081840152601f19601f820116905080830192505050838103825285818151815260200191508051906020019080838360005b83811015614f88578082015181840152602081019050614f6d565b50505050905090810190601f168015614fb55780820380516001836020036101000a031916815260200191505b509e505050505050505050505050505050600060405180830381600087803b158015614fe057600080fd5b505af1158015614ff4573d6000803e3d6000fd5b505050505b6101f46150206109c48b01603f60408d028161501157fe5b04615e1e90919063ffffffff16565b015a1015615096576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60005a90506150ff8f8f8f8f8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e60008d146150f4578e6150fa565b6109c45a035b615da1565b93506151145a82615e3890919063ffffffff16565b90508380615123575060008a14155b8061512f575060008814155b6151a1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6000808911156151bb576151b8828b8b8b8b615b9b565b90505b84156151fe57837f442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e826040518082815260200191505060405180910390a2615237565b837f23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d23826040518082815260200191505060405180910390a25b5050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146152e3578073ffffffffffffffffffffffffffffffffffffffff16639327136883856040518363ffffffff1660e01b815260040180838152602001821515815260200192505050600060405180830381600087803b1580156152ca57600080fd5b505af11580156152de573d6000803e3d6000fd5b505050505b50509b9a5050505050505050505050565b60006004541461536c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b81518111156153e3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600181101561545a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60006001905060005b835181101561576057600084828151811061547a57fe5b60200260200101519050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156154ee5750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561552657503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561555e57508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614155b6155d0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146156d1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b80600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550809250508080600101915050615463565b506001600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550825160038190555081600481905550505050565b3073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415615896576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475334303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60007f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d560001b90508181555050565b600073ffffffffffffffffffffffffffffffffffffffff1660016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146159c7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001806000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614615b9757615a8382615e58565b615af5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b615b248260008360017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff615da1565b615b96576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b5050565b600080600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614615bd85782615bda565b325b9050600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415615cf257615c443a8610615c21573a615c23565b855b615c36888a614ba090919063ffffffff16565b614b3790919063ffffffff16565b91508073ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f19350505050615ced576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b615d97565b615d1785615d09888a614ba090919063ffffffff16565b614b3790919063ffffffff16565b9150615d24848284615e6b565b615d96576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b5095945050505050565b6000600180811115615daf57fe5b836001811115615dbb57fe5b1415615dd4576000808551602087018986f49050615de4565b600080855160208701888a87f190505b95945050505050565b6000807f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c860001b9050805491505090565b600081831015615e2e5781615e30565b825b905092915050565b600082821115615e4757600080fd5b600082840390508091505092915050565b600080823b905060008111915050919050565b60008063a9059cbb8484604051602401808373ffffffffffffffffffffffffffffffffffffffff168152602001828152602001925050506040516020818303038152906040529060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050602060008251602084016000896127105a03f13d60008114615f125760208114615f1a5760009350615f25565b819350615f25565b600051158215171593505b505050939250505056fea2646970667358221220cd2bdb262f44c0636136d3c6bfed2c2458921f82c3bf476053bd2e9ac618b2da64736f6c63430007060033", + "balance": "0x0" + }, + "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67": { + "contractName": "SafeProxyFactory", + "storage": {}, + "code": "0x608060405234801561001057600080fd5b50600436106100575760003560e01c80631688f0b91461005c5780633408e4701461016b57806353e5d93514610189578063d18af54d1461020c578063ec9e80bb1461033b575b600080fd5b61013f6004803603606081101561007257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001906401000000008111156100af57600080fd5b8201836020820111156100c157600080fd5b803590602001918460018302840111640100000000831117156100e357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192908035906020019092919050505061044a565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6101736104fe565b6040518082815260200191505060405180910390f35b61019161050b565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101d15780820151818401526020810190506101b6565b50505050905090810190601f1680156101fe5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61030f6004803603608081101561022257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019064010000000081111561025f57600080fd5b82018360208201111561027157600080fd5b8035906020019184600183028401116401000000008311171561029357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610536565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b61041e6004803603606081101561035157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019064010000000081111561038e57600080fd5b8201836020820111156103a057600080fd5b803590602001918460018302840111640100000000831117156103c257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803590602001909291905050506106e5565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60008083805190602001208360405160200180838152602001828152602001925050506040516020818303038152906040528051906020012090506104908585836107a8565b91508173ffffffffffffffffffffffffffffffffffffffff167f4f51faf6c4561ff95f067657e43439f0f856d97c04d9ec9070a6199ad418e23586604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a2509392505050565b6000804690508091505090565b60606040518060200161051d906109c5565b6020820181038252601f19601f82011660405250905090565b6000808383604051602001808381526020018273ffffffffffffffffffffffffffffffffffffffff1660601b8152601401925050506040516020818303038152906040528051906020012060001c905061059186868361044a565b9150600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16146106dc578273ffffffffffffffffffffffffffffffffffffffff16631e52b518838888886040518563ffffffff1660e01b8152600401808573ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff16815260200180602001838152602001828103825284818151815260200191508051906020019080838360005b83811015610674578082015181840152602081019050610659565b50505050905090810190601f1680156106a15780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b1580156106c357600080fd5b505af11580156106d7573d6000803e3d6000fd5b505050505b50949350505050565b6000808380519060200120836106f96104fe565b60405160200180848152602001838152602001828152602001935050505060405160208183030381529060405280519060200120905061073a8585836107a8565b91508173ffffffffffffffffffffffffffffffffffffffff167f4f51faf6c4561ff95f067657e43439f0f856d97c04d9ec9070a6199ad418e23586604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a2509392505050565b60006107b3846109b2565b610825576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f53696e676c65746f6e20636f6e7472616374206e6f74206465706c6f7965640081525060200191505060405180910390fd5b600060405180602001610837906109c5565b6020820181038252601f19601f820116604052508573ffffffffffffffffffffffffffffffffffffffff166040516020018083805190602001908083835b602083106108985780518252602082019150602081019050602083039250610875565b6001836020036101000a038019825116818451168082178552505050505050905001828152602001925050506040516020818303038152906040529050828151826020016000f59150600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610984576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260138152602001807f437265617465322063616c6c206661696c65640000000000000000000000000081525060200191505060405180910390fd5b6000845111156109aa5760008060008651602088016000875af114156109a957600080fd5b5b509392505050565b600080823b905060008111915050919050565b6101e6806109d38339019056fe608060405234801561001057600080fd5b506040516101e63803806101e68339818101604052602081101561003357600080fd5b8101908080519060200190929190505050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156100ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806101c46022913960400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060ab806101196000396000f3fe608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea264697066735822122003d1488ee65e08fa41e58e888a9865554c535f2c77126a82cb4c0f917f31441364736f6c63430007060033496e76616c69642073696e676c65746f6e20616464726573732070726f7669646564a26469706673582212200fd975ca8e62d9bf08aa3d09c74b9bdc9d7acba7621835be4187989ddd0e54b164736f6c63430007060033", + "balance": "0x0" + }, + "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526": { + "contractName": "MultiSend", + "storage": {}, + "code": "0x60806040526004361061001e5760003560e01c80638d80ff0a14610023575b600080fd5b6100dc6004803603602081101561003957600080fd5b810190808035906020019064010000000081111561005657600080fd5b82018360208201111561006857600080fd5b8035906020019184600183028401116401000000008311171561008a57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506100de565b005b7f00000000000000000000000038869bf66a61cf6bdb996a6ae40d5853fd43b52673ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff161415610183576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260308152602001806102106030913960400191505060405180910390fd5b805160205b8181101561020a578083015160f81c6001820184015160601c6015830185015160358401860151605585018701600085600081146101cd57600181146101dd576101e8565b6000808585888a5af191506101e8565b6000808585895af491505b5060008114156101f757600080fd5b8260550187019650505050505050610188565b50505056fe4d756c746953656e642073686f756c64206f6e6c792062652063616c6c6564207669612064656c656761746563616c6ca264697066735822122021102e6d5bc1da75411b41fe2792a1748bf5c49c794e51e81405ccd2399da13564736f6c63430007060033", + "balance": "0x0" + }, + "0x9641d764fc13c8B624c04430C7356C1C7C8102e2": { + "contractName": "MultiSendCallOnly", + "storage": {}, + "code": "0x60806040526004361061001e5760003560e01c80638d80ff0a14610023575b600080fd5b6100dc6004803603602081101561003957600080fd5b810190808035906020019064010000000081111561005657600080fd5b82018360208201111561006857600080fd5b8035906020019184600183028401116401000000008311171561008a57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506100de565b005b805160205b8181101561015f578083015160f81c6001820184015160601c60158301850151603584018601516055850187016000856000811461012857600181146101385761013d565b6000808585888a5af1915061013d565b600080fd5b50600081141561014c57600080fd5b82605501870196505050505050506100e3565b50505056fea26469706673582212208d297bb003abee230b5dfb38774688f37a6fbb97a82a21728e8049b2acb9b73564736f6c63430007060033", + "balance": "0x0" + }, + "0x9b35Af71d77eaf8d7e40252370304687390A1A52": { + "contractName": "CreateCall", + "storage": {}, + "code": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80634847be6f1461003b5780634c8c9ea114610134575b600080fd5b6101086004803603606081101561005157600080fd5b81019080803590602001909291908035906020019064010000000081111561007857600080fd5b82018360208201111561008a57600080fd5b803590602001918460018302840111640100000000831117156100ac57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190505050610223565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6101f76004803603604081101561014a57600080fd5b81019080803590602001909291908035906020019064010000000081111561017157600080fd5b82018360208201111561018357600080fd5b803590602001918460018302840111640100000000831117156101a557600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061031d565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60008183518460200186f59050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156102d3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260198152602001807f436f756c64206e6f74206465706c6f7920636f6e74726163740000000000000081525060200191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff167f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b51160405160405180910390a29392505050565b600081516020830184f09050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156103cc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260198152602001807f436f756c64206e6f74206465706c6f7920636f6e74726163740000000000000081525060200191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff167f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b51160405160405180910390a29291505056fea26469706673582212204f5b682b785a4bde69d22ce13d07ddd6c58ec565b71a1a95733b0ee584b5e47864736f6c63430007060033", + "balance": "0x0" + }, + "0xd53cd0aB83D845Ac265BE939c57F53AD838012c9": { + "contractName": "SignMessageLib", + "storage": {}, + "code": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80630a1028c41461003b57806385a5affe1461010a575b600080fd5b6100f46004803603602081101561005157600080fd5b810190808035906020019064010000000081111561006e57600080fd5b82018360208201111561008057600080fd5b803590602001918460018302840111640100000000831117156100a257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610183565b6040518082815260200191505060405180910390f35b6101816004803603602081101561012057600080fd5b810190808035906020019064010000000081111561013d57600080fd5b82018360208201111561014f57600080fd5b8035906020019184600183028401116401000000008311171561017157600080fd5b90919293919293905050506102f4565b005b6000807f60b3cbf8b4a223d68d641b3b6ddf9a298e7f33710cf3d3a9d1146b5a6150fbca60001b83805190602001206040516020018083815260200182815260200192505050604051602081830303815290604052805190602001209050601960f81b600160f81b3073ffffffffffffffffffffffffffffffffffffffff1663f698da256040518163ffffffff1660e01b815260040160206040518083038186803b15801561023157600080fd5b505afa158015610245573d6000803e3d6000fd5b505050506040513d602081101561025b57600080fd5b81019080805190602001909291905050508360405160200180857effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152600101847effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260010183815260200182815260200194505050505060405160208183030381529060405280519060200120915050919050565b600061034383838080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050610183565b905060016007600083815260200190815260200160002081905550807fe7f4675038f4f6034dfcbbb24c4dc08e4ebf10eb9d257d3d02c0f38d122ac6e460405160405180910390a250505056fea2646970667358221220b6edbb5eb57b87c6371f0f7b62449c6a356d2bbc694eefa3b35338ca1d8fbc3564736f6c63430007060033", + "balance": "0x0" + }, + "0x3d4BA2E0884aa488718476ca2FB8Efc291A46199": { + "contractName": "SimulateTxAccessor", + "storage": {}, + "code": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c80631c5fb21114610030575b600080fd5b6100de6004803603608081101561004657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561008d57600080fd5b82018360208201111561009f57600080fd5b803590602001918460018302840111640100000000831117156100c157600080fd5b9091929391929390803560ff169060200190929190505050610169565b60405180848152602001831515815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561012c578082015181840152602081019050610111565b50505050905090810190601f1680156101595780820380516001836020036101000a031916815260200191505b5094505050505060405180910390f35b60008060607f0000000000000000000000003d4ba2e0884aa488718476ca2fb8efc291a4619973ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff161415610213576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260398152602001806102e46039913960400191505060405180910390fd5b60005a9050610269898989898080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050885a610297565b92505a8103935060405160203d0181016040523d81523d6000602083013e8092505050955095509592505050565b60006001808111156102a557fe5b8360018111156102b157fe5b14156102ca576000808551602087018986f490506102da565b600080855160208701888a87f190505b9594505050505056fe53696d756c61746554784163636573736f722073686f756c64206f6e6c792062652063616c6c6564207669612064656c656761746563616c6ca264697066735822122066ec514c62d72e456c1ac0997627506854acd03fceabe3c0532054bd50122c9064736f6c63430007060033", + "balance": "0x0" + }, + "0x526643F69b81B008F46d95CD5ced5eC0edFFDaC6": { + "contractName": "SafeMigration", + "storage": {}, + "code": "0x608060405234801561001057600080fd5b50600436106100885760003560e01c80639bf47d6e1161005b5780639bf47d6e14610109578063caa12add1461013d578063ed007fc614610171578063f6682ab01461017b57610088565b806307f464a41461008d5780630d7101f71461009757806368cb3d94146100cb57806372f7a956146100d5575b600080fd5b610095610185565b005b61009f6102f9565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100d361031d565b005b6100dd61046d565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b610111610491565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6101456104b5565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6101796104d9565b005b610183610629565b005b7f000000000000000000000000526643f69b81b008f46d95cd5ced5ec0edffdac673ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff16141561022a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603081526020018061079e6030913960400191505060405180910390fd5b7f00000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c7626000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f75e41bc35ff1bf14d81d1d2f649c0084a0f974f9289c803ec9898eeec4c8d0b87f00000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c762604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a1565b7f000000000000000000000000fd0732dc9e303f09fcef3a7388ad10a83459ec9981565b7f000000000000000000000000526643f69b81b008f46d95cd5ced5ec0edffdac673ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff1614156103c2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603081526020018061079e6030913960400191505060405180910390fd5b6103ca610185565b3073ffffffffffffffffffffffffffffffffffffffff1663f08a03237f000000000000000000000000fd0732dc9e303f09fcef3a7388ad10a83459ec996040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff168152602001915050600060405180830381600087803b15801561045357600080fd5b505af1158015610467573d6000803e3d6000fd5b50505050565b7f000000000000000000000000526643f69b81b008f46d95cd5ced5ec0edffdac681565b7f00000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c76281565b7f00000000000000000000000041675c099f32341bf84bfc5382af534df5c7461a81565b7f000000000000000000000000526643f69b81b008f46d95cd5ced5ec0edffdac673ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff16141561057e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603081526020018061079e6030913960400191505060405180910390fd5b610586610629565b3073ffffffffffffffffffffffffffffffffffffffff1663f08a03237f000000000000000000000000fd0732dc9e303f09fcef3a7388ad10a83459ec996040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff168152602001915050600060405180830381600087803b15801561060f57600080fd5b505af1158015610623573d6000803e3d6000fd5b50505050565b7f000000000000000000000000526643f69b81b008f46d95cd5ced5ec0edffdac673ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff1614156106ce576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603081526020018061079e6030913960400191505060405180910390fd5b7f00000000000000000000000041675c099f32341bf84bfc5382af534df5c7461a6000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f75e41bc35ff1bf14d81d1d2f649c0084a0f974f9289c803ec9898eeec4c8d0b87f00000000000000000000000041675c099f32341bf84bfc5382af534df5c7461a604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a156fe4d6967726174696f6e2073686f756c64206f6e6c792062652063616c6c6564207669612064656c656761746563616c6ca2646970667358221220225c3ae21c323c66a6ac816f6d90cf900873611c034a6b26050d6022c9efdafd64736f6c63430007060033", + "balance": "0x0" + }, + "0xfF83F6335d8930cBad1c0D439A841f01888D9f69": { + "contractName": "SafeToL2Migration", + "storage": {}, + "code": "0x608060405234801561001057600080fd5b50600436106100415760003560e01c806372f7a95614610046578063d9a208121461007a578063ef2624ae146100de575b600080fd5b61004e610122565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100dc6004803603604081101561009057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610146565b005b610120600480360360208110156100f457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506109dd565b005b7f000000000000000000000000ff83f6335d8930cbad1c0d439a841f01888d9f6981565b7f000000000000000000000000ff83f6335d8930cbad1c0d439a841f01888d9f6973ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff1614156101eb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260308152602001806115476030913960400191505060405180910390fd5b600160055414610246576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806115f46022913960400191505060405180910390fd5b61024f816110b8565b6102a4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806115776021913960400191505060405180910390fd5b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663ffa1ad746040518163ffffffff1660e01b815260040160006040518083038186803b15801561030d57600080fd5b505afa158015610321573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f82011682018060405250602081101561034b57600080fd5b810190808051604051939291908464010000000082111561036b57600080fd5b8382019150602082018581111561038157600080fd5b825186600182028301116401000000008211171561039e57600080fd5b8083526020830192505050908051906020019080838360005b838110156103d25780820151818401526020810190506103b7565b50505050905090810190601f1680156103ff5780820380516001836020036101000a031916815260200191505b506040525050506040516020018082805190602001908083835b6020831061043c5780518252602082019150602081019050602083039250610419565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120905060405160200180807f312e312e310000000000000000000000000000000000000000000000000000008152506005019050604051602081830303815290604052805190602001208114610517576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180611598602b913960400191505060405180910390fd5b60008373ffffffffffffffffffffffffffffffffffffffff1663ffa1ad746040518163ffffffff1660e01b815260040160006040518083038186803b15801561055f57600080fd5b505afa158015610573573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f82011682018060405250602081101561059d57600080fd5b81019080805160405193929190846401000000008211156105bd57600080fd5b838201915060208201858111156105d357600080fd5b82518660018202830111640100000000821117156105f057600080fd5b8083526020830192505050908051906020019080838360005b83811015610624578082015181840152602081019050610609565b50505050905090810190601f1680156106515780820380516001836020036101000a031916815260200191505b506040525050506040516020018082805190602001908083835b6020831061068e578051825260208201915060208101905060208303925061066b565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120905060405160200180807f312e332e30000000000000000000000000000000000000000000000000000000815250600501905060405160208183030381529060405280519060200120811480610764575060405160200180807f312e342e3100000000000000000000000000000000000000000000000000000081525060050190506040516020818303038152906040528051906020012081145b6107b9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180611598602b913960400191505060405180910390fd5b60003090508073ffffffffffffffffffffffffffffffffffffffff1663f08a0323856040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff168152602001915050600060405180830381600087803b15801561082757600080fd5b505af115801561083b573d6000803e3d6000fd5b505050507f000000000000000000000000ff83f6335d8930cbad1c0d439a841f01888d9f6973ffffffffffffffffffffffffffffffffffffffff167f141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a861089f6110cb565b60045460008860405180806020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff168152602001828103825286818151815260200191508051906020019060200280838360005b8381101561092657808201518184015260208101905061090b565b505050509050019550505050505060405180910390a2600063d9a208128686604051602401808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff168152602001925050506040516020818303038152906040529060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505090506109d58682611279565b505050505050565b7f000000000000000000000000ff83f6335d8930cbad1c0d439a841f01888d9f6973ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff161415610a82576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260308152602001806115476030913960400191505060405180910390fd5b600160055414610add576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806115f46022913960400191505060405180910390fd5b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415610b88576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806115246023913960400191505060405180910390fd5b60008173ffffffffffffffffffffffffffffffffffffffff1663ffa1ad746040518163ffffffff1660e01b815260040160006040518083038186803b158015610bd057600080fd5b505afa158015610be4573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052506020811015610c0e57600080fd5b8101908080516040519392919084640100000000821115610c2e57600080fd5b83820191506020820185811115610c4457600080fd5b8251866001820283011164010000000082111715610c6157600080fd5b8083526020830192505050908051906020019080838360005b83811015610c95578082015181840152602081019050610c7a565b50505050905090810190601f168015610cc25780820380516001836020036101000a031916815260200191505b506040525050506040516020018082805190602001908083835b60208310610cff5780518252602082019150602081019050602083039250610cdc565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405280519060200120905060008373ffffffffffffffffffffffffffffffffffffffff1663ffa1ad746040518163ffffffff1660e01b815260040160006040518083038186803b158015610d8457600080fd5b505afa158015610d98573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052506020811015610dc257600080fd5b8101908080516040519392919084640100000000821115610de257600080fd5b83820191506020820185811115610df857600080fd5b8251866001820283011164010000000082111715610e1557600080fd5b8083526020830192505050908051906020019080838360005b83811015610e49578082015181840152602081019050610e2e565b50505050905090810190601f168015610e765780820380516001836020036101000a031916815260200191505b506040525050506040516020018082805190602001908083835b60208310610eb35780518252602082019150602081019050602083039250610e90565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051602081830303815290604052805190602001209050808214610f48576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260318152602001806115c36031913960400191505060405180910390fd5b60405160200180807f312e332e30000000000000000000000000000000000000000000000000000000815250600501905060405160208183030381529060405280519060200120811480610fe1575060405160200180807f312e342e3100000000000000000000000000000000000000000000000000000081525060050190506040516020818303038152906040528051906020012081145b611036576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180611598602b913960400191505060405180910390fd5b600063ef2624ae85604051602401808273ffffffffffffffffffffffffffffffffffffffff1681526020019150506040516020818303038152906040529060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505090506110b18582611279565b5050505050565b600080823b905060008111915050919050565b6060600060035467ffffffffffffffff811180156110e857600080fd5b506040519080825280602002602001820160405280156111175781602001602082028036833780820191505090505b509050600060019050600080600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b8273ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161461126f57808483815181106111c657fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600260008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508180600101925050611186565b8394505050505090565b816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008033600454604051602001808481526020018373ffffffffffffffffffffffffffffffffffffffff168152602001828152602001935050505060405160208183030381529060405290507f66753cd2356569ee081232e3be8909b950e0a76c1f8460c3a5e3c2be32b11bed7f000000000000000000000000ff83f6335d8930cbad1c0d439a841f01888d9f69600084600160008060008060008a604051808b73ffffffffffffffffffffffffffffffffffffffff1681526020018a81526020018060200189600181111561138b57fe5b81526020018881526020018781526020018681526020018573ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff168152602001806020018060200184810384528c818151815260200191508051906020019080838360005b8381101561141a5780820151818401526020810190506113ff565b50505050905090810190601f1680156114475780820380516001836020036101000a031916815260200191505b50848103835260008152602001848103825285818151815260200191508051906020019080838360005b8381101561148c578082015181840152602081019050611471565b50505050905090810190601f1680156114b95780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a17f75e41bc35ff1bf14d81d1d2f649c0084a0f974f9289c803ec9898eeec4c8d0b883604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150505056fe5361666520697320616c7265616479207573696e67207468652073696e676c65746f6e4d6967726174696f6e2073686f756c64206f6e6c792062652063616c6c6564207669612064656c656761746563616c6c66616c6c6261636b48616e646c6572206973206e6f74206120636f6e747261637450726f76696465642073696e676c65746f6e2076657273696f6e206973206e6f7420737570706f727465644c322073696e676c65746f6e206d757374206d617463682063757272656e742076657273696f6e2073696e676c65746f6e53616665206d7573742068617665206e6f7420657865637574656420616e79207478a2646970667358221220d914c30647fc923b19a050a72872aeedb4f96d94ca6646a06297dff0c83f728564736f6c63430007060033", + "balance": "0x0" + }, + "0xBD89A1CE4DDe368FFAB0eC35506eEcE0b1fFdc54": { + "contractName": "SafeToL2Setup", + "storage": {}, + "code": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063fe51f64314610030575b600080fd5b6100726004803603602081101561004657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610074565b005b7f000000000000000000000000bd89a1ce4dde368ffab0ec35506eece0b1ffdc5473ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff161415610119576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260348152602001806102a96034913960400191505060405180910390fd5b600060055414610174576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806102dd6022913960400191505060405180910390fd5b80600061018082610295565b14156101f4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601c8152602001807f4163636f756e7420646f65736e277420636f6e7461696e20636f64650000000081525060200191505060405180910390fd5b60016101fe6102a0565b1461029157816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f75e41bc35ff1bf14d81d1d2f649c0084a0f974f9289c803ec9898eeec4c8d0b882604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15b5050565b6000813b9050919050565b60004690509056fe53616665546f4c3253657475702073686f756c64206f6e6c792062652063616c6c6564207669612064656c656761746563616c6c53616665206d7573742068617665206e6f7420657865637574656420616e79207478a264697066735822122023649cd94e3067c8a913b2bbb4a32dc4fea9be2d0f070a4b7403c3e4e8db452464736f6c63430007060033", "balance": "0x0" } } diff --git a/src/Nethermind/Chains/swan-mainnet.json.zst b/src/Nethermind/Chains/swan-mainnet.json.zst index 12fee6b15512..3b5d192de95f 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 ab19326a6977..d928fac7e896 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/taiko-hekla.json b/src/Nethermind/Chains/taiko-hekla.json deleted file mode 100644 index ce1d71ff905e..000000000000 --- a/src/Nethermind/Chains/taiko-hekla.json +++ /dev/null @@ -1,293 +0,0 @@ -{ - "name": "Taiko Hekla", - "dataDir": "taiko-hekla", - "engine": { - "Taiko": { - "ontakeTransition": "0xcd340", - "pacayaTransition": "0x13d5b0", - "taikoL2Address": "0x1670090000000000000000000000000000010001" - } - }, - "params": { - "chainId": "167009", - "maxCodeSize": "0x6000", - "maxCodeSizeTransition": "0x0", - "eip150Transition": "0x0", - "eip160Transition": "0x0", - "eip161abcTransition": "0x0", - "eip161dTransition": "0x0", - "eip155Transition": "0x0", - "eip140Transition": "0x0", - "eip211Transition": "0x0", - "eip214Transition": "0x0", - "eip658Transition": "0x0", - "eip145Transition": "0x0", - "eip1014Transition": "0x0", - "eip1052Transition": "0x0", - "eip1283Transition": "0x0", - "eip1283DisableTransition": "0x0", - "eip152Transition": "0x0", - "eip1108Transition": "0x0", - "eip1344Transition": "0x0", - "eip1884Transition": "0x0", - "eip2028Transition": "0x0", - "eip2200Transition": "0x0", - "eip2565Transition": "0x0", - "eip2929Transition": "0x0", - "eip2930Transition": "0x0", - "eip1559Transition": "0x0", - "eip1559FeeCollectorTransition": "0x0", - "feeCollector": "0x1670090000000000000000000000000000010001", - "eip1559ElasticityMultiplier": "0x2", - "eip1559BaseFeeMaxChangeDenominator": "0x8", - "eip1559BaseFeeMinValue": "0x86ff51", - "eip3198Transition": "0x0", - "eip3529Transition": "0x0", - "eip3541Transition": "0x0", - "eip4895TransitionTimestamp": "0x0", - "eip3651TransitionTimestamp": "0x0", - "eip3855TransitionTimestamp": "0x0", - "eip3860TransitionTimestamp": "0x0", - "terminalTotalDifficulty": "0", - "eip1559BaseFeeMinValueTransition": "0xcd340" - }, - "genesis": { - "seal": { - "ethereum": { - "nonce": "0x0000000000000000", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" - } - }, - "number": "0x0", - "difficulty": "0x0", - "author": "0x0000000000000000000000000000000000000000", - "timestamp": "0x0", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "extraData": "0x", - "baseFeePerGas": "0x989680", - "gasLimit": "0xe4e1c0" - }, - "nodes": [ - "enode://1733a899719c64edc8ad6818598b6b9aa41889297a7ee7b9cbf3e610d4df2e207b0e04fd40060a36f020116ab5ad451201e448fc224cd38b0a0d5fcbb1d2c812@34.126.109.163:30303", - "enode://3c7e00eff6a98f5d49084db988b9bee9cab3338ee809d88e41318dc7ea7fb67ab8e8a923e4a9f193fecd7698ef92c0977e07ac850e10777bdd11cc25045d63bf@35.198.236.33:30303", - "enode://eb5079aae185d5d8afa01bfd2d349da5b476609aced2b57c90142556cf0ee4a152bcdd724627a7de97adfc2a68af5742a8f58781366e6a857d4bde98de6fe986@34.66.210.65:30303", - "enode://2294f526cbb7faa778192289c252307420532191438ce821d3c50232e019a797bda8c8f8541de0847e953bb03096123856935e32294de9814d15d120131499ba@34.72.186.213:30303" - ], - "accounts": { - "0xd26010814C620F80A70cbD58cE353A10fa05491a": { - "balance": "0xfffffffffffffacbbb7ca13a7fffffff" - }, - "0xd2b932FaCeb6FB733394e8445050daE6c025F9Be": { - "balance": "0xfffffffffffffacbbb7ca13a7fffffff" - }, - "0x0167009000000000000000000000000000000006": { - "contractName": "SharedAddressManagerImpl", - "storage": { - "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190" - }, - "code": "0x6080604052600436106100fa575f3560e01c80635c975abb116100925780638da5cb5b116100625780638da5cb5b14610299578063a86f9d9e146102b6578063d8f4648f146102d5578063e30c3978146102f4578063f2fde38b14610311575f80fd5b80635c975abb1461022d578063715018a61461025d57806379ba5097146102715780638456cb5914610285575f80fd5b80633eb6b8cf116100cd5780633eb6b8cf146101c55780633f4ba83a146101e45780634f1ef286146101f857806352d1902d1461020b575f80fd5b806319ab453c146100fe57806328f713cc1461011f5780633659cfe6146101875780633ab76e9f146101a6575b5f80fd5b348015610109575f80fd5b5061011d610118366004610f59565b610330565b005b34801561012a575f80fd5b5061016a610139366004610f90565b67ffffffffffffffff919091165f90815260fb6020908152604080832093835292905220546001600160a01b031690565b6040516001600160a01b0390911681526020015b60405180910390f35b348015610192575f80fd5b5061011d6101a1366004610f59565b610442565b3480156101b1575f80fd5b5060975461016a906001600160a01b031681565b3480156101d0575f80fd5b5061016a6101df366004610fc7565b61051f565b3480156101ef575f80fd5b5061011d610535565b61011d610206366004611014565b6105b4565b348015610216575f80fd5b5061021f61067f565b60405190815260200161017e565b348015610238575f80fd5b5061024d60c954610100900460ff1660021490565b604051901515815260200161017e565b348015610268575f80fd5b5061011d610730565b34801561027c575f80fd5b5061011d610741565b348015610290575f80fd5b5061011d6107b8565b3480156102a4575f80fd5b506033546001600160a01b031661016a565b3480156102c1575f80fd5b5061016a6102d03660046110d2565b610837565b3480156102e0575f80fd5b5061011d6102ef3660046110fc565b610843565b3480156102ff575f80fd5b506065546001600160a01b031661016a565b34801561031c575f80fd5b5061011d61032b366004610f59565b610921565b5f54610100900460ff161580801561034e57505f54600160ff909116105b806103675750303b15801561036757505f5460ff166001145b6103cf5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b5f805460ff1916600117905580156103f0575f805461ff0019166101001790555b6103f982610992565b801561043e575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6001600160a01b037f000000000000000000000000016700900000000000000000000000000000000616300361048a5760405162461bcd60e51b81526004016103c690611139565b7f00000000000000000000000001670090000000000000000000000000000000066001600160a01b03166104d25f80516020611273833981519152546001600160a01b031690565b6001600160a01b0316146104f85760405162461bcd60e51b81526004016103c690611185565b610501816109c2565b604080515f8082526020820190925261051c918391906109ca565b50565b5f61052b848484610b39565b90505b9392505050565b61054960c954610100900460ff1660021490565b6105665760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166101001790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a16105b2335f610c27565b565b6001600160a01b037f00000000000000000000000001670090000000000000000000000000000000061630036105fc5760405162461bcd60e51b81526004016103c690611139565b7f00000000000000000000000001670090000000000000000000000000000000066001600160a01b03166106445f80516020611273833981519152546001600160a01b031690565b6001600160a01b03161461066a5760405162461bcd60e51b81526004016103c690611185565b610673826109c2565b61043e828260016109ca565b5f306001600160a01b037f0000000000000000000000000167009000000000000000000000000000000006161461071e5760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c000000000000000060648201526084016103c6565b505f8051602061127383398151915290565b610738610c3f565b6105b25f610c99565b60655433906001600160a01b031681146107af5760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016103c6565b61051c81610c99565b6107cc60c954610100900460ff1660021490565b156107ea5760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a16105b2336001610c27565b5f61052e468484610b39565b61084b610c3f565b67ffffffffffffffff83165f90815260fb602090815260408083208584529091529020546001600160a01b0390811690821681900361089d576040516327b026fb60e21b815260040160405180910390fd5b67ffffffffffffffff84165f81815260fb6020908152604080832087845282529182902080546001600160a01b0319166001600160a01b038781169182179092558351908152908516918101919091528592917f500dcd607a98daece9bccc2511bf6032471252929de73caf507aae0e082f8453910160405180910390a350505050565b610929610c3f565b606580546001600160a01b0383166001600160a01b0319909116811790915561095a6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6109b06001600160a01b038216156109aa5781610c99565b33610c99565b5060c9805461ff001916610100179055565b61051c610c3f565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff1615610a02576109fd83610cb2565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015610a5c575060408051601f3d908101601f19168201909252610a59918101906111d1565b60015b610abf5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b60648201526084016103c6565b5f805160206112738339815191528114610b2d5760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b60648201526084016103c6565b506109fd838383610d4d565b6097545f906001600160a01b0316610b6457604051638ed88b2560e01b815260040160405180910390fd5b609754604051630a3dc4f360e21b815267ffffffffffffffff86166004820152602481018590526001600160a01b03909116906328f713cc90604401602060405180830381865afa158015610bbb573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610bdf91906111e8565b905081158015610bf657506001600160a01b038116155b1561052e57604051632b0d65db60e01b815267ffffffffffffffff85166004820152602481018490526044016103c6565b60405162580a9560e71b815260040160405180910390fd5b6033546001600160a01b031633146105b25760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103c6565b606580546001600160a01b031916905561051c81610d77565b6001600160a01b0381163b610d1f5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016103c6565b5f8051602061127383398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b610d5683610dc8565b5f82511180610d625750805b156109fd57610d718383610e07565b50505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b610dd181610cb2565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606061052e83836040518060600160405280602781526020016112936027913960605f80856001600160a01b031685604051610e439190611225565b5f60405180830381855af49150503d805f8114610e7b576040519150601f19603f3d011682016040523d82523d5f602084013e610e80565b606091505b5091509150610e9186838387610e9b565b9695505050505050565b60608315610f095782515f03610f02576001600160a01b0385163b610f025760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016103c6565b5081610f13565b610f138383610f1b565b949350505050565b815115610f2b5781518083602001fd5b8060405162461bcd60e51b81526004016103c69190611240565b6001600160a01b038116811461051c575f80fd5b5f60208284031215610f69575f80fd5b813561052e81610f45565b803567ffffffffffffffff81168114610f8b575f80fd5b919050565b5f8060408385031215610fa1575f80fd5b610faa83610f74565b946020939093013593505050565b80358015158114610f8b575f80fd5b5f805f60608486031215610fd9575f80fd5b610fe284610f74565b925060208401359150610ff760408501610fb8565b90509250925092565b634e487b7160e01b5f52604160045260245ffd5b5f8060408385031215611025575f80fd5b823561103081610f45565b9150602083013567ffffffffffffffff8082111561104c575f80fd5b818501915085601f83011261105f575f80fd5b81358181111561107157611071611000565b604051601f8201601f19908116603f0116810190838211818310171561109957611099611000565b816040528281528860208487010111156110b1575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f80604083850312156110e3575f80fd5b823591506110f360208401610fb8565b90509250929050565b5f805f6060848603121561110e575f80fd5b61111784610f74565b925060208401359150604084013561112e81610f45565b809150509250925092565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b5f602082840312156111e1575f80fd5b5051919050565b5f602082840312156111f8575f80fd5b815161052e81610f45565b5f5b8381101561121d578181015183820152602001611205565b50505f910152565b5f8251611236818460208701611203565b9190910192915050565b602081525f825180602084015261125e816040850160208701611203565b601f01601f1916919091016040019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212203cf7c7909aa3a171d69bba3422ad3ee05166e507361c8bce5bed32f66ed9173764736f6c63430008180033", - "balance": "0x0" - }, - "0x1670090000000000000000000000000000000006": { - "contractName": "SharedAddressManager", - "storage": { - "0x00000000000000000000000000000000000000000000000000000000000000c9": "0x0000000000000000000000000000000000000000000000000000000000000101", - "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190", - "0x06e004775639cdb0e38f2c8a0f80bf9e3f0bafc0587c4deccd476e1b0b083676": "0x0000000000000000000000001670090000000000000000000000000000000001", - "0xb8c522e28ba83112fa09f2f92b3b5b52a7c1f13cbb8f63d8646f6622fef3f813": "0x0000000000000000000000001670090000000000000000000000000000000002", - "0x82ae8f3253e7287d155c8294e95cd3dd52ed217ba1d329ff95e6d8ffa381f4e0": "0x0000000000000000000000001670090000000000000000000000000000000003", - "0xe4ea7533cbb05f4e190c159cb9fb81e8521df30826303aeb47c41cb35d1bba33": "0x0000000000000000000000001670090000000000000000000000000000000004", - "0x57e7be70ddd7eb8855d6252773a58a2cd3685df17defaa305b7a91e54f33be8c": "0x0000000000000000000000001670090000000000000000000000000000000005", - "0xf03fbf03cd2fb98817c4c19098b6c47f4474a83b719463ed4e6a6e8f3fddabad": "0x0000000000000000000000000167009000000000000000000000000000010096", - "0x5b1fda64ce34e2802383bf43f53729861587224c0d0f408a5711efaa5b3332e7": "0x0000000000000000000000000167009000000000000000000000000000010097", - "0x8b468f5c678e4e20924a892aeff9bd555f11fdf7569ebdbbf1f51f10ac196137": "0x0000000000000000000000000167009000000000000000000000000000010098", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0167009000000000000000000000000000000006" - }, - "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156070573d5ff35b3d5ffdfea2646970667358221220ee2b34631d0aeafcbca8c09b3d006837becb1e761da7ef1dfde69d83e98a206164736f6c63430008180033", - "balance": "0x0" - }, - "0x0167009000000000000000000000000000000001": { - "contractName": "BridgeImpl", - "storage": { - "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190" - }, - "code": "0x6080604052600436106101e9575f3560e01c8063715018a611610108578063b916a0be1161009d578063d8beb5c31161006d578063d8beb5c314610611578063e30c397814610640578063eefbf17e1461065d578063f09a401614610694578063f2fde38b146106b3575f80fd5b8063b916a0be1461056b578063d0496d6a1461058a578063d1aaa5df146105d2578063d6ba38b2146105f2575f80fd5b80638da5cb5b116100d85780638da5cb5b146104d25780638e3881a9146104ef5780639939a2dc1461052d578063a86f9d9e1461054c575f80fd5b8063715018a61461046d5780637844845b1461048157806379ba5097146104aa5780638456cb59146104be575f80fd5b806348548f251161017e5780635c975abb1161014e5780635c975abb1461039f5780636be4eb55146103bf5780636c334e2e146103de5780636edbad04146103ff575f80fd5b806348548f251461033a5780634f1ef2861461035957806352d1902d1461036c57806357209f4814610380575f80fd5b80633ab76e9f116101b95780633ab76e9f146102955780633c6cf473146102cc5780633eb6b8cf146103075780633f4ba83a14610326575f80fd5b806316b205c1146101f4578063302ac39914610215578063324c058e146102475780633659cfe614610276575f80fd5b366101f057005b5f80fd5b3480156101ff575f80fd5b5061021361020e366004612d7d565b6106d2565b005b348015610220575f80fd5b5061023461022f366004613039565b610c3a565b6040519081526020015b60405180910390f35b348015610252575f80fd5b50610266610261366004612d7d565b610c69565b604051901515815260200161023e565b348015610281575f80fd5b5061021361029036600461306a565b610cde565b3480156102a0575f80fd5b506097546102b4906001600160a01b031681565b6040516001600160a01b03909116815260200161023e565b3480156102d7575f80fd5b506102fa6102e6366004613085565b60fc6020525f908152604090205460ff1681565b60405161023e91906130b0565b348015610312575f80fd5b506102b46103213660046130e3565b610dc4565b348015610331575f80fd5b50610213610dd0565b348015610345575f80fd5b50610213610354366004613120565b610e4f565b610213610367366004613193565b6110a7565b348015610377575f80fd5b50610234611176565b34801561038b575f80fd5b5061021361039a3660046131df565b611228565b3480156103aa575f80fd5b5061026660c954610100900460ff1660021490565b3480156103ca575f80fd5b506102666103d9366004612d7d565b611347565b6103f16103ec366004613216565b6113a8565b60405161023e9291906133b8565b34801561040a575f80fd5b50610446610419366004613085565b6101006020525f90815260409020546001600160401b03811690600160401b90046001600160a01b031682565b604080516001600160401b0390931683526001600160a01b0390911660208301520161023e565b348015610478575f80fd5b506102136116b3565b34801561048c575f80fd5b506104956116c4565b6040805192835260208301919091520161023e565b3480156104b5575f80fd5b50610213611702565b3480156104c9575f80fd5b50610213611779565b3480156104dd575f80fd5b506033546001600160a01b03166102b4565b3480156104fa575f80fd5b5061050e6105093660046133d0565b6117f8565b6040805192151583526001600160a01b0390911660208301520161023e565b348015610538575f80fd5b50610266610547366004613216565b611824565b348015610557575f80fd5b506102b46105663660046133e9565b6118f2565b348015610576575f80fd5b5061021361058536600461340c565b6118fe565b348015610595575f80fd5b5061059e611aaf565b60408051825181526020808401516001600160a01b031690820152918101516001600160401b03169082015260600161023e565b3480156105dd575f80fd5b506102346105ec366004613085565b60031890565b3480156105fd575f80fd5b5061021361060c366004612d7d565b611b56565b34801561061c575f80fd5b5061026661062b36600461306a565b60ff60208190525f9182526040909120541681565b34801561064b575f80fd5b506065546001600160a01b03166102b4565b348015610668575f80fd5b5060fb5461067c906001600160801b031681565b6040516001600160801b03909116815260200161023e565b34801561069f575f80fd5b506102136106ae36600461344f565b611f7a565b3480156106be575f80fd5b506102136106cd36600461306a565b612088565b60026106e060c95460ff1690565b60ff16036107015760405163dfc60d8560e01b815260040160405180910390fd5b61070b60026120f9565b61071f60c954610100900460ff1660021490565b1561073d5760405163bae6e2a960e01b815260040160405180910390fd5b61074d60808401606085016133d0565b46816001600160401b03161461077657604051631c6c777560e31b815260040160405180910390fd5b5f61078361022f8661347b565b90505f8082815260fc602052604090205460ff1660048111156107a8576107a861309c565b146107c657604051630cfafbf960e01b815260040160405180910390fd5b5f6107e26d7369676e616c5f7365727669636560901b5f6118f2565b5f83815261010060205260409020549091506001600160401b031667fffffffffffffffe198101610826576040516329e5274f60e21b815260040160405180910390fd5b5f806108306116c4565b915091505f836001600160401b03165f0361091657610861858761085a60608e0160408f016133d0565b8c8c61210f565b61087e57604051635ea5ecc760e01b815260040160405180910390fd5b5042925060018215610916576040518060400160405280856001600160401b031681526020018b61014001355f146108b657336108c6565b6108c660c08d0160a08e0161306a565b6001600160a01b039081169091525f88815261010060209081526040909120835181549490920151909216600160401b026001600160e01b03199093166001600160401b03909116179190911790555b821580159061094357505f8681526101006020526040902054600160401b90046001600160a01b03163314155b1561094d57918101915b6109606001600160401b0385168461349a565b4210610bc6576101408a013515801561099a575061098460c08b0160a08c0161306a565b6001600160a01b0316336001600160a01b031614155b156109b8576040516372b6e1c360e11b815260040160405180910390fd5b5f8681526101006020526040812080546001600160e01b0319169055806109e560e08d0160c08e0161306a565b6001600160a01b03161480610a11575030610a0660e08d0160c08e0161306a565b6001600160a01b0316145b80610a3c57506001600160a01b038616610a3160e08d0160c08e0161306a565b6001600160a01b0316145b80610a72575060ff5f610a5560e08e0160c08f0161306a565b6001600160a01b0316815260208101919091526040015f205460ff165b15610a8e57506101008a0135610a89876002612197565b610af3565b5f610a9f60c08d0160a08e0161306a565b6001600160a01b0316336001600160a01b031614610ac2578b6101400135610ac4565b5a5b9050610ad18c89836122db565b15610ae657610ae1886002612197565b610af1565b610af1886001612197565b505b5f80610b066101008e0160e08f0161306a565b6001600160a01b031614610b2a57610b256101008d0160e08e0161306a565b610b3a565b610b3a60c08d0160a08e0161306a565b90506001600160a01b0381163303610b7357610b6e610b5e836101208f013561349a565b6001600160a01b0383169061243b565b610b95565b610b82336101208e013561243b565b610b956001600160a01b0382168361243b565b60405188907fe7d1e1f435233f7a187624ac11afaf32ee0da368cef8a5625be394412f619254905f90a25050610c24565b8015610c0b57857f3a7420670ebb84feae884388421d5f63bb1f9e073c54c8103e9e2ca7a98346e58b5f604051610bfe929190613664565b60405180910390a2610c24565b60405163714f083160e11b815260040160405180910390fd5b50505050505050610c3560016120f9565b505050565b5f81604051602001610c4c9190613687565b604051602081830303815290604052805190602001209050919050565b5f46610c7b60608601604087016133d0565b6001600160401b031614610c9057505f610cd7565b610cd4610cae6d7369676e616c5f7365727669636560901b5f6118f2565b610cbd6105ec61022f8861347b565b610ccd60808801606089016133d0565b868661210f565b90505b9392505050565b6001600160a01b037f0000000000000000000000000167009000000000000000000000000000000001163003610d2f5760405162461bcd60e51b8152600401610d26906136bd565b60405180910390fd5b7f00000000000000000000000001670090000000000000000000000000000000016001600160a01b0316610d775f80516020613943833981519152546001600160a01b031690565b6001600160a01b031614610d9d5760405162461bcd60e51b8152600401610d2690613709565b610da681612446565b604080515f80825260208201909252610dc19183919061244e565b50565b5f610cd48484846125b8565b610de460c954610100900460ff1660021490565b610e015760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166101001790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610e4d335f6126a4565b565b6e6272696467655f7761746368646f6760881b610e746033546001600160a01b031690565b6001600160a01b0316336001600160a01b031614158015610eb15750610e9b8160016118f2565b6001600160a01b0316336001600160a01b031614155b15610ecf57604051630d85cccf60e11b815260040160405180910390fd5b5f5b838110156110a0575f858583818110610eec57610eec613755565b9050602002013590508315610ff1575f81815261010060205260408120546001600160401b03169003610f325760405163880b250b60e01b815260040160405180910390fd5b5f81815261010060205260409020546001600160401b031667fffffffffffffffe1901610f72576040516329e5274f60e21b815260040160405180910390fd5b5f8181526101006020526040808220805467ffffffffffffffff19166001600160401b03179055517fa3bf322f86f6b7b2fcb75744c1a9e22891ae257bbeb6b2a265627371e2651bcb91610fe491849160019192835290151560208301526001600160401b0316604082015260600190565b60405180910390a1611097565b5f81815261010060205260409020546001600160401b03908116146110295760405163640f938b60e11b815260040160405180910390fd5b5f81815261010060209081526040808320805467ffffffffffffffff1916426001600160401b03169081179091558151858152928301939093528101919091527fa3bf322f86f6b7b2fcb75744c1a9e22891ae257bbeb6b2a265627371e2651bcb9060600160405180910390a15b50600101610ed1565b5050505050565b6001600160a01b037f00000000000000000000000001670090000000000000000000000000000000011630036110ef5760405162461bcd60e51b8152600401610d26906136bd565b7f00000000000000000000000001670090000000000000000000000000000000016001600160a01b03166111375f80516020613943833981519152546001600160a01b031690565b6001600160a01b03161461115d5760405162461bcd60e51b8152600401610d2690613709565b61116682612446565b6111728282600161244e565b5050565b5f306001600160a01b037f000000000000000000000000016700900000000000000000000000000000000116146112155760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610d26565b505f805160206139438339815191525b90565b6e6272696467655f7761746368646f6760881b61124d6033546001600160a01b031690565b6001600160a01b0316336001600160a01b03161415801561128a57506112748160016118f2565b6001600160a01b0316336001600160a01b031614155b156112a857604051630d85cccf60e11b815260040160405180910390fd5b6001600160a01b0383165f90815260ff602081905260409091205483151591161515036112e8576040516319d893ad60e21b815260040160405180910390fd5b6001600160a01b0383165f81815260ff6020908152604091829020805460ff191686151590811790915591519182527f7113ce15c395851033544a97557341cdc71886964b54ff108a685d359ed4cdf8910160405180910390a2505050565b5f4661135960808601606087016133d0565b6001600160401b03161461136e57505f610cd7565b610cd461138c6d7369676e616c5f7365727669636560901b5f6118f2565b61139861022f8761347b565b610ccd60608801604089016133d0565b604080516101a0810182525f8082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e082018390526101008201839052610120820183905261014082018390526101608201819052610180820152600261141c60c95460ff1690565b60ff160361143d5760405163dfc60d8560e01b815260040160405180910390fd5b61144760026120f9565b61145b60c954610100900460ff1660021490565b156114795760405163bae6e2a960e01b815260040160405180910390fd5b5f61148a60a085016080860161306a565b6001600160a01b031614806114b657505f6114ab60c0850160a0860161306a565b6001600160a01b0316145b156114d457604051633c4f94dd60e11b815260040160405180910390fd5b5f6114e861050960808601606087016133d0565b5090508061150957604051631c6c777560e31b815260040160405180910390fd5b4661151a60808601606087016133d0565b6001600160401b03160361154157604051631c6c777560e31b815260040160405180910390fd5b5f61155661012086013561010087013561349a565b905034811461157857604051634ac2abdf60e11b815260040160405180910390fd5b6115818561347b565b60fb80549194506001600160801b03909116905f61159e83613769565b82546101009290920a6001600160801b03818102199093169183160217909155168352336020840152466001600160401b031660408401526115df83610c3a565b93506115fc6d7369676e616c5f7365727669636560901b5f6118f2565b6001600160a01b03166366ca2bc0856040518263ffffffff1660e01b815260040161162991815260200190565b6020604051808303815f875af1158015611645573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611669919061378e565b50837f9a4c6dce9e49d66f9d79b5f213b08c30c2bcef51424e23934a80f4865e1f70398460405161169a91906137a5565b60405180910390a250506116ae60016120f9565b915091565b6116bb61274f565b610e4d5f6127a9565b5f806116cf466127c2565b156116e05750610e10916101809150565b6116e9466127d6565b156116fa575061012c916101809150565b505f91829150565b60655433906001600160a01b031681146117705760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b6064820152608401610d26565b610dc1816127a9565b61178d60c954610100900460ff1660021490565b156117ab5760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a1610e4d3360016126a4565b5f8061180f836562726964676560d01b6001610dc4565b6001600160a01b038116151594909350915050565b5f4661183660608401604085016133d0565b6001600160401b03161461184b57505f919050565b6118666d7369676e616c5f7365727669636560901b5f6118f2565b6001600160a01b03166332676bc63061188161022f8661347b565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401602060405180830381865afa1580156118c8573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906118ec91906137b7565b92915050565b5f610cd74684846125b8565b600261190c60c95460ff1690565b60ff160361192d5760405163dfc60d8560e01b815260040160405180910390fd5b61193760026120f9565b61194b60c954610100900460ff1660021490565b156119695760405163bae6e2a960e01b815260040160405180910390fd5b61197960808301606084016133d0565b46816001600160401b0316146119a257604051631c6c777560e31b815260040160405180910390fd5b61014083013515806119b15750815b156119f7576119c660c0840160a0850161306a565b6001600160a01b0316336001600160a01b0316146119f7576040516372b6e1c360e11b815260040160405180910390fd5b5f611a0461022f8561347b565b905060015f82815260fc602052604090205460ff166004811115611a2a57611a2a61309c565b14611a4857604051636e10a9f360e01b815260040160405180910390fd5b611a5384825a6122db565b15611a6857611a63816002612197565b611a79565b8215611a7957611a79816003612197565b60405181907f72d1525c4df70aedf1877ec89702311c795a01c082917308a30fb40059da2cc7905f90a2505061117260016120f9565b604080516060810182525f8082526020820181905291810191909152611b26604080516060810182525f8082526020820181905291810191909152506040805160608101825260fd54815260fe546001600160a01b0381166020830152600160a01b90046001600160401b03169181019190915290565b80519091501580611b38575080515f19145b1561122557604051635ceed17360e01b815260040160405180910390fd5b6002611b6460c95460ff1690565b60ff1603611b855760405163dfc60d8560e01b815260040160405180910390fd5b611b8f60026120f9565b611ba360c954610100900460ff1660021490565b15611bc15760405163bae6e2a960e01b815260040160405180910390fd5b611bd160608401604085016133d0565b46816001600160401b031614611bfa57604051631c6c777560e31b815260040160405180910390fd5b5f611c0761022f8661347b565b90505f8082815260fc602052604090205460ff166004811115611c2c57611c2c61309c565b14611c4a57604051630cfafbf960e01b815260040160405180910390fd5b5f81815261010060205260409020546001600160401b031667fffffffffffffffe198101611c8b576040516329e5274f60e21b815260040160405180910390fd5b5f611c946116c4565b5090505f826001600160401b03165f03611dc0575f611cc46d7369676e616c5f7365727669636560901b5f6118f2565b604051631933b5e360e11b8152306004820152602481018790529091506001600160a01b038216906332676bc690604401602060405180830381865afa158015611d10573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d3491906137b7565b611d515760405163ab035ad560e01b815260040160405180910390fd5b60038518611d6a828261085a60808e0160608f016133d0565b611d875760405163f149234f60e01b815260040160405180910390fd5b429450600192508315611dbd575f86815261010060205260409020805467ffffffffffffffff19166001600160401b0387161790555b50505b611dd36001600160401b0384168361349a565b4210611f29575f8481526101006020908152604080832080546001600160e01b031916905560fc825291829020805460ff19166004179055611e3591631e37aef160e11b91611e26918c01908c0161306a565b6001600160a01b0316906127ed565b15611ed257611e548430611e4f60608c0160408d016133d0565b61287a565b611e646040890160208a0161306a565b6001600160a01b0316633c6f5de28961010001358a876040518463ffffffff1660e01b8152600401611e979291906137d2565b5f604051808303818588803b158015611eae575f80fd5b505af1158015611ec0573d5f803e3d5ffd5b5050505050611ecd6128cf565b611efa565b611efa610100890135611eeb60a08b0160808c0161306a565b6001600160a01b03169061243b565b60405184907fc6fbc1fa0145a394c9c414b2ae7bd634eb50dd888938bcd75692ae427b680fa2905f90a2611f6b565b8015610c0b57837f3a7420670ebb84feae884388421d5f63bb1f9e073c54c8103e9e2ca7a98346e5896001604051611f62929190613664565b60405180910390a25b5050505050610c3560016120f9565b5f54610100900460ff1615808015611f9857505f54600160ff909116105b80611fb15750303b158015611fb157505f5460ff166001145b6120145760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610d26565b5f805460ff191660011790558015612035575f805461ff0019166101001790555b61203f83836128f4565b8015610c35575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a1505050565b61209061274f565b606580546001600160a01b0383166001600160a01b031990911681179091556120c16033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60c9805460ff191660ff92909216919091179055565b5f856001600160a01b031663910af6ed85612134876562726964676560d01b5f610dc4565b8887876040518663ffffffff1660e01b81526004016121579594939291906137f3565b5f604051808303815f87803b15801561216e575f80fd5b505af192505050801561217f575060015b61218a57505f61218e565b5060015b95945050505050565b8060048111156121a9576121a961309c565b5f83815260fc602052604090205460ff1660048111156121cb576121cb61309c565b036121d4575050565b5f82815260fc60205260409020805482919060ff191660018360048111156121fe576121fe61309c565b0217905550817f6c51882bc2ed67617f77a1e9b9a25d2caad8448647ecb093b357a603b25756348260405161223391906130b0565b60405180910390a2600381600481111561224f5761224f61309c565b036111725761226f6d7369676e616c5f7365727669636560901b5f6118f2565b60405163019b28af60e61b81526003841860048201526001600160a01b0391909116906366ca2bc0906024016020604051808303815f875af11580156122b7573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c35919061378e565b5f815f036122fc576040516308c2ad5360e01b815260040160405180910390fd5b3061230d604086016020870161306a565b6001600160a01b03160361232357612323613829565b61234783612337604087016020880161306a565b611e4f60608801604089016133d0565b600461235761016086018661383d565b90501015801561238e5750637f07c94760e01b61237861016086018661383d565b6123819161387f565b6001600160e01b03191614155b80156123b757506123b76123a860e0860160c0870161306a565b6001600160a01b03163b151590565b156123c357505f612433565b612430610100850135836123db61016088018861383d565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061241f9250505060e0890160c08a0161306a565b6001600160a01b0316929190612953565b90505b610cd76128cf565b61117282825a612990565b610dc161274f565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff161561248157610c35836129d3565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156124db575060408051601f3d908101601f191682019092526124d89181019061378e565b60015b61253e5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610d26565b5f8051602061394383398151915281146125ac5760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610d26565b50610c35838383612a6e565b6097545f906001600160a01b03166125e357604051638ed88b2560e01b815260040160405180910390fd5b609754604051630a3dc4f360e21b81526001600160401b0386166004820152602481018590526001600160a01b03909116906328f713cc90604401602060405180830381865afa158015612639573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061265d91906138af565b90508115801561267457506001600160a01b038116155b15610cd757604051632b0d65db60e01b81526001600160401b038516600482015260248101849052604401610d26565b6033546001600160a01b03838116911614806126ea57506126d56b31b430b4b72fb830bab9b2b960a11b60016118f2565b6001600160a01b0316826001600160a01b0316145b156126f3575050565b80801561272d57506127186e6272696467655f7761746368646f6760881b60016118f2565b6001600160a01b0316826001600160a01b0316145b15612736575050565b604051630d85cccf60e11b815260040160405180910390fd5b6033546001600160a01b03163314610e4d5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610d26565b606580546001600160a01b0319169055610dc181612a98565b5f60018214806118ec57506118ec82612ae9565b5f617e2c82101580156118ec575050617e90101590565b5f6001600160a01b0383163b61280457505f6118ec565b6040516301ffc9a760e01b81526001600160e01b0319831660048201526001600160a01b038416906301ffc9a790602401602060405180830381865afa92505050801561286e575060408051601f3d908101601f1916820190925261286b918101906137b7565b60015b156118ec579392505050565b604080516060810182528481526001600160a01b03909316602084018190526001600160401b03909216920182905260fd9290925560fe8054600160a01b9092026001600160e01b0319909216909217179055565b6128d846612b2a565b156128e857610e4d5f808061287a565b610e4d5f19808061287a565b5f54610100900460ff1661291a5760405162461bcd60e51b8152600401610d26906138ca565b61292382612b49565b6001600160a01b03811661294a576040516375cabfef60e11b815260040160405180910390fd5b61117281612b79565b5f6001600160a01b03851661297b57604051634c67134d60e11b815260040160405180910390fd5b5f80835160208501878988f195945050505050565b815f0361299c57505050565b6129b683838360405180602001604052805f815250612953565b610c3557604051634c67134d60e11b815260040160405180910390fd5b6001600160a01b0381163b612a405760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610d26565b5f8051602061394383398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b612a7783612be9565b5f82511180612a835750805b15610c3557612a928383612c28565b50505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f6002821480612af95750600482145b80612b045750600582145b80612b0f5750602a82145b80612b1b575061426882145b806118ec57505062aa36a71490565b5f6001821480612b1b57506142688214806118ec57505062aa36a71490565b612b676001600160a01b03821615612b6157816127a9565b336127a9565b5060c9805461ff001916610100179055565b5f54610100900460ff16612b9f5760405162461bcd60e51b8152600401610d26906138ca565b6001600160401b03461115612bc75760405163a12e8fa960e01b815260040160405180910390fd5b609780546001600160a01b0319166001600160a01b0392909216919091179055565b612bf2816129d3565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060610cd783836040518060600160405280602781526020016139636027913960605f80856001600160a01b031685604051612c649190613915565b5f60405180830381855af49150503d805f8114612c9c576040519150601f19603f3d011682016040523d82523d5f602084013e612ca1565b606091505b5091509150612cb286838387612cbc565b9695505050505050565b60608315612d2a5782515f03612d23576001600160a01b0385163b612d235760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610d26565b5081612d34565b612d348383612d3c565b949350505050565b815115612d4c5781518083602001fd5b8060405162461bcd60e51b8152600401610d269190613930565b5f6101a08284031215612d77575f80fd5b50919050565b5f805f60408486031215612d8f575f80fd5b83356001600160401b0380821115612da5575f80fd5b612db187838801612d66565b94506020860135915080821115612dc6575f80fd5b818601915086601f830112612dd9575f80fd5b813581811115612de7575f80fd5b876020828501011115612df8575f80fd5b6020830194508093505050509250925092565b634e487b7160e01b5f52604160045260245ffd5b6040516101a081016001600160401b0381118282101715612e4257612e42612e0b565b60405290565b80356001600160801b0381168114612e5e575f80fd5b919050565b6001600160a01b0381168114610dc1575f80fd5b8035612e5e81612e63565b80356001600160401b0381168114612e5e575f80fd5b5f82601f830112612ea7575f80fd5b81356001600160401b0380821115612ec157612ec1612e0b565b604051601f8301601f19908116603f01168101908282118183101715612ee957612ee9612e0b565b81604052838152866020858801011115612f01575f80fd5b836020870160208301375f602085830101528094505050505092915050565b5f6101a08284031215612f31575f80fd5b612f39612e1f565b9050612f4482612e48565b8152612f5260208301612e77565b6020820152612f6360408301612e82565b6040820152612f7460608301612e82565b6060820152612f8560808301612e77565b6080820152612f9660a08301612e77565b60a0820152612fa760c08301612e77565b60c0820152612fb860e08301612e77565b60e0820152610100828101359082015261012080830135908201526101408083013590820152610160808301356001600160401b0380821115612ff9575f80fd5b61300586838701612e98565b83850152610180925082850135915080821115613020575f80fd5b5061302d85828601612e98565b82840152505092915050565b5f60208284031215613049575f80fd5b81356001600160401b0381111561305e575f80fd5b612d3484828501612f20565b5f6020828403121561307a575f80fd5b8135610cd781612e63565b5f60208284031215613095575f80fd5b5035919050565b634e487b7160e01b5f52602160045260245ffd5b60208101600583106130d057634e487b7160e01b5f52602160045260245ffd5b91905290565b8015158114610dc1575f80fd5b5f805f606084860312156130f5575f80fd5b6130fe84612e82565b9250602084013591506040840135613115816130d6565b809150509250925092565b5f805f60408486031215613132575f80fd5b83356001600160401b0380821115613148575f80fd5b818601915086601f83011261315b575f80fd5b813581811115613169575f80fd5b8760208260051b850101111561317d575f80fd5b60209283019550935050840135613115816130d6565b5f80604083850312156131a4575f80fd5b82356131af81612e63565b915060208301356001600160401b038111156131c9575f80fd5b6131d585828601612e98565b9150509250929050565b5f80604083850312156131f0575f80fd5b82356131fb81612e63565b9150602083013561320b816130d6565b809150509250929050565b5f60208284031215613226575f80fd5b81356001600160401b0381111561323b575f80fd5b612d3484828501612d66565b5f5b83811015613261578181015183820152602001613249565b50505f910152565b5f8151808452613280816020860160208601613247565b601f01601f19169290920160200192915050565b80516001600160801b031682525f6101a060208301516132bf60208601826001600160a01b03169052565b5060408301516132da60408601826001600160401b03169052565b5060608301516132f560608601826001600160401b03169052565b50608083015161331060808601826001600160a01b03169052565b5060a083015161332b60a08601826001600160a01b03169052565b5060c083015161334660c08601826001600160a01b03169052565b5060e083015161336160e08601826001600160a01b03169052565b506101008381015190850152610120808401519085015261014080840151908501526101608084015181860183905261339c83870182613269565b925050506101808084015185830382870152612cb28382613269565b828152604060208201525f610cd46040830184613294565b5f602082840312156133e0575f80fd5b610cd782612e82565b5f80604083850312156133fa575f80fd5b82359150602083013561320b816130d6565b5f806040838503121561341d575f80fd5b82356001600160401b03811115613432575f80fd5b61343e85828601612d66565b925050602083013561320b816130d6565b5f8060408385031215613460575f80fd5b823561346b81612e63565b9150602083013561320b81612e63565b5f6118ec3683612f20565b634e487b7160e01b5f52601160045260245ffd5b808201808211156118ec576118ec613486565b5f808335601e198436030181126134c2575f80fd5b83016020810192503590506001600160401b038111156134e0575f80fd5b8036038213156134ee575f80fd5b9250929050565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b5f6101a061353b8461352e85612e48565b6001600160801b03169052565b61354760208401612e77565b6001600160a01b0316602085015261356160408401612e82565b6001600160401b0316604085015261357b60608401612e82565b6001600160401b0316606085015261359560808401612e77565b6001600160a01b031660808501526135af60a08401612e77565b6001600160a01b031660a08501526135c960c08401612e77565b6001600160a01b031660c08501526135e360e08401612e77565b6001600160a01b031660e0850152610100838101359085015261012080840135908501526101408084013590850152610160613621818501856134ad565b838388015261363384880182846134f5565b9350505050610180613647818501856134ad565b868403838801526136598482846134f5565b979650505050505050565b604081525f613676604083018561351d565b905082151560208301529392505050565b60408152600d60408201526c5441494b4f5f4d45535341474560981b6060820152608060208201525f610cd76080830184613294565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b634e487b7160e01b5f52603260045260245ffd5b5f6001600160801b0380831681810361378457613784613486565b6001019392505050565b5f6020828403121561379e575f80fd5b5051919050565b602081525f610cd76020830184613294565b5f602082840312156137c7575f80fd5b8151610cd7816130d6565b604081525f6137e4604083018561351d565b90508260208301529392505050565b6001600160401b038616815260018060a01b0385166020820152836040820152608060608201525f6136596080830184866134f5565b634e487b7160e01b5f52600160045260245ffd5b5f808335601e19843603018112613852575f80fd5b8301803591506001600160401b0382111561386b575f80fd5b6020019150368190038213156134ee575f80fd5b6001600160e01b031981358181169160048510156138a75780818660040360031b1b83161692505b505092915050565b5f602082840312156138bf575f80fd5b8151610cd781612e63565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f8251613926818460208701613247565b9190910192915050565b602081525f610cd7602083018461326956fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220fd8171596afd791a7977d46b3ef76e3b99d868dfc289ca5278123d358ae79dfd64736f6c63430008180033", - "balance": "0x0" - }, - "0x1670090000000000000000000000000000000001": { - "contractName": "Bridge", - "storage": { - "0x00000000000000000000000000000000000000000000000000000000000000c9": "0x0000000000000000000000000000000000000000000000000000000000000101", - "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190", - "0x0000000000000000000000000000000000000000000000000000000000000097": "0x0000000000000000000000001670090000000000000000000000000000000006", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0167009000000000000000000000000000000001" - }, - "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156070573d5ff35b3d5ffdfea2646970667358221220ee2b34631d0aeafcbca8c09b3d006837becb1e761da7ef1dfde69d83e98a206164736f6c63430008180033", - "balance": "0x033b2dcd9a1ae301c8000000" - }, - "0x0167009000000000000000000000000000000002": { - "contractName": "ERC20VaultImpl", - "storage": { - "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190" - }, - "code": "0x60806040526004361062000163575f3560e01c8063715018a611620000c2578063a86f9d9e1162000076578063a86f9d9e14620003f8578063caec3e4e146200041c578063e30c3978146200044f578063f09a4016146200046e578063f2fde38b1462000492578063fa233d0c14620004b6575f80fd5b8063715018a6146200034657806379ba5097146200035d5780637f07c94714620003745780638456cb59146200038b5780638da5cb5b14620003a25780639aa8605c14620003c1575f80fd5b80633eb6b8cf116200011a5780633eb6b8cf14620002775780633f4ba83a146200029b5780634f1ef28614620002b257806352d1902d14620002c95780635c975abb14620002e057806367090ccf1462000302575f80fd5b806301ffc9a7146200016757806306fdde0314620001b05780630ecd8be914620001dc5780633659cfe614620002195780633ab76e9f146200023f5780633c6f5de21462000260575b5f80fd5b34801562000173575f80fd5b506200019b6200018536600462002f2f565b6001600160e01b031916631e37aef160e11b1490565b60405190151581526020015b60405180910390f35b348015620001bc575f80fd5b506a195c98cc8c17dd985d5b1d60aa1b5b604051908152602001620001a7565b348015620001e8575f80fd5b5062000200620001fa36600462002f6d565b620004dc565b6040516001600160a01b039091168152602001620001a7565b34801562000225575f80fd5b506200023d6200023736600462002fc4565b62000bb6565b005b3480156200024b575f80fd5b5060975462000200906001600160a01b031681565b6200023d6200027136600462002fe2565b62000ca8565b34801562000283575f80fd5b5062000200620002953660046200304e565b62000e53565b348015620002a7575f80fd5b506200023d62000e6b565b6200023d620002c3366004620031ab565b62000eee565b348015620002d5575f80fd5b50620001cd62000fc5565b348015620002ec575f80fd5b506200019b60c954610100900460ff1660021490565b3480156200030e575f80fd5b506200020062000320366004620031fd565b61012e60209081525f92835260408084209091529082529020546001600160a01b031681565b34801562000352575f80fd5b506200023d6200107a565b34801562000369575f80fd5b506200023d6200108f565b6200023d6200038536600462003223565b6200110a565b34801562000397575f80fd5b506200023d620012a0565b348015620003ae575f80fd5b506033546001600160a01b031662000200565b348015620003cd575f80fd5b50620003e5620003df36600462002fc4565b62001323565b604051620001a7959493929190620032e4565b34801562000404575f80fd5b50620002006200041636600462003341565b62001488565b34801562000428575f80fd5b506200019b6200043a36600462002fc4565b61012f6020525f908152604090205460ff1681565b3480156200045b575f80fd5b506065546001600160a01b031662000200565b3480156200047a575f80fd5b506200023d6200048c36600462003367565b62001496565b3480156200049e575f80fd5b506200023d620004b036600462002fc4565b620015ae565b620004cd620004c736600462003386565b62001622565b604051620001a79190620033c0565b5f6002620004ec60c95460ff1690565b60ff16036200050e5760405163dfc60d8560e01b815260040160405180910390fd5b6200051a600262001aaf565b6200052f60c954610100900460ff1660021490565b156200054e5760405163bae6e2a960e01b815260040160405180910390fd5b6200055862001ac5565b6001600160a01b03821615806200058f57506001600160a01b038281165f90815261012d6020526040902054600160401b90041615155b15620005ae5760405163dc63f98760e01b815260040160405180910390fd5b6001600160a01b0382165f90815261012f602052604090205460ff1615620005e9576040516375c42fc160e01b815260040160405180910390fd5b6033546001600160a01b03166001600160a01b0316826001600160a01b0316638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200063b573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190620006619190620034f3565b6001600160a01b031614620006895760405163c0507c1760e01b815260040160405180910390fd5b61012e5f6200069c602086018662003511565b6001600160401b031681526020019081526020015f205f846020016020810190620006c8919062002fc4565b6001600160a01b03908116825260208201929092526040015f2054169050801562000a53576001600160a01b038181165f90815261012d60209081526040808320815160a08101835281546001600160401b0381168252600160401b810490961693810193909352600160e01b90940460ff1690820152600183018054929391926060840191906200075a906200352f565b80601f016020809104026020016040519081016040528092919081815260200182805462000788906200352f565b8015620007d75780601f10620007ad57610100808354040283529160200191620007d7565b820191905f5260205f20905b815481529060010190602001808311620007b957829003601f168201915b50505050508152602001600282018054620007f2906200352f565b80601f016020809104026020016040519081016040528092919081815260200182805462000820906200352f565b80156200086f5780601f1062000845576101008083540402835291602001916200086f565b820191905f5260205f20905b8154815290600101906020018083116200085157829003601f168201915b50505050508152505090508360400160208101906200088f919062003585565b60ff16816040015160ff16141580620008d95750620008b26060850185620035a3565b604051620008c2929190620035ef565b604051809103902081606001518051906020012014155b80620009165750620008ef6080850185620035a3565b604051620008ff929190620035ef565b604051809103902081608001518051906020012014155b156200093557604051632f9d1d7b60e11b815260040160405180910390fd5b6001600160a01b0382165f90815261012d6020526040812080546001600160e81b0319168155906200096b600183018262002ecf565b6200097a600283015f62002ecf565b50506001600160a01b038281165f81815261012f6020526040808220805460ff191660011790555163b8f2e0c560e01b8152928616600484015260248301529063b8f2e0c5906044015f604051808303815f87803b158015620009db575f80fd5b505af1158015620009ee573d5f803e3d5ffd5b505060405163b8f2e0c560e01b81526001600160a01b038581166004830152600160248301528616925063b8f2e0c591506044015f604051808303815f87803b15801562000a3a575f80fd5b505af115801562000a4d573d5f803e3d5ffd5b50505050505b6001600160a01b0382165f90815261012d60205260409020839062000a79828262003713565b5082905061012e5f62000a90602087018762003511565b6001600160401b031681526020019081526020015f205f85602001602081019062000abc919062002fc4565b6001600160a01b03166001600160a01b031681526020019081526020015f205f6101000a8154816001600160a01b0302191690836001600160a01b0316021790555082602001602081019062000b13919062002fc4565b6001600160a01b031662000b2b602085018562003511565b6001600160401b03167f031d68e1805917560c34a5f55a7dd91bef98f911190ed02cdbb53caedae6c39d838562000b666060890189620035a3565b62000b7560808b018b620035a3565b62000b8760608d0160408e0162003585565b60405162000b9c979695949392919062003810565b60405180910390a362000bb0600162001aaf565b92915050565b6001600160a01b037f000000000000000000000000016700900000000000000000000000000000000216300362000c0a5760405162461bcd60e51b815260040162000c01906200386b565b60405180910390fd5b7f00000000000000000000000001670090000000000000000000000000000000026001600160a01b031662000c545f80516020620045de833981519152546001600160a01b031690565b6001600160a01b03161462000c7d5760405162461bcd60e51b815260040162000c0190620038b7565b62000c888162001b21565b604080515f8082526020820190925262000ca59183919062001b2b565b50565b600262000cb760c95460ff1690565b60ff160362000cd95760405163dfc60d8560e01b815260040160405180910390fd5b62000ce5600262001aaf565b62000cfa60c954610100900460ff1660021490565b1562000d195760405163bae6e2a960e01b815260040160405180910390fd5b62000d2362001ca2565b505f62000d35610160840184620035a3565b62000d4591600490829062003903565b81019062000d5491906200392c565b90505f808280602001905181019062000d6e9190620039c4565b9350505091505f62000d968387608001602081019062000d8f919062002fc4565b8462001d99565b905062000dc461010087013562000db460a0890160808a0162002fc4565b6001600160a01b03169062001e45565b62000dd660a087016080880162002fc4565b6001600160a01b0316857f3dea0f5955b148debf6212261e03bd80eaf8534bee43780452d16637dcc22dd58560200151848660405162000e37939291906001600160a01b039384168152919092166020820152604081019190915260600190565b60405180910390a35050505062000e4f600162001aaf565b5050565b5f62000e6184848462001e52565b90505b9392505050565b62000e8060c954610100900460ff1660021490565b62000e9e5760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166101001790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a162000eec335f62001f45565b565b6001600160a01b037f000000000000000000000000016700900000000000000000000000000000000216300362000f395760405162461bcd60e51b815260040162000c01906200386b565b7f00000000000000000000000001670090000000000000000000000000000000026001600160a01b031662000f835f80516020620045de833981519152546001600160a01b031690565b6001600160a01b03161462000fac5760405162461bcd60e51b815260040162000c0190620038b7565b62000fb78262001b21565b62000e4f8282600162001b2b565b5f306001600160a01b037f00000000000000000000000001670090000000000000000000000000000000021614620010665760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c0000000000000000606482015260840162000c01565b505f80516020620045de8339815191525b90565b6200108462001ac5565b62000eec5f62001f4f565b60655433906001600160a01b03168114620010ff5760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b606482015260840162000c01565b62000ca58162001f4f565b60026200111960c95460ff1690565b60ff16036200113b5760405163dfc60d8560e01b815260040160405180910390fd5b62001147600262001aaf565b6200115c60c954610100900460ff1660021490565b156200117b5760405163bae6e2a960e01b815260040160405180910390fd5b5f8080806200118d8587018762003ad0565b93509350935093505f620011a062001f6a565b90506001600160a01b0383161580620011c157506001600160a01b03831630145b15620011e05760405163def9481360e01b815260040160405180910390fd5b5f620011ee86858562001d99565b9050620012056001600160a01b0385163462001e45565b836001600160a01b0316856001600160a01b0316835f01517f75a051823424fc80e92556c41cb0ad977ae1dcb09c68a9c38acab86b11a69f8985604001518a6020015186896040516200128694939291906001600160401b039490941684526001600160a01b03928316602085015291166040830152606082015260800190565b60405180910390a450505050505062000e4f600162001aaf565b620012b560c954610100900460ff1660021490565b15620012d45760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a162000eec33600162001f45565b61012d6020525f9081526040902080546001820180546001600160401b03831693600160401b84046001600160a01b031693600160e01b900460ff169290916200136d906200352f565b80601f01602080910402602001604051908101604052809291908181526020018280546200139b906200352f565b8015620013ea5780601f10620013c057610100808354040283529160200191620013ea565b820191905f5260205f20905b815481529060010190602001808311620013cc57829003601f168201915b50505050509080600201805462001401906200352f565b80601f01602080910402602001604051908101604052809291908181526020018280546200142f906200352f565b80156200147e5780601f1062001454576101008083540402835291602001916200147e565b820191905f5260205f20905b8154815290600101906020018083116200146057829003601f168201915b5050505050905085565b5f62000e6446848462001e52565b5f54610100900460ff1615808015620014b557505f54600160ff909116105b80620014d05750303b158015620014d057505f5460ff166001145b620015355760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840162000c01565b5f805460ff19166001179055801562001557575f805461ff0019166101001790555b6200156383836200208f565b8015620015a9575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050565b620015b862001ac5565b606580546001600160a01b0383166001600160a01b03199091168117909155620015ea6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b604080516101a0810182525f8082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e0820183905261010082018390526101208201839052610140820192909252610160810182905261018081019190915260026200169b60c95460ff1690565b60ff1603620016bd5760405163dfc60d8560e01b815260040160405180910390fd5b620016c9600262001aaf565b620016de60c954610100900460ff1660021490565b15620016fd5760405163bae6e2a960e01b815260040160405180910390fd5b81608001355f036200172257604051634299323b60e11b815260040160405180910390fd5b5f62001735608084016060850162002fc4565b6001600160a01b0316036200175d576040516303f8a7d360e01b815260040160405180910390fd5b61012f5f62001773608085016060860162002fc4565b6001600160a01b0316815260208101919091526040015f205460ff1615620017ae576040516375c42fc160e01b815260040160405180910390fd5b5f8080620017e533620017c8608088016060890162002fc4565b620017da6060890160408a0162002fc4565b8860800135620020f6565b604080516101a0810182525f808252602080830182905292820181905294975092955090935060608201906200181e9089018962003511565b6001600160401b03168152602001336001600160a01b031681526020015f6001600160a01b03168860200160208101906200185a919062002fc4565b6001600160a01b03160362001870573362001882565b620018826040890160208a0162002fc4565b6001600160a01b03168152602090810190620018bd90620018a6908a018a62003511565b6a195c98cc8c17dd985d5b1d60aa1b5b5f62000e53565b6001600160a01b03168152602001620018de610100890160e08a0162002fc4565b6001600160a01b03168152602001620018fc60c08901353462003bcf565b815260c0880135602082015260a08801356040820152606081018690526080016200192c610100890189620035a3565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920182905250939094525092935091506200197c90506562726964676560d01b8262001488565b6001600160a01b0316636c334e2e34846040518363ffffffff1660e01b8152600401620019aa9190620033c0565b5f6040518083038185885af1158015620019c6573d5f803e3d5ffd5b50505050506040513d5f823e601f3d908101601f19168201604052620019f0919081019062003c06565b9650905062001a06606088016040890162002fc4565b60808701516001600160a01b039182169116827feb8a69f21b7a981e25f90d9f1e2ab7fa5bdbfddbc0ac160344145fc5caa6ddd262001a4960208c018c62003511565b602089015162001a6060808e0160608f0162002fc4565b604080516001600160401b0390941684526001600160a01b0392831660208501529116908201526060810188905260800160405180910390a4505050505062001aaa600162001aaf565b919050565b60c9805460ff191660ff92909216919091179055565b6033546001600160a01b0316331462000eec5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640162000c01565b62000ca562001ac5565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff161562001b6157620015a9836200262d565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa92505050801562001bbe575060408051601f3d908101601f1916820190925262001bbb9181019062003d65565b60015b62001c235760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b606482015260840162000c01565b5f80516020620045de833981519152811462001c945760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b606482015260840162000c01565b50620015a9838383620026cb565b604080516060810182525f808252602082018190529181019190915262001cd36562726964676560d01b5f62001488565b6001600160a01b0316336001600160a01b03161462001d0557604051632583296b60e01b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa15801562001d42573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001d68919062003d7d565b60208101519091506001600160a01b031633146200107757604051632583296b60e01b815260040160405180910390fd5b5f46845f01516001600160401b03160362001dd05750602083015162001dca6001600160a01b0382168484620026fb565b62000e64565b62001ddb8462002760565b6040516340c10f1960e01b81526001600160a01b03858116600483015260248201859052919250908216906340c10f19906044015f604051808303815f87803b15801562001e27575f80fd5b505af115801562001e3a573d5f803e3d5ffd5b505050509392505050565b62000e4f82825a620027a5565b6097545f906001600160a01b031662001e7e57604051638ed88b2560e01b815260040160405180910390fd5b609754604051630a3dc4f360e21b81526001600160401b0386166004820152602481018590526001600160a01b03909116906328f713cc90604401602060405180830381865afa15801562001ed5573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001efb9190620034f3565b90508115801562001f1357506001600160a01b038116155b1562000e6457604051632b0d65db60e01b81526001600160401b03851660048201526024810184905260440162000c01565b62000e4f62001ac5565b606580546001600160a01b031916905562000ca581620027ec565b604080516060810182525f808252602082018190529181019190915262001f9b6562726964676560d01b5f62001488565b6001600160a01b0316336001600160a01b03161462001fcd57604051632583296b60e01b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa1580156200200a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062002030919062003d7d565b90505f620020528260400151620018b66a195c98cc8c17dd985d5b1d60aa1b90565b9050806001600160a01b031682602001516001600160a01b0316146200208b57604051632583296b60e01b815260040160405180910390fd5b5090565b5f54610100900460ff16620020b85760405162461bcd60e51b815260040162000c019062003deb565b620020c3826200283d565b6001600160a01b038116620020eb576040516375cabfef60e11b815260040160405180910390fd5b62000e4f8162002871565b6040805160a0810182525f80825260208083018290528284018290526060808401819052608084018190526001600160a01b03888116845261012d90925293822054600160401b900416156200233e576001600160a01b038681165f90815261012d6020908152604091829020825160a08101845281546001600160401b0381168252600160401b810490951692810192909252600160e01b90930460ff1691810191909152600182018054919291606084019190620021b6906200352f565b80601f0160208091040260200160405190810160405280929190818152602001828054620021e4906200352f565b8015620022335780601f10620022095761010080835404028352916020019162002233565b820191905f5260205f20905b8154815290600101906020018083116200221557829003601f168201915b505050505081526020016002820180546200224e906200352f565b80601f01602080910402602001604051908101604052809291908181526020018280546200227c906200352f565b8015620022cb5780601f10620022a157610100808354040283529160200191620022cb565b820191905f5260205f20905b815481529060010190602001808311620022ad57829003601f168201915b505050919092525050604051632770a7eb60e21b815233600482015260248101879052919350506001600160a01b03871690639dc29fac906044015f604051808303815f87803b1580156200231e575f80fd5b505af115801562002331573d5f803e3d5ffd5b50505050839050620025ad565b5f8690506040518060a00160405280466001600160401b03168152602001886001600160a01b03168152602001826001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015620023a8573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190620023ce919062003e36565b60ff168152602001826001600160a01b03166395d89b416040518163ffffffff1660e01b81526004015f60405180830381865afa15801562002412573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526200243b919081019062003e54565b8152602001826001600160a01b03166306fdde036040518163ffffffff1660e01b81526004015f60405180830381865afa1580156200247c573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f19168201604052620024a5919081019062003e54565b90526040516370a0823160e01b815230600482015290935087905f906001600160a01b038316906370a0823190602401602060405180830381865afa158015620024f1573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062002517919062003d65565b9050620025306001600160a01b03831633308a620028e5565b6040516370a0823160e01b815230600482015281906001600160a01b038416906370a0823190602401602060405180830381865afa15801562002575573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906200259b919062003d65565b620025a7919062003bcf565b93505050505b306001600160a01b0316637f07c94783898885604051602001620025d5949392919062003e89565b60408051601f1981840301815290829052620025f49160240162003f2e565b604051602081830303815290604052915060e01b6020820180516001600160e01b03838183161783525050505092509450945094915050565b6001600160a01b0381163b6200269c5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840162000c01565b5f80516020620045de83398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b620026d6836200291f565b5f82511180620026e35750805b15620015a957620026f5838362002960565b50505050565b6040516001600160a01b038316602482015260448101829052620015a990849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b03199093169290921790915262002988565b80516001600160401b03165f90815261012e60209081526040808320828501516001600160a01b039081168552925290912054168062001aaa5762000bb08262002a62565b815f03620027b257505050565b620027ce83838360405180602001604052805f81525062002c74565b620015a957604051634c67134d60e11b815260040160405180910390fd5b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b6200285f6001600160a01b0382161562002858578162001f4f565b3362001f4f565b5060c9805461ff001916610100179055565b5f54610100900460ff166200289a5760405162461bcd60e51b815260040162000c019062003deb565b6001600160401b03461115620028c35760405163a12e8fa960e01b815260040160405180910390fd5b609780546001600160a01b0319166001600160a01b0392909216919091179055565b6040516001600160a01b0380851660248301528316604482015260648101829052620026f59085906323b872dd60e01b9060840162002728565b6200292a816200262d565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606062000e648383604051806060016040528060278152602001620045fe6027913962002cb2565b5f620029de826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b031662002d2c9092919063ffffffff16565b905080515f148062002a0157508080602001905181019062002a01919062003f42565b620015a95760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840162000c01565b5f8062002a776033546001600160a01b031690565b60975460208501518551604080880151606089015160808a0151925162002aaf97966001600160a01b03169594939060240162003f60565b60408051601f198184030181529190526020810180516001600160e01b031663bb86ef9360e01b179052905062002af76c0627269646765645f657263323609c1b5f62001488565b8160405162002b069062002f0b565b62002b1392919062003fcf565b604051809103905ff08015801562002b2d573d5f803e3d5ffd5b506001600160a01b038082165f90815261012d602090815260409182902087518154928901519389015160ff16600160e01b0260ff60e01b1994909516600160401b026001600160e01b03199093166001600160401b0390911617919091179190911691909117815560608501519193508491600182019062002bb1908262003ff4565b506080820151600282019062002bc8908262003ff4565b505083516001600160401b039081165f90815261012e6020908152604080832082890180516001600160a01b039081168652919093529281902080546001600160a01b0319168885169081179091559151885160608a015160808b0151848c01519451959850929095169516937fb6b427556e8cb0ebf9175da4bc48c64c4f56e44cfaf8c3ab5ebf8e2ea13090799362002c669391929190620040c0565b60405180910390a450919050565b5f6001600160a01b03851662002c9d57604051634c67134d60e11b815260040160405180910390fd5b5f80835160208501878988f195945050505050565b60605f80856001600160a01b03168560405162002cd09190620040fc565b5f60405180830381855af49150503d805f811462002d0a576040519150601f19603f3d011682016040523d82523d5f602084013e62002d0f565b606091505b509150915062002d228683838762002d3c565b9695505050505050565b606062000e6184845f8562002dc3565b6060831562002daf5782515f0362002da7576001600160a01b0385163b62002da75760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640162000c01565b508162002dbb565b62002dbb838362002ea2565b949350505050565b60608247101562002e265760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b606482015260840162000c01565b5f80866001600160a01b0316858760405162002e439190620040fc565b5f6040518083038185875af1925050503d805f811462002e7f576040519150601f19603f3d011682016040523d82523d5f602084013e62002e84565b606091505b509150915062002e978783838762002d3c565b979650505050505050565b81511562002eb35781518083602001fd5b8060405162461bcd60e51b815260040162000c01919062003f2e565b50805462002edd906200352f565b5f825580601f1062002eed575050565b601f0160209004905f5260205f209081019062000ca5919062002f19565b6104c4806200411a83390190565b5b808211156200208b575f815560010162002f1a565b5f6020828403121562002f40575f80fd5b81356001600160e01b03198116811462000e64575f80fd5b6001600160a01b038116811462000ca5575f80fd5b5f806040838503121562002f7f575f80fd5b82356001600160401b0381111562002f95575f80fd5b830160a0818603121562002fa7575f80fd5b9150602083013562002fb98162002f58565b809150509250929050565b5f6020828403121562002fd5575f80fd5b813562000e648162002f58565b5f806040838503121562002ff4575f80fd5b82356001600160401b038111156200300a575f80fd5b83016101a081860312156200301d575f80fd5b946020939093013593505050565b6001600160401b038116811462000ca5575f80fd5b801515811462000ca5575f80fd5b5f805f6060848603121562003061575f80fd5b83356200306e816200302b565b9250602084013591506040840135620030878162003040565b809150509250925092565b634e487b7160e01b5f52604160045260245ffd5b60405160a081016001600160401b0381118282101715620030cb57620030cb62003092565b60405290565b6040516101a081016001600160401b0381118282101715620030cb57620030cb62003092565b604051601f8201601f191681016001600160401b038111828210171562003122576200312262003092565b604052919050565b5f6001600160401b0382111562003145576200314562003092565b50601f01601f191660200190565b5f82601f83011262003163575f80fd5b81356200317a62003174826200312a565b620030f7565b8181528460208386010111156200318f575f80fd5b816020850160208301375f918101602001919091529392505050565b5f8060408385031215620031bd575f80fd5b8235620031ca8162002f58565b915060208301356001600160401b03811115620031e5575f80fd5b620031f38582860162003153565b9150509250929050565b5f80604083850312156200320f575f80fd5b82359150602083013562002fb98162002f58565b5f806020838503121562003235575f80fd5b82356001600160401b03808211156200324c575f80fd5b818501915085601f83011262003260575f80fd5b8135818111156200326f575f80fd5b86602082850101111562003281575f80fd5b60209290920196919550909350505050565b5f5b83811015620032af57818101518382015260200162003295565b50505f910152565b5f8151808452620032d081602086016020860162003293565b601f01601f19169290920160200192915050565b6001600160401b03861681526001600160a01b038516602082015260ff8416604082015260a0606082018190525f906200332190830185620032b7565b8281036080840152620033358185620032b7565b98975050505050505050565b5f806040838503121562003353575f80fd5b82359150602083013562002fb98162003040565b5f806040838503121562003379575f80fd5b823562002fa78162002f58565b5f6020828403121562003397575f80fd5b81356001600160401b03811115620033ad575f80fd5b8201610120818503121562000e64575f80fd5b60208152620033db6020820183516001600160801b03169052565b5f6020830151620033f760408401826001600160a01b03169052565b5060408301516001600160401b03811660608401525060608301516001600160401b03811660808401525060808301516001600160a01b03811660a08401525060a08301516001600160a01b03811660c08401525060c08301516001600160a01b03811660e08401525060e08301516101006200347e818501836001600160a01b03169052565b8401516101208481019190915284015161014080850191909152840151610160808501919091528401516101a061018080860182905291925090620034c86101c0860184620032b7565b90860151858203601f19018387015290925062002d228382620032b7565b805162001aaa8162002f58565b5f6020828403121562003504575f80fd5b815162000e648162002f58565b5f6020828403121562003522575f80fd5b813562000e64816200302b565b600181811c908216806200354457607f821691505b6020821081036200356357634e487b7160e01b5f52602260045260245ffd5b50919050565b60ff8116811462000ca5575f80fd5b803562001aaa8162003569565b5f6020828403121562003596575f80fd5b813562000e648162003569565b5f808335601e19843603018112620035b9575f80fd5b8301803591506001600160401b03821115620035d3575f80fd5b602001915036819003821315620035e8575f80fd5b9250929050565b818382375f9101908152919050565b601f821115620015a957805f5260205f20601f840160051c81016020851015620036255750805b601f840160051c820191505b8181101562003646575f815560010162003631565b5050505050565b6001600160401b0383111562003667576200366762003092565b6200367f836200367883546200352f565b83620035fe565b5f601f841160018114620036b3575f85156200369b5750838201355b5f19600387901b1c1916600186901b17835562003646565b5f83815260208120601f198716915b82811015620036e45786850135825560209485019460019092019101620036c2565b508682101562003701575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b813562003720816200302b565b6001600160401b03811690508154816001600160401b0319821617835560208401356200374d8162002f58565b68010000000000000000600160e01b03604091821b166001600160e01b0319831684178117855590850135620037838162003569565b60ff60e01b8160e01b1660ff60e01b19851662ffffff60e81b851617831717855550505050620037b76060830183620035a3565b620037c78183600186016200364d565b5050620037d86080830183620035a3565b620026f58183600286016200364d565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b6001600160a01b0388811682528716602082015260a0604082018190525f906200383e9083018789620037e8565b828103606084015262003853818688620037e8565b91505060ff8316608083015298975050505050505050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b5f808585111562003912575f80fd5b838611156200391f575f80fd5b5050820193919092039150565b5f602082840312156200393d575f80fd5b81356001600160401b0381111562003953575f80fd5b62002dbb8482850162003153565b805162001aaa816200302b565b805162001aaa8162003569565b5f82601f8301126200398b575f80fd5b81516200399c62003174826200312a565b818152846020838601011115620039b1575f80fd5b62002dbb82602083016020870162003293565b5f805f8060808587031215620039d8575f80fd5b84516001600160401b0380821115620039ef575f80fd5b9086019060a0828903121562003a03575f80fd5b62003a0d620030a6565b825162003a1a816200302b565b8152602083015162003a2c8162002f58565b602082015262003a3f604084016200396e565b604082015260608301518281111562003a56575f80fd5b62003a648a8286016200397b565b60608301525060808301518281111562003a7c575f80fd5b62003a8a8a8286016200397b565b608083015250955062003aa391505060208601620034e6565b925062003ab360408601620034e6565b6060959095015193969295505050565b803562001aaa8162002f58565b5f805f806080858703121562003ae4575f80fd5b84356001600160401b038082111562003afb575f80fd5b9086019060a0828903121562003b0f575f80fd5b62003b19620030a6565b823562003b26816200302b565b8152602083013562003b388162002f58565b602082015262003b4b6040840162003578565b604082015260608301358281111562003b62575f80fd5b62003b708a82860162003153565b60608301525060808301358281111562003b88575f80fd5b62003b968a82860162003153565b608083015250955062003baf9150506020860162003ac3565b925062003bbf6040860162003ac3565b9396929550929360600135925050565b8181038181111562000bb057634e487b7160e01b5f52601160045260245ffd5b80516001600160801b038116811462001aaa575f80fd5b5f806040838503121562003c18575f80fd5b8251915060208301516001600160401b038082111562003c36575f80fd5b908401906101a0828703121562003c4b575f80fd5b62003c55620030d1565b62003c608362003bef565b815262003c7060208401620034e6565b602082015262003c836040840162003961565b604082015262003c966060840162003961565b606082015262003ca960808401620034e6565b608082015262003cbc60a08401620034e6565b60a082015262003ccf60c08401620034e6565b60c082015262003ce260e08401620034e6565b60e0820152610100838101519082015261012080840151908201526101408084015190820152610160808401518381111562003d1c575f80fd5b62003d2a898287016200397b565b828401525050610180808401518381111562003d44575f80fd5b62003d52898287016200397b565b8284015250508093505050509250929050565b5f6020828403121562003d76575f80fd5b5051919050565b5f6060828403121562003d8e575f80fd5b604051606081018181106001600160401b038211171562003db35762003db362003092565b60405282518152602083015162003dca8162002f58565b6020820152604083015162003ddf816200302b565b60408201529392505050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f6020828403121562003e47575f80fd5b815162000e648162003569565b5f6020828403121562003e65575f80fd5b81516001600160401b0381111562003e7b575f80fd5b62002dbb848285016200397b565b608081526001600160401b03855116608082015260018060a01b0360208601511660a082015260ff60408601511660c08201525f606086015160a060e084015262003ed9610120840182620032b7565b90506080870151607f198483030161010085015262003ef98282620032b7565b9250505062003f1360208301866001600160a01b03169052565b6001600160a01b039390931660408201526060015292915050565b602081525f62000e646020830184620032b7565b5f6020828403121562003f53575f80fd5b815162000e648162003040565b6001600160a01b0388811682528781166020830152861660408201526001600160401b038516606082015260ff8416608082015260e060a082018190525f9062003fad90830185620032b7565b82810360c084015262003fc18185620032b7565b9a9950505050505050505050565b6001600160a01b03831681526040602082018190525f9062000e6190830184620032b7565b81516001600160401b0381111562004010576200401062003092565b62004028816200402184546200352f565b84620035fe565b602080601f8311600181146200405e575f8415620040465750858301515b5f19600386901b1c1916600185901b178555620040b8565b5f85815260208120601f198616915b828110156200408e578886015182559484019460019091019084016200406d565b5085821015620040ac57878501515f19600388901b60f8161c191681555b505060018460011b0185555b505050505050565b606081525f620040d46060830186620032b7565b8281036020840152620040e88186620032b7565b91505060ff83166040830152949350505050565b5f82516200410f81846020870162003293565b919091019291505056fe60806040526040516104c43803806104c4833981016040819052610022916102d2565b61002d82825f610034565b50506103e7565b61003d8361005f565b5f825111806100495750805b1561005a57610058838361009e565b505b505050565b610068816100ca565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606100c3838360405180606001604052806027815260200161049d6027913961017d565b9392505050565b6001600160a01b0381163b61013c5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80856001600160a01b031685604051610199919061039a565b5f60405180830381855af49150503d805f81146101d1576040519150601f19603f3d011682016040523d82523d5f602084013e6101d6565b606091505b5090925090506101e8868383876101f2565b9695505050505050565b606083156102605782515f03610259576001600160a01b0385163b6102595760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610133565b508161026a565b61026a8383610272565b949350505050565b8151156102825781518083602001fd5b8060405162461bcd60e51b815260040161013391906103b5565b634e487b7160e01b5f52604160045260245ffd5b5f5b838110156102ca5781810151838201526020016102b2565b50505f910152565b5f80604083850312156102e3575f80fd5b82516001600160a01b03811681146102f9575f80fd5b60208401519092506001600160401b0380821115610315575f80fd5b818501915085601f830112610328575f80fd5b81518181111561033a5761033a61029c565b604051601f8201601f19908116603f011681019083821181831017156103625761036261029c565b8160405282815288602084870101111561037a575f80fd5b61038b8360208301602088016102b0565b80955050505050509250929050565b5f82516103ab8184602087016102b0565b9190910192915050565b602081525f82518060208401526103d38160408501602087016102b0565b601f01601f19169190910160400192915050565b60aa806103f35f395ff3fe608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156070573d5ff35b3d5ffdfea2646970667358221220ee2b34631d0aeafcbca8c09b3d006837becb1e761da7ef1dfde69d83e98a206164736f6c63430008180033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122022da3beb0be1c40b804b65c991383ee9453da8ac34cdebe2148f706343621e4e64736f6c63430008180033", - "balance": "0x0" - }, - "0x1670090000000000000000000000000000000002": { - "contractName": "ERC20Vault", - "storage": { - "0x00000000000000000000000000000000000000000000000000000000000000c9": "0x0000000000000000000000000000000000000000000000000000000000000101", - "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190", - "0x0000000000000000000000000000000000000000000000000000000000000097": "0x0000000000000000000000001670090000000000000000000000000000000006", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0167009000000000000000000000000000000002" - }, - "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156070573d5ff35b3d5ffdfea2646970667358221220ee2b34631d0aeafcbca8c09b3d006837becb1e761da7ef1dfde69d83e98a206164736f6c63430008180033", - "balance": "0x0" - }, - "0x0167009000000000000000000000000000000003": { - "contractName": "ERC721VaultImpl", - "storage": { - "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190" - }, - "code": "0x6080604052600436106200019b575f3560e01c80635c975abb11620000de5780638456cb591162000092578063a86f9d9e116200006a578063a86f9d9e14620004c9578063e30c397814620004ed578063f09a4016146200050c578063f2fde38b1462000530575f80fd5b80638456cb59146200045d5780638da5cb5b14620004745780639aa8605c1462000493575f80fd5b80635c975abb146200039c578063634da63a14620003be57806367090ccf14620003d4578063715018a6146200041857806379ba5097146200042f5780637f07c9471462000446575f80fd5b80633ab76e9f11620001525780633f4ba83a116200012a5780633f4ba83a146200033b5780634f1ef286146200035257806352d1902d146200036957806359f4a9071462000380575f80fd5b80633ab76e9f14620002c65780633c6f5de214620003005780633eb6b8cf1462000317575f80fd5b806301ffc9a7146200019f57806306fdde0314620001e85780631507cc471462000215578063150b7a02146200023b5780632ca069a514620002845780633659cfe614620002a0575b5f80fd5b348015620001ab575f80fd5b50620001d3620001bd366004620026b4565b6001600160e01b031916631e37aef160e11b1490565b60405190151581526020015b60405180910390f35b348015620001f4575f80fd5b506b195c98cdcc8c57dd985d5b1d60a21b5b604051908152602001620001df565b6200022c62000226366004620028f3565b62000554565b604051620001df919062002a78565b34801562000247575f80fd5b506200026a6200025936600462002be5565b630a85bd0160e11b95945050505050565b6040516001600160e01b03199091168152602001620001df565b34801562000290575f80fd5b506200026a636cdb3d1360e11b81565b348015620002ac575f80fd5b50620002c4620002be36600462002c5a565b62000961565b005b348015620002d2575f80fd5b50609754620002e7906001600160a01b031681565b6040516001600160a01b039091168152602001620001df565b620002c46200031136600462002c78565b62000a53565b34801562000323575f80fd5b50620002e76200033536600462002ccf565b62000c28565b34801562000347575f80fd5b50620002c462000c40565b620002c46200036336600462002d13565b62000cc3565b34801562000375575f80fd5b506200020662000d9a565b3480156200038c575f80fd5b506200026a6380ac58cd60e01b81565b348015620003a8575f80fd5b50620001d360c954610100900460ff1660021490565b348015620003ca575f80fd5b5062000206600a81565b348015620003e0575f80fd5b50620002e7620003f236600462002d65565b61012e60209081525f92835260408084209091529082529020546001600160a01b031681565b34801562000424575f80fd5b50620002c462000e4f565b3480156200043b575f80fd5b50620002c462000e64565b620002c46200045736600462002d96565b62000edf565b34801562000469575f80fd5b50620002c462001093565b34801562000480575f80fd5b506033546001600160a01b0316620002e7565b3480156200049f575f80fd5b50620004b7620004b136600462002c5a565b62001116565b604051620001df949392919062002dd8565b348015620004d5575f80fd5b50620002e7620004e736600462002e2b565b62001271565b348015620004f9575f80fd5b506065546001600160a01b0316620002e7565b34801562000518575f80fd5b50620002c46200052a36600462002e51565b62001288565b3480156200053c575f80fd5b50620002c46200054e36600462002c5a565b620013a0565b604080516101a0810182525f8082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e082018390526101008201839052610120820183905261014082019290925261016081018290526101808101919091526002620005cd60c95460ff1690565b60ff1603620005ef5760405163dfc60d8560e01b815260040160405180910390fd5b620005fb600262001414565b6200061060c954610100900460ff1660021490565b156200062f5760405163bae6e2a960e01b815260040160405180910390fd5b818060a0015151816080015151146200065b5760405163196e8a4160e31b815260040160405180910390fd5b600a8160800151511115620006835760405163e4a4c1c760e01b815260040160405180910390fd5b60608101516001600160a01b0316620006af576040516303f8a7d360e01b815260040160405180910390fd5b5f5b83608001515181101562000709578360a001518181518110620006d857620006d862002e82565b60200260200101515f146200070057604051634299323b60e11b815260040160405180910390fd5b600101620006b1565b5060608301516200072b906001600160a01b03166380ac58cd60e01b6200142a565b6200074957604051633ee915f560e11b815260040160405180910390fd5b5f80620007573386620014bd565b604080516101a0810182525f808252602080830182905292820181905289516001600160401b03166060830152336080830152918901519395509193509160a08201906001600160a01b0316620007af5733620007b5565b87602001515b6001600160a01b03168152602001620007e9885f0151620007e26b195c98cdcc8c57dd985d5b1d60a21b90565b5f62000c28565b6001600160a01b031681526020018761010001516001600160a01b031681526020018760e00151346200081d919062002e96565b81526020018760e0015181526020018760c00151815260200184815260200187610120015181525090505f6200085d6562726964676560d01b5f62001271565b6001600160a01b0316636c334e2e34846040518363ffffffff1660e01b81526004016200088b919062002a78565b5f6040518083038185885af1158015620008a7573d5f803e3d5ffd5b50505050506040513d5f823e601f3d908101601f19168201604052620008d1919081019062002f30565b809750819250505086604001516001600160a01b031686608001516001600160a01b0316827fabbf62a1459339f9ac59136d313a5ccd83d2706cc6d4c04d90642520169144dc896060015187602001518c606001518d608001518e60a0015160405162000943959493929190620030cb565b60405180910390a450505050506200095c600162001414565b919050565b6001600160a01b037f0000000000000000000000000167009000000000000000000000000000000003163003620009b55760405162461bcd60e51b8152600401620009ac9062003127565b60405180910390fd5b7f00000000000000000000000001670090000000000000000000000000000000036001600160a01b0316620009ff5f8051602062003de5833981519152546001600160a01b031690565b6001600160a01b03161462000a285760405162461bcd60e51b8152600401620009ac9062003173565b62000a338162001988565b604080515f8082526020820190925262000a509183919062001992565b50565b600262000a6260c95460ff1690565b60ff160362000a845760405163dfc60d8560e01b815260040160405180910390fd5b62000a90600262001414565b62000aa560c954610100900460ff1660021490565b1562000ac45760405163bae6e2a960e01b815260040160405180910390fd5b62000ace62001b09565b505f62000ae0610160840184620031bf565b62000af091600490829062003204565b81019062000aff91906200322d565b90505f808280602001905181019062000b199190620032c5565b9350505091505f62000b418387608001602081019062000b3a919062002c5a565b8462001c00565b905062000b6f61010087013562000b5f60a0890160808a0162002c5a565b6001600160a01b03169062001d8a565b62000b8160a087016080880162002c5a565b6001600160a01b0316857fe48bef18455e47bca14864ab6e82dffa29df148b051c09de95aec44ecf13598c8560200151848687516001600160401b0381111562000bcf5762000bcf620026dd565b60405190808252806020026020018201604052801562000bf9578160200160208202803683370190505b5060405162000c0c9493929190620033cf565b60405180910390a35050505062000c24600162001414565b5050565b5f62000c3684848462001d97565b90505b9392505050565b62000c5560c954610100900460ff1660021490565b62000c735760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166101001790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a162000cc1335f62001e8a565b565b6001600160a01b037f000000000000000000000000016700900000000000000000000000000000000316300362000d0e5760405162461bcd60e51b8152600401620009ac9062003127565b7f00000000000000000000000001670090000000000000000000000000000000036001600160a01b031662000d585f8051602062003de5833981519152546001600160a01b031690565b6001600160a01b03161462000d815760405162461bcd60e51b8152600401620009ac9062003173565b62000d8c8262001988565b62000c248282600162001992565b5f306001600160a01b037f0000000000000000000000000167009000000000000000000000000000000003161462000e3b5760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401620009ac565b505f8051602062003de58339815191525b90565b62000e5962001e8f565b62000cc15f62001eeb565b60655433906001600160a01b0316811462000ed45760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b6064820152608401620009ac565b62000a508162001eeb565b600262000eee60c95460ff1690565b60ff160362000f105760405163dfc60d8560e01b815260040160405180910390fd5b62000f1c600262001414565b62000f3160c954610100900460ff1660021490565b1562000f505760405163bae6e2a960e01b815260040160405180910390fd5b5f80808062000f628587018762003410565b93509350935093505f62000f7562001f06565b90506001600160a01b038316158062000f9657506001600160a01b03831630145b1562000fb55760405163def9481360e01b815260040160405180910390fd5b5f62000fc386858562001c00565b905062000fda6001600160a01b0385163462001d8a565b836001600160a01b0316856001600160a01b0316835f01517f895f73e418d1bbbad2a311d085fad00e5d98a960e9f2afa4b942071d39bec43a85604001518a6020015186898a516001600160401b038111156200103b576200103b620026dd565b60405190808252806020026020018201604052801562001065578160200160208202803683370190505b5060405162001079959493929190620030cb565b60405180910390a450505050505062000c24600162001414565b620010a860c954610100900460ff1660021490565b15620010c75760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a162000cc133600162001e8a565b61012d6020525f9081526040902080546001820180546001600160401b03831693600160401b9093046001600160a01b031692919062001156906200350e565b80601f016020809104026020016040519081016040528092919081815260200182805462001184906200350e565b8015620011d35780601f10620011a957610100808354040283529160200191620011d3565b820191905f5260205f20905b815481529060010190602001808311620011b557829003601f168201915b505050505090806002018054620011ea906200350e565b80601f016020809104026020016040519081016040528092919081815260200182805462001218906200350e565b8015620012675780601f106200123d5761010080835404028352916020019162001267565b820191905f5260205f20905b8154815290600101906020018083116200124957829003601f168201915b5050505050905084565b5f6200127f46848462001d97565b90505b92915050565b5f54610100900460ff1615808015620012a757505f54600160ff909116105b80620012c25750303b158015620012c257505f5460ff166001145b620013275760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401620009ac565b5f805460ff19166001179055801562001349575f805461ff0019166101001790555b6200135583836200202c565b80156200139b575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050565b620013aa62001e8f565b606580546001600160a01b0383166001600160a01b03199091168117909155620013dc6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60c9805460ff191660ff92909216919091179055565b5f6001600160a01b0383163b6200144357505f62001282565b6040516301ffc9a760e01b81526001600160e01b0319831660048201526001600160a01b038416906301ffc9a790602401602060405180830381865afa925050508015620014b0575060408051601f3d908101601f19168201909252620014ad9181019062003548565b60015b1562001282579392505050565b604080516080810182525f8082526020820152606091810182905280820182905260608301516001600160a01b039081165f90815261012d6020526040902054600160401b90041615620017435760608301516001600160a01b039081165f90815261012d6020908152604091829020825160808101845281546001600160401b0381168252600160401b90049094169184019190915260018101805491928401916200156a906200350e565b80601f016020809104026020016040519081016040528092919081815260200182805462001598906200350e565b8015620015e75780601f10620015bd57610100808354040283529160200191620015e7565b820191905f5260205f20905b815481529060010190602001808311620015c957829003601f168201915b5050505050815260200160028201805462001602906200350e565b80601f016020809104026020016040519081016040528092919081815260200182805462001630906200350e565b80156200167f5780601f1062001655576101008083540402835291602001916200167f565b820191905f5260205f20905b8154815290600101906020018083116200166157829003601f168201915b50505050508152505090505f5b8360800151518110156200173c5783606001516001600160a01b0316639dc29fac8686608001518481518110620016c757620016c762002e82565b60200260200101516040518363ffffffff1660e01b8152600401620017019291906001600160a01b03929092168252602082015260400190565b5f604051808303815f87803b15801562001719575f80fd5b505af11580156200172c573d5f803e3d5ffd5b505050508060010190506200168c565b5062001903565b5f836060015190506040518060800160405280466001600160401b0316815260200185606001516001600160a01b03168152602001826001600160a01b03166395d89b416040518163ffffffff1660e01b81526004015f60405180830381865afa158015620017b4573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f19168201604052620017dd919081019062003566565b8152602001826001600160a01b03166306fdde036040518163ffffffff1660e01b81526004015f60405180830381865afa1580156200181e573d5f803e3d5ffd5b505050506040513d5f823e601f3d908101601f1916820160405262001847919081019062003566565b905291505f5b8460800151518110156200190057816001600160a01b03166342842e0e87308860800151858151811062001885576200188562002e82565b60209081029190910101516040516001600160e01b031960e086901b1681526001600160a01b03938416600482015292909116602483015260448201526064015f604051808303815f87803b158015620018dd575f80fd5b505af1158015620018f0573d5f803e3d5ffd5b505050508060010190506200184d565b50505b306001600160a01b0316637f07c9478286866040015187608001516040516020016200193394939291906200359b565b60408051601f198184030181529082905262001952916024016200362a565b604051602081830303815290604052915060e01b6020820180516001600160e01b03838183161783525050505091509250929050565b62000a5062001e8f565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff1615620019c8576200139b8362002093565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa92505050801562001a25575060408051601f3d908101601f1916820190925262001a22918101906200363e565b60015b62001a8a5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401620009ac565b5f8051602062003de5833981519152811462001afb5760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401620009ac565b506200139b83838362002131565b604080516060810182525f808252602082018190529181019190915262001b3a6562726964676560d01b5f62001271565b6001600160a01b0316336001600160a01b03161462001b6c57604051632583296b60e01b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa15801562001ba9573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001bcf919062003656565b60208101519091506001600160a01b0316331462000e4c57604051632583296b60e01b815260040160405180910390fd5b5f46845f01516001600160401b03160362001ccf575060208301515f5b825181101562001cc857816001600160a01b03166342842e0e308686858151811062001c4d5762001c4d62002e82565b60209081029190910101516040516001600160e01b031960e086901b1681526001600160a01b03938416600482015292909116602483015260448201526064015f604051808303815f87803b15801562001ca5575f80fd5b505af115801562001cb8573d5f803e3d5ffd5b5050505080600101905062001c1d565b5062000c39565b62001cda8462002161565b90505f5b825181101562001d8257816001600160a01b03166340c10f198585848151811062001d0d5762001d0d62002e82565b60200260200101516040518363ffffffff1660e01b815260040162001d479291906001600160a01b03929092168252602082015260400190565b5f604051808303815f87803b15801562001d5f575f80fd5b505af115801562001d72573d5f803e3d5ffd5b5050505080600101905062001cde565b509392505050565b62000c2482825a620021a6565b6097545f906001600160a01b031662001dc357604051638ed88b2560e01b815260040160405180910390fd5b609754604051630a3dc4f360e21b81526001600160401b0386166004820152602481018590526001600160a01b03909116906328f713cc90604401602060405180830381865afa15801562001e1a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001e409190620036c4565b90508115801562001e5857506001600160a01b038116155b1562000c3957604051632b0d65db60e01b81526001600160401b038516600482015260248101849052604401620009ac565b62000c245b6033546001600160a01b0316331462000cc15760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401620009ac565b606580546001600160a01b031916905562000a5081620021ed565b604080516060810182525f808252602082018190529181019190915262001f376562726964676560d01b5f62001271565b6001600160a01b0316336001600160a01b03161462001f6957604051632583296b60e01b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa15801562001fa6573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001fcc919062003656565b90505f62001fef8260400151620007e26b195c98cdcc8c57dd985d5b1d60a21b90565b9050806001600160a01b031682602001516001600160a01b0316146200202857604051632583296b60e01b815260040160405180910390fd5b5090565b5f54610100900460ff16620020555760405162461bcd60e51b8152600401620009ac90620036e2565b62002060826200223e565b6001600160a01b03811662002088576040516375cabfef60e11b815260040160405180910390fd5b62000c248162002272565b6001600160a01b0381163b620021025760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401620009ac565b5f8051602062003de583398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b6200213c83620022e6565b5f82511180620021495750805b156200139b576200215b838362002327565b50505050565b80516001600160401b03165f90815261012e60209081526040808320828501516001600160a01b03908116855292529091205416806200095c5762001282826200234f565b815f03620021b357505050565b620021cf83838360405180602001604052805f8152506200253a565b6200139b57604051634c67134d60e11b815260040160405180910390fd5b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b620022606001600160a01b0382161562002259578162001eeb565b3362001eeb565b5060c9805461ff001916610100179055565b5f54610100900460ff166200229b5760405162461bcd60e51b8152600401620009ac90620036e2565b6001600160401b03461115620022c45760405163a12e8fa960e01b815260040160405180910390fd5b609780546001600160a01b0319166001600160a01b0392909216919091179055565b620022f18162002093565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606200127f838360405180606001604052806027815260200162003e056027913962002578565b5f80620023646033546001600160a01b031690565b60975460208501518551604080880151606089015191516200239796956001600160a01b0316949392906024016200372d565b60408051601f198184030181529190526020810180516001600160e01b03166377c6257360e11b1790529050620023e06d627269646765645f65726337323160901b5f62001271565b81604051620023ef90620026a6565b620023fc92919062003792565b604051809103905ff08015801562002416573d5f803e3d5ffd5b506001600160a01b038082165f90815261012d60209081526040918290208751815492890151909416600160401b026001600160e01b03199092166001600160401b0390941693909317178255850151919350849160018201906200247c908262003806565b506060820151600282019062002493908262003806565b505083516001600160401b039081165f90815261012e6020908152604080832082890180516001600160a01b039081168652919093529281902080546001600160a01b03191688851690811790915591518851828a015160608b01519351949750919094169493909316927f44977f2d30fe1e3aee2c1476f2f95aaacaf34e44b9359c403da01fcc93fd751b926200252c9290620038d2565b60405180910390a450919050565b5f6001600160a01b0385166200256357604051634c67134d60e11b815260040160405180910390fd5b5f80835160208501878988f195945050505050565b60605f80856001600160a01b03168560405162002596919062003903565b5f60405180830381855af49150503d805f8114620025d0576040519150601f19603f3d011682016040523d82523d5f602084013e620025d5565b606091505b5091509150620025e886838387620025f2565b9695505050505050565b60608315620026655782515f036200265d576001600160a01b0385163b6200265d5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401620009ac565b508162002671565b62002671838362002679565b949350505050565b8151156200268a5781518083602001fd5b8060405162461bcd60e51b8152600401620009ac91906200362a565b6104c4806200392183390190565b5f60208284031215620026c5575f80fd5b81356001600160e01b0319811681146200127f575f80fd5b634e487b7160e01b5f52604160045260245ffd5b60405161014081016001600160401b0381118282101715620027175762002717620026dd565b60405290565b6040516101a081016001600160401b0381118282101715620027175762002717620026dd565b604051608081016001600160401b0381118282101715620027175762002717620026dd565b604051601f8201601f191681016001600160401b0381118282101715620027935762002793620026dd565b604052919050565b6001600160401b038116811462000a50575f80fd5b80356200095c816200279b565b6001600160a01b038116811462000a50575f80fd5b80356200095c81620027bd565b5f6001600160401b03821115620027fa57620027fa620026dd565b5060051b60200190565b5f82601f83011262002814575f80fd5b813560206200282d6200282783620027df565b62002768565b8083825260208201915060208460051b8701019350868411156200284f575f80fd5b602086015b848110156200286d578035835291830191830162002854565b509695505050505050565b5f6001600160401b03821115620028935762002893620026dd565b50601f01601f191660200190565b5f82601f830112620028b1575f80fd5b8135620028c2620028278262002878565b818152846020838601011115620028d7575f80fd5b816020850160208301375f918101602001919091529392505050565b5f6020828403121562002904575f80fd5b81356001600160401b03808211156200291b575f80fd5b90830190610140828603121562002930575f80fd5b6200293a620026f1565b6200294583620027b0565b81526200295560208401620027d2565b60208201526200296860408401620027d2565b60408201526200297b60608401620027d2565b606082015260808301358281111562002992575f80fd5b620029a08782860162002804565b60808301525060a083013582811115620029b8575f80fd5b620029c68782860162002804565b60a08301525060c083013560c082015260e083013560e0820152610100620029f0818501620027d2565b90820152610120838101358381111562002a08575f80fd5b62002a1688828701620028a1565b918301919091525095945050505050565b5f5b8381101562002a4357818101518382015260200162002a29565b50505f910152565b5f815180845262002a6481602086016020860162002a27565b601f01601f19169290920160200192915050565b6020815262002a936020820183516001600160801b03169052565b5f602083015162002aaf60408401826001600160a01b03169052565b5060408301516001600160401b03811660608401525060608301516001600160401b03811660808401525060808301516001600160a01b03811660a08401525060a08301516001600160a01b03811660c08401525060c08301516001600160a01b03811660e08401525060e083015161010062002b36818501836001600160a01b03169052565b8401516101208481019190915284015161014080850191909152840151610160808501919091528401516101a06101808086018290529192509062002b806101c086018462002a4b565b90860151858203601f190183870152909250620025e8838262002a4b565b5f8083601f84011262002baf575f80fd5b5081356001600160401b0381111562002bc6575f80fd5b60208301915083602082850101111562002bde575f80fd5b9250929050565b5f805f805f6080868803121562002bfa575f80fd5b853562002c0781620027bd565b9450602086013562002c1981620027bd565b93506040860135925060608601356001600160401b0381111562002c3b575f80fd5b62002c498882890162002b9e565b969995985093965092949392505050565b5f6020828403121562002c6b575f80fd5b81356200127f81620027bd565b5f806040838503121562002c8a575f80fd5b82356001600160401b0381111562002ca0575f80fd5b83016101a0818603121562002cb3575f80fd5b946020939093013593505050565b801515811462000a50575f80fd5b5f805f6060848603121562002ce2575f80fd5b833562002cef816200279b565b925060208401359150604084013562002d088162002cc1565b809150509250925092565b5f806040838503121562002d25575f80fd5b823562002d3281620027bd565b915060208301356001600160401b0381111562002d4d575f80fd5b62002d5b85828601620028a1565b9150509250929050565b5f806040838503121562002d77575f80fd5b82359150602083013562002d8b81620027bd565b809150509250929050565b5f806020838503121562002da8575f80fd5b82356001600160401b0381111562002dbe575f80fd5b62002dcc8582860162002b9e565b90969095509350505050565b6001600160401b03851681526001600160a01b03841660208201526080604082018190525f9062002e0c9083018562002a4b565b828103606084015262002e20818562002a4b565b979650505050505050565b5f806040838503121562002e3d575f80fd5b82359150602083013562002d8b8162002cc1565b5f806040838503121562002e63575f80fd5b823562002e7081620027bd565b9150602083013562002d8b81620027bd565b634e487b7160e01b5f52603260045260245ffd5b818103818111156200128257634e487b7160e01b5f52601160045260245ffd5b80516001600160801b03811681146200095c575f80fd5b80516200095c81620027bd565b80516200095c816200279b565b5f82601f83011262002ef7575f80fd5b815162002f08620028278262002878565b81815284602083860101111562002f1d575f80fd5b6200267182602083016020870162002a27565b5f806040838503121562002f42575f80fd5b8251915060208301516001600160401b038082111562002f60575f80fd5b908401906101a0828703121562002f75575f80fd5b62002f7f6200271d565b62002f8a8362002eb6565b815262002f9a6020840162002ecd565b602082015262002fad6040840162002eda565b604082015262002fc06060840162002eda565b606082015262002fd36080840162002ecd565b608082015262002fe660a0840162002ecd565b60a082015262002ff960c0840162002ecd565b60c08201526200300c60e0840162002ecd565b60e0820152610100838101519082015261012080840151908201526101408084015190820152610160808401518381111562003046575f80fd5b620030548982870162002ee7565b82840152505061018080840151838111156200306e575f80fd5b6200307c8982870162002ee7565b8284015250508093505050509250929050565b5f815180845260208085019450602084015f5b83811015620030c057815187529582019590820190600101620030a2565b509495945050505050565b6001600160401b03861681526001600160a01b0385811660208301528416604082015260a0606082018190525f9062003107908301856200308f565b82810360808401526200311b81856200308f565b98975050505050505050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b5f808335601e19843603018112620031d5575f80fd5b8301803591506001600160401b03821115620031ef575f80fd5b60200191503681900382131562002bde575f80fd5b5f808585111562003213575f80fd5b8386111562003220575f80fd5b5050820193919092039150565b5f602082840312156200323e575f80fd5b81356001600160401b0381111562003254575f80fd5b6200267184828501620028a1565b5f82601f83011262003272575f80fd5b81516020620032856200282783620027df565b8083825260208201915060208460051b870101935086841115620032a7575f80fd5b602086015b848110156200286d5780518352918301918301620032ac565b5f805f8060808587031215620032d9575f80fd5b84516001600160401b0380821115620032f0575f80fd5b908601906080828903121562003304575f80fd5b6200330e62002743565b82516200331b816200279b565b815260208301516200332d81620027bd565b602082015260408301518281111562003344575f80fd5b620033528a82860162002ee7565b6040830152506060830151828111156200336a575f80fd5b620033788a82860162002ee7565b60608301525095506200338e6020880162002ecd565b94506200339e6040880162002ecd565b93506060870151915080821115620033b4575f80fd5b50620033c38782880162003262565b91505092959194509250565b6001600160a01b038581168252841660208201526080604082018190525f90620033fc908301856200308f565b828103606084015262002e2081856200308f565b5f805f806080858703121562003424575f80fd5b84356001600160401b03808211156200343b575f80fd5b90860190608082890312156200344f575f80fd5b6200345962002743565b823562003466816200279b565b815260208301356200347881620027bd565b60208201526040830135828111156200348f575f80fd5b6200349d8a828601620028a1565b604083015250606083013582811115620034b5575f80fd5b620034c38a828601620028a1565b6060830152509550620034d960208801620027d2565b9450620034e960408801620027d2565b93506060870135915080821115620034ff575f80fd5b50620033c38782880162002804565b600181811c908216806200352357607f821691505b6020821081036200354257634e487b7160e01b5f52602260045260245ffd5b50919050565b5f6020828403121562003559575f80fd5b81516200127f8162002cc1565b5f6020828403121562003577575f80fd5b81516001600160401b038111156200358d575f80fd5b620026718482850162002ee7565b608080825285516001600160401b03168282015260208601516001600160a01b0390811660a0840152604087015160c08401929092525f9190620035e461010085018362002a4b565b91506060880151607f198584030160e086015262003603838262002a4b565b88831660208701529187166040860152508381036060850152905062002e2081856200308f565b602081525f6200127f602083018462002a4b565b5f602082840312156200364f575f80fd5b5051919050565b5f6060828403121562003667575f80fd5b604051606081018181106001600160401b03821117156200368c576200368c620026dd565b604052825181526020830151620036a381620027bd565b60208201526040830151620036b8816200279b565b60408201529392505050565b5f60208284031215620036d5575f80fd5b81516200127f81620027bd565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6001600160a01b0387811682528681166020830152851660408201526001600160401b038416606082015260c0608082018190525f90620037719083018562002a4b565b82810360a084015262003785818562002a4b565b9998505050505050505050565b6001600160a01b03831681526040602082018190525f9062000c369083018462002a4b565b601f8211156200139b57805f5260205f20601f840160051c81016020851015620037de5750805b601f840160051c820191505b81811015620037ff575f8155600101620037ea565b5050505050565b81516001600160401b03811115620038225762003822620026dd565b6200383a816200383384546200350e565b84620037b7565b602080601f83116001811462003870575f8415620038585750858301515b5f19600386901b1c1916600185901b178555620038ca565b5f85815260208120601f198616915b82811015620038a0578886015182559484019460019091019084016200387f565b5085821015620038be57878501515f19600388901b60f8161c191681555b505060018460011b0185555b505050505050565b604081525f620038e6604083018562002a4b565b8281036020840152620038fa818562002a4b565b95945050505050565b5f82516200391681846020870162002a27565b919091019291505056fe60806040526040516104c43803806104c4833981016040819052610022916102d2565b61002d82825f610034565b50506103e7565b61003d8361005f565b5f825111806100495750805b1561005a57610058838361009e565b505b505050565b610068816100ca565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606100c3838360405180606001604052806027815260200161049d6027913961017d565b9392505050565b6001600160a01b0381163b61013c5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80856001600160a01b031685604051610199919061039a565b5f60405180830381855af49150503d805f81146101d1576040519150601f19603f3d011682016040523d82523d5f602084013e6101d6565b606091505b5090925090506101e8868383876101f2565b9695505050505050565b606083156102605782515f03610259576001600160a01b0385163b6102595760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610133565b508161026a565b61026a8383610272565b949350505050565b8151156102825781518083602001fd5b8060405162461bcd60e51b815260040161013391906103b5565b634e487b7160e01b5f52604160045260245ffd5b5f5b838110156102ca5781810151838201526020016102b2565b50505f910152565b5f80604083850312156102e3575f80fd5b82516001600160a01b03811681146102f9575f80fd5b60208401519092506001600160401b0380821115610315575f80fd5b818501915085601f830112610328575f80fd5b81518181111561033a5761033a61029c565b604051601f8201601f19908116603f011681019083821181831017156103625761036261029c565b8160405282815288602084870101111561037a575f80fd5b61038b8360208301602088016102b0565b80955050505050509250929050565b5f82516103ab8184602087016102b0565b9190910192915050565b602081525f82518060208401526103d38160408501602087016102b0565b601f01601f19169190910160400192915050565b60aa806103f35f395ff3fe608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156070573d5ff35b3d5ffdfea2646970667358221220ee2b34631d0aeafcbca8c09b3d006837becb1e761da7ef1dfde69d83e98a206164736f6c63430008180033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122078d47b9ef19554ced9fdffe22a811a18cbe200ac438a1d972be4de0a658b715564736f6c63430008180033", - "balance": "0x0" - }, - "0x1670090000000000000000000000000000000003": { - "contractName": "ERC721Vault", - "storage": { - "0x00000000000000000000000000000000000000000000000000000000000000c9": "0x0000000000000000000000000000000000000000000000000000000000000101", - "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190", - "0x0000000000000000000000000000000000000000000000000000000000000097": "0x0000000000000000000000001670090000000000000000000000000000000006", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0167009000000000000000000000000000000003" - }, - "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156070573d5ff35b3d5ffdfea2646970667358221220ee2b34631d0aeafcbca8c09b3d006837becb1e761da7ef1dfde69d83e98a206164736f6c63430008180033", - "balance": "0x0" - }, - "0x0167009000000000000000000000000000000004": { - "contractName": "ERC1155VaultImpl", - "storage": { - "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190" - }, - "code": "0x608060405260043610620001b7575f3560e01c8063634da63a11620000fa5780639aa8605c1162000092578063e30c3978116200006a578063e30c397814620004fd578063f09a4016146200051c578063f23a6e611462000540578063f2fde38b1462000570575f80fd5b80639aa8605c1462000471578063a86f9d9e14620004a7578063bc197c8114620004cb575f80fd5b806379ba509711620000d257806379ba5097146200040d5780637f07c94714620004245780638456cb59146200043b5780638da5cb5b1462000452575f80fd5b8063634da63a146200039c57806367090ccf14620003b2578063715018a614620003f6575f80fd5b80633c6f5de2116200016e5780634f1ef28611620001465780634f1ef286146200033057806352d1902d146200034757806359f4a907146200035e5780635c975abb146200037a575f80fd5b80633c6f5de214620002de5780633eb6b8cf14620002f55780633f4ba83a1462000319575f80fd5b806301ffc9a714620001bb57806306fdde0314620001f45780631507cc4714620002225780632ca069a514620002485780633659cfe6146200027e5780633ab76e9f14620002a4575b5f80fd5b348015620001c7575f80fd5b50620001df620001d93660046200268e565b62000594565b60405190151581526020015b60405180910390f35b34801562000200575f80fd5b506c195c98cc4c4d4d57dd985d5b1d609a1b5b604051908152602001620001eb565b6200023962000233366004620028cd565b620005cb565b604051620001eb919062002a52565b34801562000254575f80fd5b5062000264636cdb3d1360e11b81565b6040516001600160e01b03199091168152602001620001eb565b3480156200028a575f80fd5b50620002a26200029c36600462002b78565b620009d9565b005b348015620002b0575f80fd5b50609754620002c5906001600160a01b031681565b6040516001600160a01b039091168152602001620001eb565b620002a2620002ef36600462002b96565b62000acb565b34801562000301575f80fd5b50620002c56200031336600462002bed565b62000c5f565b34801562000325575f80fd5b50620002a262000c77565b620002a26200034136600462002c31565b62000cfa565b34801562000353575f80fd5b506200021362000dd1565b3480156200036a575f80fd5b50620002646380ac58cd60e01b81565b34801562000386575f80fd5b50620001df60c954610100900460ff1660021490565b348015620003a8575f80fd5b5062000213600a81565b348015620003be575f80fd5b50620002c5620003d036600462002c83565b61012e60209081525f92835260408084209091529082529020546001600160a01b031681565b34801562000402575f80fd5b50620002a262000e86565b34801562000419575f80fd5b50620002a262000e9b565b620002a26200043536600462002cfb565b62000f16565b34801562000447575f80fd5b50620002a262001089565b3480156200045e575f80fd5b506033546001600160a01b0316620002c5565b3480156200047d575f80fd5b50620004956200048f36600462002b78565b6200110c565b604051620001eb949392919062002d3d565b348015620004b3575f80fd5b50620002c5620004c536600462002d90565b62001267565b348015620004d7575f80fd5b5062000264620004e936600462002df9565b63bc197c8160e01b98975050505050505050565b34801562000509575f80fd5b506065546001600160a01b0316620002c5565b34801562000528575f80fd5b50620002a26200053a36600462002ebd565b62001275565b3480156200054c575f80fd5b50620002646200055e36600462002eee565b63f23a6e6160e01b9695505050505050565b3480156200057c575f80fd5b50620002a26200058e36600462002b78565b6200138d565b5f6001600160e01b031982166301ffc9a760e01b1480620005c55750631e37aef160e11b6001600160e01b03198316145b92915050565b604080516101a0810182525f8082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e0820183905261010082018390526101208201839052610140820192909252610160810182905261018081019190915260026200064460c95460ff1690565b60ff1603620006665760405163dfc60d8560e01b815260040160405180910390fd5b62000672600262001401565b6200068760c954610100900460ff1660021490565b15620006a65760405163bae6e2a960e01b815260040160405180910390fd5b818060a001515181608001515114620006d25760405163196e8a4160e31b815260040160405180910390fd5b600a8160800151511115620006fa5760405163e4a4c1c760e01b815260040160405180910390fd5b60608101516001600160a01b031662000726576040516303f8a7d360e01b815260040160405180910390fd5b5f5b8360a001515181101562000780578360a0015181815181106200074f576200074f62002f6c565b60200260200101515f036200077757604051634299323b60e11b815260040160405180910390fd5b60010162000728565b506060830151620007a2906001600160a01b0316636cdb3d1360e11b62001417565b620007c057604051633ee915f560e11b815260040160405180910390fd5b5f80620007ce3386620014aa565b604080516101a0810182525f808252602080830182905292820181905289516001600160401b03166060830152336080830152918901519395509193509160a08201906001600160a01b03166200082657336200082c565b87602001515b6001600160a01b0316815260200162000861885f01516200085a6c195c98cc4c4d4d57dd985d5b1d609a1b90565b5f62000c5f565b6001600160a01b031681526020018761010001516001600160a01b031681526020018760e001513462000895919062002f80565b81526020018760e0015181526020018760c00151815260200184815260200187610120015181525090505f620008d56562726964676560d01b5f62001267565b6001600160a01b0316636c334e2e34846040518363ffffffff1660e01b815260040162000903919062002a52565b5f6040518083038185885af11580156200091f573d5f803e3d5ffd5b50505050506040513d5f823e601f3d908101601f191682016040526200094991908101906200301a565b809750819250505086604001516001600160a01b031686608001516001600160a01b0316827fabbf62a1459339f9ac59136d313a5ccd83d2706cc6d4c04d90642520169144dc896060015187602001518c606001518d608001518e60a00151604051620009bb959493929190620031b5565b60405180910390a45050505050620009d4600162001401565b919050565b6001600160a01b037f000000000000000000000000016700900000000000000000000000000000000416300362000a2d5760405162461bcd60e51b815260040162000a249062003211565b60405180910390fd5b7f00000000000000000000000001670090000000000000000000000000000000046001600160a01b031662000a775f8051602062003fd8833981519152546001600160a01b031690565b6001600160a01b03161462000aa05760405162461bcd60e51b815260040162000a24906200325d565b62000aab81620019fc565b604080515f8082526020820190925262000ac89183919062001a06565b50565b600262000ada60c95460ff1690565b60ff160362000afc5760405163dfc60d8560e01b815260040160405180910390fd5b62000b08600262001401565b62000b1d60c954610100900460ff1660021490565b1562000b3c5760405163bae6e2a960e01b815260040160405180910390fd5b62000b4662001b7d565b505f62000b58610160840184620032a9565b62000b68916004908290620032ee565b81019062000b77919062003317565b90505f805f8380602001905181019062000b929190620033af565b94509450505092505f62000bbd8488608001602081019062000bb5919062002b78565b858562001c74565b905062000beb61010088013562000bdb60a08a0160808b0162002b78565b6001600160a01b03169062001d70565b62000bfd60a088016080890162002b78565b6001600160a01b0316867fe48bef18455e47bca14864ab6e82dffa29df148b051c09de95aec44ecf13598c866020015184878760405162000c429493929190620034df565b60405180910390a3505050505062000c5b600162001401565b5050565b5f62000c6d84848462001d7d565b90505b9392505050565b62000c8c60c954610100900460ff1660021490565b62000caa5760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166101001790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a162000cf8335f62001e70565b565b6001600160a01b037f000000000000000000000000016700900000000000000000000000000000000416300362000d455760405162461bcd60e51b815260040162000a249062003211565b7f00000000000000000000000001670090000000000000000000000000000000046001600160a01b031662000d8f5f8051602062003fd8833981519152546001600160a01b031690565b6001600160a01b03161462000db85760405162461bcd60e51b815260040162000a24906200325d565b62000dc382620019fc565b62000c5b8282600162001a06565b5f306001600160a01b037f0000000000000000000000000167009000000000000000000000000000000004161462000e725760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c0000000000000000606482015260840162000a24565b505f8051602062003fd88339815191525b90565b62000e9062001e75565b62000cf85f62001ed1565b60655433906001600160a01b0316811462000f0b5760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b606482015260840162000a24565b62000ac88162001ed1565b600262000f2560c95460ff1690565b60ff160362000f475760405163dfc60d8560e01b815260040160405180910390fd5b62000f53600262001401565b62000f6860c954610100900460ff1660021490565b1562000f875760405163bae6e2a960e01b815260040160405180910390fd5b5f8080808062000f9a8688018862003520565b945094509450945094505f62000faf62001eec565b90506001600160a01b038416158062000fd057506001600160a01b03841630145b1562000fef5760405163def9481360e01b815260040160405180910390fd5b5f62000ffe8786868662001c74565b9050620010156001600160a01b0386163462001d70565b846001600160a01b0316866001600160a01b0316835f01517f895f73e418d1bbbad2a311d085fad00e5d98a960e9f2afa4b942071d39bec43a85604001518b60200151868a8a6040516200106e959493929190620031b5565b60405180910390a45050505050505062000c5b600162001401565b6200109e60c954610100900460ff1660021490565b15620010bd5760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a162000cf833600162001e70565b61012d6020525f9081526040902080546001820180546001600160401b03831693600160401b9093046001600160a01b03169291906200114c9062003643565b80601f01602080910402602001604051908101604052809291908181526020018280546200117a9062003643565b8015620011c95780601f106200119f57610100808354040283529160200191620011c9565b820191905f5260205f20905b815481529060010190602001808311620011ab57829003601f168201915b505050505090806002018054620011e09062003643565b80601f01602080910402602001604051908101604052809291908181526020018280546200120e9062003643565b80156200125d5780601f1062001233576101008083540402835291602001916200125d565b820191905f5260205f20905b8154815290600101906020018083116200123f57829003601f168201915b5050505050905084565b5f62000c7046848462001d7d565b5f54610100900460ff16158080156200129457505f54600160ff909116105b80620012af5750303b158015620012af57505f5460ff166001145b620013145760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840162000a24565b5f805460ff19166001179055801562001336575f805461ff0019166101001790555b62001342838362002013565b801562001388575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050565b6200139762001e75565b606580546001600160a01b0383166001600160a01b03199091168117909155620013c96033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60c9805460ff191660ff92909216919091179055565b5f6001600160a01b0383163b6200143057505f620005c5565b6040516301ffc9a760e01b81526001600160e01b0319831660048201526001600160a01b038416906301ffc9a790602401602060405180830381865afa9250505080156200149d575060408051601f3d908101601f191682019092526200149a918101906200367d565b60015b15620005c5579392505050565b604080516080810182525f8082526020820152606091810182905280820182905260608301516001600160a01b039081165f90815261012d6020526040902054600160401b90041615620017565760608301516001600160a01b039081165f90815261012d6020908152604091829020825160808101845281546001600160401b0381168252600160401b9004909416918401919091526001810180549192840191620015579062003643565b80601f0160208091040260200160405190810160405280929190818152602001828054620015859062003643565b8015620015d45780601f10620015aa57610100808354040283529160200191620015d4565b820191905f5260205f20905b815481529060010190602001808311620015b657829003601f168201915b50505050508152602001600282018054620015ef9062003643565b80601f01602080910402602001604051908101604052809291908181526020018280546200161d9062003643565b80156200166c5780601f1062001642576101008083540402835291602001916200166c565b820191905f5260205f20905b8154815290600101906020018083116200164e57829003601f168201915b50505050508152505090505f5b8360800151518110156200174f5783606001516001600160a01b031663f5298aca8686608001518481518110620016b457620016b462002f6c565b60200260200101518760a001518581518110620016d557620016d562002f6c565b60209081029190910101516040516001600160e01b031960e086901b1681526001600160a01b039093166004840152602483019190915260448201526064015f604051808303815f87803b1580156200172c575f80fd5b505af11580156200173f573d5f803e3d5ffd5b5050505080600101905062001679565b506200197a565b6040518060800160405280466001600160401b0316815260200184606001516001600160a01b0316815260200160405180602001604052805f815250815260200160405180602001604052805f81525081525090505f83606001519050806001600160a01b03166306fdde036040518163ffffffff1660e01b81526004015f60405180830381865afa9250505080156200181357506040513d5f823e601f3d908101601f191682016040526200181091908101906200369b565b60015b156200181f5760608301525b806001600160a01b03166395d89b416040518163ffffffff1660e01b81526004015f60405180830381865afa9250505080156200187f57506040513d5f823e601f3d908101601f191682016040526200187c91908101906200369b565b60015b156200188b5760408301525b5f5b846080015151811015620019775784606001516001600160a01b031663f242432a333088608001518581518110620018c957620018c962002f6c565b60200260200101518960a001518681518110620018ea57620018ea62002f6c565b60209081029190910101516040516001600160e01b031960e087901b1681526001600160a01b0394851660048201529390921660248401526044830152606482015260a060848201525f60a482015260c4015f604051808303815f87803b15801562001954575f80fd5b505af115801562001967573d5f803e3d5ffd5b505050508060010190506200188d565b50505b604080840151608085015160a086015192513093637f07c94793620019a79387938b9390602001620036d0565b60408051601f1981840301815290829052620019c69160240162003787565b604051602081830303815290604052915060e01b6020820180516001600160e01b03838183161783525050505091509250929050565b62000ac862001e75565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff161562001a3c5762001388836200207a565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa92505050801562001a99575060408051601f3d908101601f1916820190925262001a96918101906200379b565b60015b62001afe5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b606482015260840162000a24565b5f8051602062003fd8833981519152811462001b6f5760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b606482015260840162000a24565b506200138883838362002118565b604080516060810182525f808252602082018190529181019190915262001bae6562726964676560d01b5f62001267565b6001600160a01b0316336001600160a01b03161462001be057604051632583296b60e01b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa15801562001c1d573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001c439190620037b3565b60208101519091506001600160a01b0316331462000e8357604051632583296b60e01b815260040160405180910390fd5b5f46855f01516001600160401b03160362001cf857506020840151604051631759616b60e11b81526001600160a01b03821690632eb2c2d69062001cc390309088908890889060040162003821565b5f604051808303815f87803b15801562001cdb575f80fd5b505af115801562001cee573d5f803e3d5ffd5b5050505062001d68565b62001d038562002148565b60405163d81d0a1560e01b81529091506001600160a01b0382169063d81d0a159062001d38908790879087906004016200387e565b5f604051808303815f87803b15801562001d50575f80fd5b505af115801562001d63573d5f803e3d5ffd5b505050505b949350505050565b62000c5b82825a6200218d565b6097545f906001600160a01b031662001da957604051638ed88b2560e01b815260040160405180910390fd5b609754604051630a3dc4f360e21b81526001600160401b0386166004820152602481018590526001600160a01b03909116906328f713cc90604401602060405180830381865afa15801562001e00573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001e269190620038b7565b90508115801562001e3e57506001600160a01b038116155b1562000c7057604051632b0d65db60e01b81526001600160401b03851660048201526024810184905260440162000a24565b62000c5b5b6033546001600160a01b0316331462000cf85760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640162000a24565b606580546001600160a01b031916905562000ac881620021d4565b604080516060810182525f808252602082018190529181019190915262001f1d6562726964676560d01b5f62001267565b6001600160a01b0316336001600160a01b03161462001f4f57604051632583296b60e01b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa15801562001f8c573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001fb29190620037b3565b90505f62001fd682604001516200085a6c195c98cc4c4d4d57dd985d5b1d609a1b90565b9050806001600160a01b031682602001516001600160a01b0316146200200f57604051632583296b60e01b815260040160405180910390fd5b5090565b5f54610100900460ff166200203c5760405162461bcd60e51b815260040162000a2490620038d5565b620020478262002225565b6001600160a01b0381166200206f576040516375cabfef60e11b815260040160405180910390fd5b62000c5b8162002259565b6001600160a01b0381163b620020e95760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840162000a24565b5f8051602062003fd883398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b6200212383620022cd565b5f82511180620021305750805b1562001388576200214283836200230e565b50505050565b80516001600160401b03165f90815261012e60209081526040808320828501516001600160a01b0390811685529252909120541680620009d457620005c58262002336565b815f036200219a57505050565b620021b683838360405180602001604052805f81525062002522565b6200138857604051634c67134d60e11b815260040160405180910390fd5b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b620022476001600160a01b0382161562002240578162001ed1565b3362001ed1565b5060c9805461ff001916610100179055565b5f54610100900460ff16620022825760405162461bcd60e51b815260040162000a2490620038d5565b6001600160401b03461115620022ab5760405163a12e8fa960e01b815260040160405180910390fd5b609780546001600160a01b0319166001600160a01b0392909216919091179055565b620022d8816200207a565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606062000c70838360405180606001604052806027815260200162003ff86027913962002560565b5f806200234b6033546001600160a01b031690565b60975460208501518551604080880151606089015191516200237e96956001600160a01b03169493929060240162003920565b60408051601f198184030181529190526020810180516001600160e01b03166377c6257360e11b1790529050620023c86e627269646765645f6572633131353560881b5f62001267565b81604051620023d79062002680565b620023e492919062003985565b604051809103905ff080158015620023fe573d5f803e3d5ffd5b506001600160a01b038082165f90815261012d60209081526040918290208751815492890151909416600160401b026001600160e01b03199092166001600160401b039094169390931717825585015191935084916001820190620024649082620039f9565b50606082015160028201906200247b9082620039f9565b505083516001600160401b039081165f90815261012e6020908152604080832082890180516001600160a01b039081168652919093529281902080546001600160a01b03191688851690811790915591518851828a015160608b01519351949750919094169493909316927f44977f2d30fe1e3aee2c1476f2f95aaacaf34e44b9359c403da01fcc93fd751b9262002514929062003ac5565b60405180910390a450919050565b5f6001600160a01b0385166200254b57604051634c67134d60e11b815260040160405180910390fd5b5f80835160208501878988f195945050505050565b60605f80856001600160a01b0316856040516200257e919062003af6565b5f60405180830381855af49150503d805f8114620025b8576040519150601f19603f3d011682016040523d82523d5f602084013e620025bd565b606091505b5091509150620025d086838387620025da565b9695505050505050565b606083156200264d5782515f0362002645576001600160a01b0385163b620026455760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640162000a24565b508162001d68565b62001d688383815115620026645781518083602001fd5b8060405162461bcd60e51b815260040162000a24919062003787565b6104c48062003b1483390190565b5f602082840312156200269f575f80fd5b81356001600160e01b03198116811462000c70575f80fd5b634e487b7160e01b5f52604160045260245ffd5b60405161014081016001600160401b0381118282101715620026f157620026f1620026b7565b60405290565b6040516101a081016001600160401b0381118282101715620026f157620026f1620026b7565b604051608081016001600160401b0381118282101715620026f157620026f1620026b7565b604051601f8201601f191681016001600160401b03811182821017156200276d576200276d620026b7565b604052919050565b6001600160401b038116811462000ac8575f80fd5b8035620009d48162002775565b6001600160a01b038116811462000ac8575f80fd5b8035620009d48162002797565b5f6001600160401b03821115620027d457620027d4620026b7565b5060051b60200190565b5f82601f830112620027ee575f80fd5b81356020620028076200280183620027b9565b62002742565b8083825260208201915060208460051b87010193508684111562002829575f80fd5b602086015b848110156200284757803583529183019183016200282e565b509695505050505050565b5f6001600160401b038211156200286d576200286d620026b7565b50601f01601f191660200190565b5f82601f8301126200288b575f80fd5b81356200289c620028018262002852565b818152846020838601011115620028b1575f80fd5b816020850160208301375f918101602001919091529392505050565b5f60208284031215620028de575f80fd5b81356001600160401b0380821115620028f5575f80fd5b9083019061014082860312156200290a575f80fd5b62002914620026cb565b6200291f836200278a565b81526200292f60208401620027ac565b60208201526200294260408401620027ac565b60408201526200295560608401620027ac565b60608201526080830135828111156200296c575f80fd5b6200297a87828601620027de565b60808301525060a08301358281111562002992575f80fd5b620029a087828601620027de565b60a08301525060c083013560c082015260e083013560e0820152610100620029ca818501620027ac565b908201526101208381013583811115620029e2575f80fd5b620029f0888287016200287b565b918301919091525095945050505050565b5f5b8381101562002a1d57818101518382015260200162002a03565b50505f910152565b5f815180845262002a3e81602086016020860162002a01565b601f01601f19169290920160200192915050565b6020815262002a6d6020820183516001600160801b03169052565b5f602083015162002a8960408401826001600160a01b03169052565b5060408301516001600160401b03811660608401525060608301516001600160401b03811660808401525060808301516001600160a01b03811660a08401525060a08301516001600160a01b03811660c08401525060c08301516001600160a01b03811660e08401525060e083015161010062002b10818501836001600160a01b03169052565b8401516101208481019190915284015161014080850191909152840151610160808501919091528401516101a06101808086018290529192509062002b5a6101c086018462002a25565b90860151858203601f190183870152909250620025d0838262002a25565b5f6020828403121562002b89575f80fd5b813562000c708162002797565b5f806040838503121562002ba8575f80fd5b82356001600160401b0381111562002bbe575f80fd5b83016101a0818603121562002bd1575f80fd5b946020939093013593505050565b801515811462000ac8575f80fd5b5f805f6060848603121562002c00575f80fd5b833562002c0d8162002775565b925060208401359150604084013562002c268162002bdf565b809150509250925092565b5f806040838503121562002c43575f80fd5b823562002c508162002797565b915060208301356001600160401b0381111562002c6b575f80fd5b62002c79858286016200287b565b9150509250929050565b5f806040838503121562002c95575f80fd5b82359150602083013562002ca98162002797565b809150509250929050565b5f8083601f84011262002cc5575f80fd5b5081356001600160401b0381111562002cdc575f80fd5b60208301915083602082850101111562002cf4575f80fd5b9250929050565b5f806020838503121562002d0d575f80fd5b82356001600160401b0381111562002d23575f80fd5b62002d318582860162002cb4565b90969095509350505050565b6001600160401b03851681526001600160a01b03841660208201526080604082018190525f9062002d719083018562002a25565b828103606084015262002d85818562002a25565b979650505050505050565b5f806040838503121562002da2575f80fd5b82359150602083013562002ca98162002bdf565b5f8083601f84011262002dc7575f80fd5b5081356001600160401b0381111562002dde575f80fd5b6020830191508360208260051b850101111562002cf4575f80fd5b5f805f805f805f8060a0898b03121562002e11575f80fd5b883562002e1e8162002797565b9750602089013562002e308162002797565b965060408901356001600160401b038082111562002e4c575f80fd5b62002e5a8c838d0162002db6565b909850965060608b013591508082111562002e73575f80fd5b62002e818c838d0162002db6565b909650945060808b013591508082111562002e9a575f80fd5b5062002ea98b828c0162002cb4565b999c989b5096995094979396929594505050565b5f806040838503121562002ecf575f80fd5b823562002edc8162002797565b9150602083013562002ca98162002797565b5f805f805f8060a0878903121562002f04575f80fd5b863562002f118162002797565b9550602087013562002f238162002797565b9450604087013593506060870135925060808701356001600160401b0381111562002f4c575f80fd5b62002f5a89828a0162002cb4565b979a9699509497509295939492505050565b634e487b7160e01b5f52603260045260245ffd5b81810381811115620005c557634e487b7160e01b5f52601160045260245ffd5b80516001600160801b0381168114620009d4575f80fd5b8051620009d48162002797565b8051620009d48162002775565b5f82601f83011262002fe1575f80fd5b815162002ff2620028018262002852565b81815284602083860101111562003007575f80fd5b62001d6882602083016020870162002a01565b5f80604083850312156200302c575f80fd5b8251915060208301516001600160401b03808211156200304a575f80fd5b908401906101a082870312156200305f575f80fd5b62003069620026f7565b620030748362002fa0565b8152620030846020840162002fb7565b6020820152620030976040840162002fc4565b6040820152620030aa6060840162002fc4565b6060820152620030bd6080840162002fb7565b6080820152620030d060a0840162002fb7565b60a0820152620030e360c0840162002fb7565b60c0820152620030f660e0840162002fb7565b60e0820152610100838101519082015261012080840151908201526101408084015190820152610160808401518381111562003130575f80fd5b6200313e8982870162002fd1565b828401525050610180808401518381111562003158575f80fd5b620031668982870162002fd1565b8284015250508093505050509250929050565b5f815180845260208085019450602084015f5b83811015620031aa578151875295820195908201906001016200318c565b509495945050505050565b6001600160401b03861681526001600160a01b0385811660208301528416604082015260a0606082018190525f90620031f19083018562003179565b828103608084015262003205818562003179565b98975050505050505050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b5f808335601e19843603018112620032bf575f80fd5b8301803591506001600160401b03821115620032d9575f80fd5b60200191503681900382131562002cf4575f80fd5b5f8085851115620032fd575f80fd5b838611156200330a575f80fd5b5050820193919092039150565b5f6020828403121562003328575f80fd5b81356001600160401b038111156200333e575f80fd5b62001d68848285016200287b565b5f82601f8301126200335c575f80fd5b815160206200336f6200280183620027b9565b8083825260208201915060208460051b87010193508684111562003391575f80fd5b602086015b8481101562002847578051835291830191830162003396565b5f805f805f60a08688031215620033c4575f80fd5b85516001600160401b0380821115620033db575f80fd5b908701906080828a031215620033ef575f80fd5b620033f96200271d565b8251620034068162002775565b81526020830151620034188162002797565b60208201526040830151828111156200342f575f80fd5b6200343d8b82860162002fd1565b60408301525060608301518281111562003455575f80fd5b620034638b82860162002fd1565b6060830152509650620034796020890162002fb7565b9550620034896040890162002fb7565b945060608801519150808211156200349f575f80fd5b620034ad89838a016200334c565b93506080880151915080821115620034c3575f80fd5b50620034d2888289016200334c565b9150509295509295909350565b6001600160a01b038581168252841660208201526080604082018190525f906200350c9083018562003179565b828103606084015262002d85818562003179565b5f805f805f60a0868803121562003535575f80fd5b85356001600160401b03808211156200354c575f80fd5b908701906080828a03121562003560575f80fd5b6200356a6200271d565b8235620035778162002775565b81526020830135620035898162002797565b6020820152604083013582811115620035a0575f80fd5b620035ae8b8286016200287b565b604083015250606083013582811115620035c6575f80fd5b620035d48b8286016200287b565b6060830152509650620035ea60208901620027ac565b9550620035fa60408901620027ac565b9450606088013591508082111562003610575f80fd5b6200361e89838a01620027de565b9350608088013591508082111562003634575f80fd5b50620034d288828901620027de565b600181811c908216806200365857607f821691505b6020821081036200367757634e487b7160e01b5f52602260045260245ffd5b50919050565b5f602082840312156200368e575f80fd5b815162000c708162002bdf565b5f60208284031215620036ac575f80fd5b81516001600160401b03811115620036c2575f80fd5b62001d688482850162002fd1565b60a080825286516001600160401b03169082015260208601516001600160a01b031660c08201526040860151608060e08301525f906200371561012084018262002a25565b90506060880151609f198483030161010085015262003735828262002a25565b9150506200374e60208401886001600160a01b03169052565b6001600160a01b0386166040840152828103606084015262003771818662003179565b9050828103608084015262003205818562003179565b602081525f62000c70602083018462002a25565b5f60208284031215620037ac575f80fd5b5051919050565b5f60608284031215620037c4575f80fd5b604051606081018181106001600160401b0382111715620037e957620037e9620026b7565b604052825181526020830151620038008162002797565b60208201526040830151620038158162002775565b60408201529392505050565b6001600160a01b0385811682528416602082015260a0604082018190525f906200384e9083018562003179565b828103606084015262003862818562003179565b83810360809094019390935250505f8152602001949350505050565b6001600160a01b03841681526060602082018190525f90620038a39083018562003179565b8281036040840152620025d0818562003179565b5f60208284031215620038c8575f80fd5b815162000c708162002797565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6001600160a01b0387811682528681166020830152851660408201526001600160401b038416606082015260c0608082018190525f90620039649083018562002a25565b82810360a084015262003978818562002a25565b9998505050505050505050565b6001600160a01b03831681526040602082018190525f9062000c6d9083018462002a25565b601f8211156200138857805f5260205f20601f840160051c81016020851015620039d15750805b601f840160051c820191505b81811015620039f2575f8155600101620039dd565b5050505050565b81516001600160401b0381111562003a155762003a15620026b7565b62003a2d8162003a26845462003643565b84620039aa565b602080601f83116001811462003a63575f841562003a4b5750858301515b5f19600386901b1c1916600185901b17855562003abd565b5f85815260208120601f198616915b8281101562003a935788860151825594840194600190910190840162003a72565b508582101562003ab157878501515f19600388901b60f8161c191681555b505060018460011b0185555b505050505050565b604081525f62003ad9604083018562002a25565b828103602084015262003aed818562002a25565b95945050505050565b5f825162003b0981846020870162002a01565b919091019291505056fe60806040526040516104c43803806104c4833981016040819052610022916102d2565b61002d82825f610034565b50506103e7565b61003d8361005f565b5f825111806100495750805b1561005a57610058838361009e565b505b505050565b610068816100ca565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606100c3838360405180606001604052806027815260200161049d6027913961017d565b9392505050565b6001600160a01b0381163b61013c5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80856001600160a01b031685604051610199919061039a565b5f60405180830381855af49150503d805f81146101d1576040519150601f19603f3d011682016040523d82523d5f602084013e6101d6565b606091505b5090925090506101e8868383876101f2565b9695505050505050565b606083156102605782515f03610259576001600160a01b0385163b6102595760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610133565b508161026a565b61026a8383610272565b949350505050565b8151156102825781518083602001fd5b8060405162461bcd60e51b815260040161013391906103b5565b634e487b7160e01b5f52604160045260245ffd5b5f5b838110156102ca5781810151838201526020016102b2565b50505f910152565b5f80604083850312156102e3575f80fd5b82516001600160a01b03811681146102f9575f80fd5b60208401519092506001600160401b0380821115610315575f80fd5b818501915085601f830112610328575f80fd5b81518181111561033a5761033a61029c565b604051601f8201601f19908116603f011681019083821181831017156103625761036261029c565b8160405282815288602084870101111561037a575f80fd5b61038b8360208301602088016102b0565b80955050505050509250929050565b5f82516103ab8184602087016102b0565b9190910192915050565b602081525f82518060208401526103d38160408501602087016102b0565b601f01601f19169190910160400192915050565b60aa806103f35f395ff3fe608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156070573d5ff35b3d5ffdfea2646970667358221220ee2b34631d0aeafcbca8c09b3d006837becb1e761da7ef1dfde69d83e98a206164736f6c63430008180033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122099dfd2e82e6430ef2c3892d351d538d37e465b5b3e67e6272faa4f53b60e26a964736f6c63430008180033", - "balance": "0x0" - }, - "0x1670090000000000000000000000000000000004": { - "contractName": "ERC1155Vault", - "storage": { - "0x00000000000000000000000000000000000000000000000000000000000000c9": "0x0000000000000000000000000000000000000000000000000000000000000101", - "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190", - "0x0000000000000000000000000000000000000000000000000000000000000097": "0x0000000000000000000000001670090000000000000000000000000000000006", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0167009000000000000000000000000000000004" - }, - "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156070573d5ff35b3d5ffdfea2646970667358221220ee2b34631d0aeafcbca8c09b3d006837becb1e761da7ef1dfde69d83e98a206164736f6c63430008180033", - "balance": "0x0" - }, - "0x0167009000000000000000000000000000010096": { - "contractName": "BridgedERC20", - "storage": {}, - "code": "0x60806040526004361061030d575f3560e01c806370a08231116101a3578063981b24d0116100f2578063bb86ef9311610092578063dd62ed3e1161006d578063dd62ed3e14610940578063e30c39781461095f578063f1127ed81461097c578063f2fde38b146109c5575f80fd5b8063bb86ef93146108e3578063c3cda52014610902578063d505accf14610921575f80fd5b8063a457c2d7116100cd578063a457c2d714610867578063a86f9d9e14610886578063a9059cbb146108a5578063b8f2e0c5146108c4575f80fd5b8063981b24d01461080a5780639ab24eb0146108295780639dc29fac14610848575f80fd5b80638456cb591161015d5780638e539e8c116101385780638e539e8c1461079857806391ddadf4146107b757806395d89b41146107e25780639711715a146107f6575f80fd5b80638456cb591461074957806384b0196e1461075d5780638da5cb5b14610784575f80fd5b806370a082311461068e578063715018a6146106c357806379ba5097146106d75780637cf8ed0d146106eb5780637e4746341461070b5780637ecebe001461072a575f80fd5b80633ab76e9f1161025f5780634ee2cd7e11610219578063587cde1e116101f4578063587cde1e146105e35780635c19a95c1461061b5780635c975abb1461063a5780636fcfff451461065a575f80fd5b80634ee2cd7e1461059d5780634f1ef286146105bc57806352d1902d146105cf575f80fd5b80633ab76e9f146105025780633eb6b8cf146105215780633f4ba83a1461054057806340c10f191461055457806349d12605146105735780634bf5d7e914610589575f80fd5b80632e74eb2d116102ca5780633659cfe6116102a55780633659cfe61461046d57806337fbe1121461048c57806339509351146104c45780633a46b1a8146104e3575f80fd5b80632e74eb2d1461040c578063313ce5671461042d5780633644e51514610459575f80fd5b806306fdde0314610311578063095ea7b31461033b5780630ae745481461036a57806318160ddd1461038a57806323b872dd146103a957806326afaadd146103c8575b5f80fd5b34801561031c575f80fd5b506103256109e4565b6040516103329190613c7f565b60405180910390f35b348015610346575f80fd5b5061035a610355366004613ca5565b6109ff565b6040519015158152602001610332565b348015610375575f80fd5b5060fb5461035a90600160a01b900460ff1681565b348015610395575f80fd5b5061012f545b604051908152602001610332565b3480156103b4575f80fd5b5061035a6103c3366004613ccf565b610a18565b3480156103d3575f80fd5b506103ed61022a5461022b546001600160a01b0390911691565b604080516001600160a01b039093168352602083019190915201610332565b348015610417575f80fd5b5061042b610426366004613d0d565b610a3d565b005b348015610438575f80fd5b5061022a54600160a01b900460ff1660405160ff9091168152602001610332565b348015610464575f80fd5b5061039b610a68565b348015610478575f80fd5b5061042b610487366004613d0d565b610a71565b348015610497575f80fd5b5061022c546104ac906001600160a01b031681565b6040516001600160a01b039091168152602001610332565b3480156104cf575f80fd5b5061035a6104de366004613ca5565b610b57565b3480156104ee575f80fd5b5061039b6104fd366004613ca5565b610b78565b34801561050d575f80fd5b506097546104ac906001600160a01b031681565b34801561052c575f80fd5b506104ac61053b366004613d37565b610bf5565b34801561054b575f80fd5b5061042b610c09565b34801561055f575f80fd5b5061042b61056e366004613ca5565b610c88565b34801561057e575f80fd5b5061039b61022b5481565b348015610594575f80fd5b50610325610dd6565b3480156105a8575f80fd5b5061039b6105b7366004613ca5565b610e6e565b61042b6105ca366004613e05565b610ec7565b3480156105da575f80fd5b5061039b610f92565b3480156105ee575f80fd5b506104ac6105fd366004613d0d565b6001600160a01b039081165f9081526101f860205260409020541690565b348015610626575f80fd5b5061042b610635366004613d0d565b611043565b348015610645575f80fd5b5061035a60c954610100900460ff1660021490565b348015610665575f80fd5b50610679610674366004613d0d565b61104d565b60405163ffffffff9091168152602001610332565b348015610699575f80fd5b5061039b6106a8366004613d0d565b6001600160a01b03165f90815261012d602052604090205490565b3480156106ce575f80fd5b5061042b61106f565b3480156106e2575f80fd5b5061042b611080565b3480156106f6575f80fd5b5061022a546104ac906001600160a01b031681565b348015610716575f80fd5b5060fb546104ac906001600160a01b031681565b348015610735575f80fd5b5061039b610744366004613d0d565b6110f7565b348015610754575f80fd5b5061042b611115565b348015610768575f80fd5b50610771611194565b6040516103329796959493929190613e65565b34801561078f575f80fd5b506104ac61122f565b3480156107a3575f80fd5b5061039b6107b2366004613efc565b611242565b3480156107c2575f80fd5b506107cb6112a9565b60405165ffffffffffff9091168152602001610332565b3480156107ed575f80fd5b506103256112b3565b348015610801575f80fd5b5061039b6112c5565b348015610815575f80fd5b5061039b610824366004613efc565b611321565b348015610834575f80fd5b5061039b610843366004613d0d565b611342565b348015610853575f80fd5b5061042b610862366004613ca5565b6113c1565b348015610872575f80fd5b5061035a610881366004613ca5565b611560565b348015610891575f80fd5b506104ac6108a0366004613f13565b6115e5565b3480156108b0575f80fd5b5061035a6108bf366004613ca5565b6115f1565b3480156108cf575f80fd5b5061042b6108de366004613f3d565b6115fe565b3480156108ee575f80fd5b5061042b6108fd366004613f95565b61179b565b34801561090d575f80fd5b5061042b61091c366004614044565b61190d565b34801561092c575f80fd5b5061042b61093b36600461409a565b611a42565b34801561094b575f80fd5b5061039b61095a366004614103565b611ba3565b34801561096a575f80fd5b506065546001600160a01b03166104ac565b348015610987575f80fd5b5061099b61099636600461413a565b611bce565b60408051825163ffffffff1681526020928301516001600160e01b03169281019290925201610332565b3480156109d0575f80fd5b5061042b6109df366004613d0d565b611c50565b60606109fa6109f1611cb6565b61022b54611d47565b905090565b5f33610a0c818585611d96565b60019150505b92915050565b5f33610a25858285611eba565b610a30858585611f32565b60019150505b9392505050565b610a456120ee565b61022c80546001600160a01b0319166001600160a01b0392909216919091179055565b5f6109fa61214d565b6001600160a01b037f0000000000000000000000000167009000000000000000000000000000010096163003610ac25760405162461bcd60e51b8152600401610ab99061416e565b60405180910390fd5b7f00000000000000000000000001670090000000000000000000000000000100966001600160a01b0316610b0a5f805160206144f6833981519152546001600160a01b031690565b6001600160a01b031614610b305760405162461bcd60e51b8152600401610ab9906141ba565b610b3981612156565b604080515f80825260208201909252610b549183919061215e565b50565b5f33610a0c818585610b698383611ba3565b610b73919061421a565b611d96565b5f610b816112a9565b65ffffffffffff168210610bd35760405162461bcd60e51b815260206004820152601960248201527804552433230566f7465733a20667574757265206c6f6f6b757603c1b6044820152606401610ab9565b6001600160a01b0383165f9081526101f960205260409020610a3690836122cd565b5f610c018484846123ae565b949350505050565b610c1d60c954610100900460ff1660021490565b610c3a5760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166101001790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610c86335f61249c565b565b6002610c9660c95460ff1690565b60ff1603610cb75760405163dfc60d8560e01b815260040160405180910390fd5b610cc160026124a4565b610cd560c954610100900460ff1660021490565b15610cf35760405163bae6e2a960e01b815260040160405180910390fd5b610cfb6124ba565b15610d195760405163b19aa30f60e01b815260040160405180910390fd5b60fb546001600160a01b03163303610d745760fb546040518281526001600160a01b038481169216907f638edf84937fb2534b47cac985ea84d6ea4f4076315b56ea1c784d26b87e2bcb9060200160405180910390a3610dbe565b610d8d6a195c98cc8c17dd985d5b1d60aa1b60016115e5565b6001600160a01b0316336001600160a01b031614610dbe576040516361fad54f60e11b815260040160405180910390fd5b610dc882826124e3565b610dd260016124a4565b5050565b606043610de16112a9565b65ffffffffffff1614610e365760405162461bcd60e51b815260206004820152601d60248201527f4552433230566f7465733a2062726f6b656e20636c6f636b206d6f64650000006044820152606401610ab9565b5060408051808201909152601d81527f6d6f64653d626c6f636b6e756d6265722666726f6d3d64656661756c74000000602082015290565b6001600160a01b0382165f90815261015f6020526040812081908190610e959085906124ed565b9150915081610ebc576001600160a01b0385165f90815261012d6020526040902054610ebe565b805b95945050505050565b6001600160a01b037f0000000000000000000000000167009000000000000000000000000000010096163003610f0f5760405162461bcd60e51b8152600401610ab99061416e565b7f00000000000000000000000001670090000000000000000000000000000100966001600160a01b0316610f575f805160206144f6833981519152546001600160a01b031690565b6001600160a01b031614610f7d5760405162461bcd60e51b8152600401610ab9906141ba565b610f8682612156565b610dd28282600161215e565b5f306001600160a01b037f000000000000000000000000016700900000000000000000000000000001009616146110315760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610ab9565b505f805160206144f683398151915290565b610b5433826125dd565b6001600160a01b0381165f9081526101f96020526040812054610a1290612658565b6110776120ee565b610c865f6126c0565b60655433906001600160a01b031681146110ee5760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b6064820152608401610ab9565b610b54816126c0565b6001600160a01b0381165f9081526101c56020526040812054610a12565b61112960c954610100900460ff1660021490565b156111475760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a1610c8633600161249c565b5f6060805f805f6060610191545f801b1480156111b2575061019254155b6111f65760405162461bcd60e51b81526020600482015260156024820152741152540dcc4c8e88155b9a5b9a5d1a585b1a5e9959605a1b6044820152606401610ab9565b6111fe6126d9565b6112066126e9565b604080515f80825260208201909252600f60f81b9b939a50919850469750309650945092509050565b5f6109fa6033546001600160a01b031690565b5f61124b6112a9565b65ffffffffffff16821061129d5760405162461bcd60e51b815260206004820152601960248201527804552433230566f7465733a20667574757265206c6f6f6b757603c1b6044820152606401610ab9565b610a126101fa836122cd565b5f6109fa436126f9565b60606109fa6112c061275f565b61276f565b5f6112ce61122f565b6001600160a01b0316336001600160a01b0316141580156112fb575061022c546001600160a01b03163314155b1561131957604051630b1d89d360e41b815260040160405180910390fd5b6109fa6127b8565b5f805f611330846101606124ed565b9150915081610a365761012f54610c01565b6001600160a01b0381165f9081526101f9602052604081205480156113af576001600160a01b0383165f9081526101f96020526040902080545f19830190811061138e5761138e61422d565b5f9182526020909120015464010000000090046001600160e01b03166113b1565b5f5b6001600160e01b03169392505050565b60026113cf60c95460ff1690565b60ff16036113f05760405163dfc60d8560e01b815260040160405180910390fd5b6113fa60026124a4565b61140e60c954610100900460ff1660021490565b1561142c5760405163bae6e2a960e01b815260040160405180910390fd5b6114346124ba565b1561150c57336001600160a01b03831614611462576040516361fad54f60e11b815260040160405180910390fd5b60fb546040518281526001600160a01b038481169216907f638edf84937fb2534b47cac985ea84d6ea4f4076315b56ea1c784d26b87e2bcb9060200160405180910390a360fb546040516340c10f1960e01b81526001600160a01b03848116600483015260248201849052909116906340c10f19906044015f604051808303815f87803b1580156114f1575f80fd5b505af1158015611503573d5f803e3d5ffd5b50505050611556565b6115256a195c98cc8c17dd985d5b1d60aa1b60016115e5565b6001600160a01b0316336001600160a01b03161461155657604051630d85cccf60e11b815260040160405180910390fd5b610dc88282612811565b5f338161156d8286611ba3565b9050838110156115cd5760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608401610ab9565b6115da8286868403611d96565b506001949350505050565b5f610a364684846123ae565b5f33610a0c818585611f32565b600261160c60c95460ff1690565b60ff160361162d5760405163dfc60d8560e01b815260040160405180910390fd5b61163760026124a4565b61164b60c954610100900460ff1660021490565b156116695760405163bae6e2a960e01b815260040160405180910390fd5b6a195c98cc8c17dd985d5b1d60aa1b61168061122f565b6001600160a01b0316336001600160a01b0316141580156116bd57506116a78160016115e5565b6001600160a01b0316336001600160a01b031614155b156116db57604051630d85cccf60e11b815260040160405180910390fd5b60fb546001600160a01b03848116911614801561170a575060fb60149054906101000a900460ff161515821515145b156117285760405163b253fdfd60e01b815260040160405180910390fd5b60fb80546001600160a01b0385166001600160a81b03199091168117600160a01b851515908102919091179092556040805191825260208201929092527fa6b6f959792843a48d9d03d13595f2de7c86ae0ce12ef0fa759dd911b205e565910160405180910390a150610dd260016124a4565b5f54610100900460ff16158080156117b957505f54600160ff909116105b806117d25750303b1580156117d257505f5460ff166001145b6118355760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610ab9565b5f805460ff191660011790558015611856575f805461ff0019166101001790555b6118628686858561281b565b61186c8888612850565b61187682846128af565b61187e6128df565b6118866128df565b61188f82612905565b61022a805461022b87905560ff8616600160a01b026001600160a81b03199091166001600160a01b038916171790558015611903575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b8342111561195d5760405162461bcd60e51b815260206004820152601d60248201527f4552433230566f7465733a207369676e617475726520657870697265640000006044820152606401610ab9565b604080517fe48329057bfd03d55e49b547132e39cffd9c1820ad7b9d4c5307691425d15adf60208201526001600160a01b0388169181019190915260608101869052608081018590525f906119d6906119ce9060a0016040516020818303038152906040528051906020012061294e565b85858561297a565b90506119e1816129a0565b8614611a2f5760405162461bcd60e51b815260206004820152601960248201527f4552433230566f7465733a20696e76616c6964206e6f6e6365000000000000006044820152606401610ab9565b611a3981886125dd565b50505050505050565b83421115611a925760405162461bcd60e51b815260206004820152601d60248201527f45524332305065726d69743a206578706972656420646561646c696e650000006044820152606401610ab9565b5f7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9888888611ac08c6129a0565b6040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810186905260e0016040516020818303038152906040528051906020012090505f611b1a8261294e565b90505f611b298287878761297a565b9050896001600160a01b0316816001600160a01b031614611b8c5760405162461bcd60e51b815260206004820152601e60248201527f45524332305065726d69743a20696e76616c6964207369676e617475726500006044820152606401610ab9565b611b978a8a8a611d96565b50505050505050505050565b6001600160a01b039182165f90815261012e6020908152604080832093909416825291909152205490565b604080518082019091525f80825260208201526001600160a01b0383165f9081526101f960205260409020805463ffffffff8416908110611c1157611c1161422d565b5f9182526020918290206040805180820190915291015463ffffffff8116825264010000000090046001600160e01b0316918101919091529392505050565b611c586120ee565b606580546001600160a01b0319166001600160a01b038316908117909155611c7e61122f565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60606101308054611cc690614241565b80601f0160208091040260200160405190810160405280929190818152602001828054611cf290614241565b8015611d3d5780601f10611d1457610100808354040283529160200191611d3d565b820191905f5260205f20905b815481529060010190602001808311611d2057829003601f168201915b5050505050905090565b606082515f03611d65575060408051602081019091525f8152610a12565b82611d6f836129c8565b604051602001611d80929190614273565b6040516020818303038152906040529050610a12565b6001600160a01b038316611df85760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610ab9565b6001600160a01b038216611e595760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610ab9565b6001600160a01b038381165f81815261012e602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b5f611ec58484611ba3565b90505f198114611f2c5781811015611f1f5760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610ab9565b611f2c8484848403611d96565b50505050565b6001600160a01b038316611f965760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610ab9565b6001600160a01b038216611ff85760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610ab9565b612003838383612a58565b6001600160a01b0383165f90815261012d60205260409020548181101561207b5760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610ab9565b6001600160a01b038085165f81815261012d602052604080822086860390559286168082529083902080548601905591517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906120db9086815260200190565b60405180910390a3611f2c848484612abe565b336120f761122f565b6001600160a01b031614610c865760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610ab9565b5f6109fa612ac9565b610b546120ee565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156121965761219183612b3c565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156121f0575060408051601f3d908101601f191682019092526121ed918101906142d3565b60015b6122535760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610ab9565b5f805160206144f683398151915281146122c15760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610ab9565b50612191838383612bd7565b81545f9081816005811115612324575f6122e684612bfb565b6122f090856142ea565b5f88815260209020909150869082015463ffffffff16111561231457809150612322565b61231f81600161421a565b92505b505b8082101561236f575f6123378383612cdf565b5f88815260209020909150869082015463ffffffff16111561235b57809150612369565b61236681600161421a565b92505b50612324565b8015612399575f8681526020902081015f19015464010000000090046001600160e01b031661239b565b5f5b6001600160e01b03169695505050505050565b6097545f906001600160a01b03166123d957604051638ed88b2560e01b815260040160405180910390fd5b609754604051630a3dc4f360e21b815267ffffffffffffffff86166004820152602481018590526001600160a01b03909116906328f713cc90604401602060405180830381865afa158015612430573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061245491906142fd565b90508115801561246b57506001600160a01b038116155b15610a3657604051632b0d65db60e01b815267ffffffffffffffff8516600482015260248101849052604401610ab9565b610dd26120ee565b60c9805460ff191660ff92909216919091179055565b60fb545f906001600160a01b0316158015906109fa57505060fb54600160a01b900460ff161590565b610dd28282612cf9565b5f805f84116125375760405162461bcd60e51b815260206004820152601660248201527504552433230536e617073686f743a20696420697320360541b6044820152606401610ab9565b61253f612d03565b84111561258e5760405162461bcd60e51b815260206004820152601d60248201527f4552433230536e617073686f743a206e6f6e6578697374656e742069640000006044820152606401610ab9565b5f6125998486612d0e565b845490915081036125b0575f8092509250506125d6565b60018460010182815481106125c7576125c761422d565b905f5260205f20015492509250505b9250929050565b6001600160a01b038281165f8181526101f860208181526040808420805461012d845282862054949093528787166001600160a01b03198416811790915590519190951694919391928592917f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f9190a4611f2c828483612db4565b5f63ffffffff8211156126bc5760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203360448201526532206269747360d01b6064820152608401610ab9565b5090565b606580546001600160a01b0319169055610b5481612ef0565b60606101938054611cc690614241565b60606101948054611cc690614241565b5f65ffffffffffff8211156126bc5760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203460448201526538206269747360d01b6064820152608401610ab9565b60606101318054611cc690614241565b606081515f0361278c57505060408051602081019091525f815290565b8160405160200161279d9190614318565b6040516020818303038152906040529050919050565b919050565b5f6127c861016280546001019055565b5f6127d1612d03565b90507f8030e83b04d87bef53480e26263266d6ca66863aa8506aca6f2559d18aa1cb678160405161280491815260200190565b60405180910390a1919050565b610dd28282612f41565b6128258484612f4b565b8151158061283257508051155b15611f2c5760405163c118d2f360e01b815260040160405180910390fd5b5f54610100900460ff166128765760405162461bcd60e51b8152600401610ab99061433d565b61287f82612f87565b6001600160a01b0381166128a6576040516375cabfef60e11b815260040160405180910390fd5b610dd281612fb7565b5f54610100900460ff166128d55760405162461bcd60e51b8152600401610ab99061433d565b610dd28282613028565b5f54610100900460ff16610c865760405162461bcd60e51b8152600401610ab99061433d565b5f54610100900460ff1661292b5760405162461bcd60e51b8152600401610ab99061433d565b610b5481604051806040016040528060018152602001603160f81b815250613069565b5f610a1261295a61214d565b8360405161190160f01b8152600281019290925260228201526042902090565b5f805f612989878787876130ba565b9150915061299681613177565b5095945050505050565b6001600160a01b0381165f9081526101c5602052604090208054600181018255905b50919050565b60605f6129d4836132c0565b60010190505f8167ffffffffffffffff8111156129f3576129f3613d7e565b6040519080825280601f01601f191660200182016040528015612a1d576020820181803683370190505b5090508181016020015b5f19016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a8504945084612a2757509392505050565b306001600160a01b03831603612a8157604051630183150560e21b815260040160405180910390fd5b612a9560c954610100900460ff1660021490565b15612ab35760405163bae6e2a960e01b815260040160405180910390fd5b612191838383613397565b6121918383836133df565b5f7f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f612af3613411565b612afb61346a565b60408051602081019490945283019190915260608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b6001600160a01b0381163b612ba95760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610ab9565b5f805160206144f683398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b612be08361349b565b5f82511180612bec5750805b1561219157611f2c83836134da565b5f815f03612c0a57505f919050565b5f6001612c16846134ff565b901c6001901b90506001818481612c2f57612c2f614388565b048201901c90506001818481612c4757612c47614388565b048201901c90506001818481612c5f57612c5f614388565b048201901c90506001818481612c7757612c77614388565b048201901c90506001818481612c8f57612c8f614388565b048201901c90506001818481612ca757612ca7614388565b048201901c90506001818481612cbf57612cbf614388565b048201901c9050610a3681828581612cd957612cd9614388565b04613592565b5f612ced600284841861439c565b610a369084841661421a565b610dd282826135a7565b5f6109fa6101625490565b81545f908103612d1f57505f610a12565b82545f905b80821015612d69575f612d378383612cdf565b5f8781526020902090915085908201541115612d5557809150612d63565b612d6081600161421a565b92505b50612d24565b5f82118015612d93575083612d9086612d836001866142ea565b5f91825260209091200190565b54145b15612dac57612da36001836142ea565b92505050610a12565b509050610a12565b816001600160a01b0316836001600160a01b031614158015612dd557505f81115b15612191576001600160a01b03831615612e63576001600160a01b0383165f9081526101f9602052604081208190612e10906136338561363e565b91509150846001600160a01b03167fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7248383604051612e58929190918252602082015260400190565b60405180910390a250505b6001600160a01b03821615612191576001600160a01b0382165f9081526101f9602052604081208190612e99906137aa8561363e565b91509150836001600160a01b03167fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7248383604051612ee1929190918252602082015260400190565b60405180910390a25050505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b610dd282826137b5565b6001600160a01b0382161580612f5f575080155b80612f6957504681145b15610dd25760405163c118d2f360e01b815260040160405180910390fd5b612fa56001600160a01b03821615612f9f57816126c0565b336126c0565b5060c9805461ff001916610100179055565b5f54610100900460ff16612fdd5760405162461bcd60e51b8152600401610ab99061433d565b67ffffffffffffffff4611156130065760405163a12e8fa960e01b815260040160405180910390fd5b609780546001600160a01b0319166001600160a01b0392909216919091179055565b5f54610100900460ff1661304e5760405162461bcd60e51b8152600401610ab99061433d565b61013061305b8382614406565b506101316121918282614406565b5f54610100900460ff1661308f5760405162461bcd60e51b8152600401610ab99061433d565b61019361309c8382614406565b506101946130aa8282614406565b50505f6101918190556101925550565b5f807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08311156130ef57505f9050600361316e565b604080515f8082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa158015613140573d5f803e3d5ffd5b5050604051601f1901519150506001600160a01b038116613168575f6001925092505061316e565b91505f90505b94509492505050565b5f81600481111561318a5761318a6144c6565b036131925750565b60018160048111156131a6576131a66144c6565b036131f35760405162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e617475726500000000000000006044820152606401610ab9565b6002816004811115613207576132076144c6565b036132545760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e677468006044820152606401610ab9565b6003816004811115613268576132686144c6565b03610b545760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b6064820152608401610ab9565b5f8072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b83106132fe5772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef8100000000831061332a576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc10000831061334857662386f26fc10000830492506010015b6305f5e1008310613360576305f5e100830492506008015b612710831061337457612710830492506004015b60648310613386576064830492506002015b600a8310610a125760010192915050565b6001600160a01b0383166133b6576133ae826137ce565b612191613802565b6001600160a01b0382166133cd576133ae836137ce565b6133d6836137ce565b612191826137ce565b6001600160a01b038381165f9081526101f8602052604080822054858416835291205461219192918216911683612db4565b5f8061341b6126d9565b805190915015613432578051602090910120919050565b6101915480156134425792915050565b7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4709250505090565b5f806134746126e9565b80519091501561348b578051602090910120919050565b6101925480156134425792915050565b6134a481612b3c565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060610a36838360405180606001604052806027815260200161451660279139613812565b5f80608083901c1561351357608092831c92015b604083901c1561352557604092831c92015b602083901c1561353757602092831c92015b601083901c1561354957601092831c92015b600883901c1561355b57600892831c92015b600483901c1561356d57600492831c92015b600283901c1561357f57600292831c92015b600183901c15610a125760010192915050565b5f8183106135a05781610a36565b5090919050565b6135b18282613886565b61012f546001600160e01b0310156136245760405162461bcd60e51b815260206004820152603060248201527f4552433230566f7465733a20746f74616c20737570706c79207269736b73206f60448201526f766572666c6f77696e6720766f74657360801b6064820152608401610ab9565b611f2c6101fa6137aa8361363e565b5f610a3682846142ea565b82545f908190818115613688575f8781526020902082015f190160408051808201909152905463ffffffff8116825264010000000090046001600160e01b0316602082015261369c565b604080518082019091525f80825260208201525b905080602001516001600160e01b031693506136bc84868863ffffffff16565b92505f821180156136e457506136d06112a9565b65ffffffffffff16815f015163ffffffff16145b15613727576136f283613959565b5f8881526020902083015f190180546001600160e01b03929092166401000000000263ffffffff9092169190911790556137a0565b86604051806040016040528061374b61373e6112a9565b65ffffffffffff16612658565b63ffffffff16815260200161375f86613959565b6001600160e01b0390811690915282546001810184555f938452602093849020835194909301519091166401000000000263ffffffff909316929092179101555b5050935093915050565b5f610a36828461421a565b6137bf82826139c1565b611f2c6101fa6136338361363e565b6001600160a01b0381165f90815261015f6020908152604080832061012d90925290912054610b549190613b07565b613b07565b610c866101606137fd61012f5490565b60605f80856001600160a01b03168560405161382e91906144da565b5f60405180830381855af49150503d805f8114613866576040519150601f19603f3d011682016040523d82523d5f602084013e61386b565b606091505b509150915061387c86838387613b4f565b9695505050505050565b6001600160a01b0382166138dc5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610ab9565b6138e75f8383612a58565b8061012f5f8282546138f9919061421a565b90915550506001600160a01b0382165f81815261012d60209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3610dd25f8383612abe565b5f6001600160e01b038211156126bc5760405162461bcd60e51b815260206004820152602760248201527f53616665436173743a2076616c756520646f65736e27742066697420696e20326044820152663234206269747360c81b6064820152608401610ab9565b6001600160a01b038216613a215760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608401610ab9565b613a2c825f83612a58565b6001600160a01b0382165f90815261012d602052604090205481811015613aa05760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b6064820152608401610ab9565b6001600160a01b0383165f81815261012d60209081526040808320868603905561012f80548790039055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3612191835f84612abe565b5f613b10612d03565b905080613b1c84613bc7565b1015612191578254600180820185555f858152602080822090930193909355938401805494850181558252902090910155565b60608315613bbd5782515f03613bb6576001600160a01b0385163b613bb65760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610ab9565b5081610c01565b610c018383613c08565b80545f908103613bd857505f919050565b81548290613be8906001906142ea565b81548110613bf857613bf861422d565b905f5260205f2001549050919050565b815115613c185781518083602001fd5b8060405162461bcd60e51b8152600401610ab99190613c7f565b5f5b83811015613c4c578181015183820152602001613c34565b50505f910152565b5f8151808452613c6b816020860160208601613c32565b601f01601f19169290920160200192915050565b602081525f610a366020830184613c54565b6001600160a01b0381168114610b54575f80fd5b5f8060408385031215613cb6575f80fd5b8235613cc181613c91565b946020939093013593505050565b5f805f60608486031215613ce1575f80fd5b8335613cec81613c91565b92506020840135613cfc81613c91565b929592945050506040919091013590565b5f60208284031215613d1d575f80fd5b8135610a3681613c91565b803580151581146127b3575f80fd5b5f805f60608486031215613d49575f80fd5b833567ffffffffffffffff81168114613d60575f80fd5b925060208401359150613d7560408501613d28565b90509250925092565b634e487b7160e01b5f52604160045260245ffd5b5f67ffffffffffffffff80841115613dac57613dac613d7e565b604051601f8501601f19908116603f01168101908282118183101715613dd457613dd4613d7e565b81604052809350858152868686011115613dec575f80fd5b858560208301375f602087830101525050509392505050565b5f8060408385031215613e16575f80fd5b8235613e2181613c91565b9150602083013567ffffffffffffffff811115613e3c575f80fd5b8301601f81018513613e4c575f80fd5b613e5b85823560208401613d92565b9150509250929050565b60ff60f81b881681525f602060e06020840152613e8560e084018a613c54565b8381036040850152613e97818a613c54565b606085018990526001600160a01b038816608086015260a0850187905284810360c0860152855180825260208088019350909101905f5b81811015613eea57835183529284019291840191600101613ece565b50909c9b505050505050505050505050565b5f60208284031215613f0c575f80fd5b5035919050565b5f8060408385031215613f24575f80fd5b82359150613f3460208401613d28565b90509250929050565b5f8060408385031215613f4e575f80fd5b8235613f5981613c91565b9150613f3460208401613d28565b803560ff811681146127b3575f80fd5b5f82601f830112613f86575f80fd5b610a3683833560208501613d92565b5f805f805f805f60e0888a031215613fab575f80fd5b8735613fb681613c91565b96506020880135613fc681613c91565b95506040880135613fd681613c91565b945060608801359350613feb60808901613f67565b925060a088013567ffffffffffffffff80821115614007575f80fd5b6140138b838c01613f77565b935060c08a0135915080821115614028575f80fd5b506140358a828b01613f77565b91505092959891949750929550565b5f805f805f8060c08789031215614059575f80fd5b863561406481613c91565b9550602087013594506040870135935061408060608801613f67565b92506080870135915060a087013590509295509295509295565b5f805f805f805f60e0888a0312156140b0575f80fd5b87356140bb81613c91565b965060208801356140cb81613c91565b955060408801359450606088013593506140e760808901613f67565b925060a0880135915060c0880135905092959891949750929550565b5f8060408385031215614114575f80fd5b823561411f81613c91565b9150602083013561412f81613c91565b809150509250929050565b5f806040838503121561414b575f80fd5b823561415681613c91565b9150602083013563ffffffff8116811461412f575f80fd5b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b634e487b7160e01b5f52601160045260245ffd5b80820180821115610a1257610a12614206565b634e487b7160e01b5f52603260045260245ffd5b600181811c9082168061425557607f821691505b6020821081036129c257634e487b7160e01b5f52602260045260245ffd5b670213934b233b2b2160c51b81525f8351614295816008850160208801613c32565b634051c55b60df1b60089184019182015283516142b981600d840160208801613c32565b602960f81b600d9290910191820152600e01949350505050565b5f602082840312156142e3575f80fd5b5051919050565b81810381811115610a1257610a12614206565b5f6020828403121561430d575f80fd5b8151610a3681613c91565b5f8251614329818460208701613c32565b610b9d60f21b920191825250600201919050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b634e487b7160e01b5f52601260045260245ffd5b5f826143b657634e487b7160e01b5f52601260045260245ffd5b500490565b601f82111561219157805f5260205f20601f840160051c810160208510156143e05750805b601f840160051c820191505b818110156143ff575f81556001016143ec565b5050505050565b815167ffffffffffffffff81111561442057614420613d7e565b6144348161442e8454614241565b846143bb565b602080601f831160018114614467575f84156144505750858301515b5f19600386901b1c1916600185901b1785556144be565b5f85815260208120601f198616915b8281101561449557888601518255948401946001909101908401614476565b50858210156144b257878501515f19600388901b60f8161c191681555b505060018460011b0185555b505050505050565b634e487b7160e01b5f52602160045260245ffd5b5f82516144eb818460208701613c32565b919091019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220ec966d93736eefd7f412aea610ad43cc033d1d66fa81be3114e02cf05ae8001664736f6c63430008180033", - "balance": "0x0" - }, - "0x0167009000000000000000000000000000010097": { - "contractName": "BridgedERC721", - "storage": {}, - "code": "0x6080604052600436106101e6575f3560e01c806367e828bf116101085780639dc29fac1161009d578063c87b56dd1161006d578063c87b56dd1461055b578063e30c39781461057a578063e985e9c514610597578063ef8c4ae6146105df578063f2fde38b146105fe575f80fd5b80639dc29fac146104df578063a22cb465146104fe578063a86f9d9e1461051d578063b88d4fde1461053c575f80fd5b80637cf8ed0d116100d85780637cf8ed0d1461047a5780638456cb591461049a5780638da5cb5b146104ae57806395d89b41146104cb575f80fd5b806367e828bf146103ef57806370a0823114610433578063715018a61461045257806379ba509714610466575f80fd5b80633f4ba83a1161017e5780634f1ef2861161014e5780634f1ef2861461038957806352d1902d1461039c5780635c975abb146103b05780636352211e146103d0575f80fd5b80633f4ba83a1461031357806340c10f191461032757806342842e0e1461034657806349d1260514610365575f80fd5b806323b872dd116101b957806323b872dd146102975780633659cfe6146102b65780633ab76e9f146102d55780633eb6b8cf146102f4575f80fd5b806301ffc9a7146101ea57806306fdde031461021e578063081812fc1461023f578063095ea7b314610276575b5f80fd5b3480156101f5575f80fd5b5061020961020436600461245d565b61061d565b60405190151581526020015b60405180910390f35b348015610229575f80fd5b5061023261066e565b60405161021591906124c5565b34801561024a575f80fd5b5061025e6102593660046124d7565b610689565b6040516001600160a01b039091168152602001610215565b348015610281575f80fd5b50610295610290366004612502565b6106af565b005b3480156102a2575f80fd5b506102956102b136600461252c565b6107c8565b3480156102c1575f80fd5b506102956102d036600461256a565b6107f9565b3480156102e0575f80fd5b5060975461025e906001600160a01b031681565b3480156102ff575f80fd5b5061025e61030e366004612594565b6108d6565b34801561031e575f80fd5b506102956108ec565b348015610332575f80fd5b50610295610341366004612502565b61096b565b348015610351575f80fd5b5061029561036036600461252c565b610a3b565b348015610370575f80fd5b5061037b6101605481565b604051908152602001610215565b610295610397366004612678565b610a55565b3480156103a7575f80fd5b5061037b610b20565b3480156103bb575f80fd5b5061020960c954610100900460ff1660021490565b3480156103db575f80fd5b5061025e6103ea3660046124d7565b610bd1565b3480156103fa575f80fd5b5061041461015f54610160546001600160a01b0390911691565b604080516001600160a01b039093168352602083019190915201610215565b34801561043e575f80fd5b5061037b61044d36600461256a565b610c31565b34801561045d575f80fd5b50610295610cb6565b348015610471575f80fd5b50610295610cc7565b348015610485575f80fd5b5061015f5461025e906001600160a01b031681565b3480156104a5575f80fd5b50610295610d3e565b3480156104b9575f80fd5b506033546001600160a01b031661025e565b3480156104d6575f80fd5b50610232610dbd565b3480156104ea575f80fd5b506102956104f9366004612502565b610dcf565b348015610509575f80fd5b506102956105183660046126c5565b610ec9565b348015610528575f80fd5b5061025e6105373660046126f8565b610ed4565b348015610547575f80fd5b50610295610556366004612719565b610ee0565b348015610566575f80fd5b506102326105753660046124d7565b610f18565b348015610585575f80fd5b506065546001600160a01b031661025e565b3480156105a2575f80fd5b506102096105b1366004612781565b6001600160a01b039182165f9081526101326020908152604080832093909416825291909152205460ff1690565b3480156105ea575f80fd5b506102956105f93660046127b8565b610f68565b348015610609575f80fd5b5061029561061836600461256a565b6110b1565b5f6001600160e01b031982166380ac58cd60e01b148061064d57506001600160e01b03198216635b5e139f60e01b145b8061066857506301ffc9a760e01b6001600160e01b03198316145b92915050565b606061068461067b611122565b610160546111b3565b905090565b5f61069382611202565b505f90815261013160205260409020546001600160a01b031690565b5f6106b982610bd1565b9050806001600160a01b0316836001600160a01b03160361072b5760405162461bcd60e51b815260206004820152602160248201527f4552433732313a20617070726f76616c20746f2063757272656e74206f776e656044820152603960f91b60648201526084015b60405180910390fd5b336001600160a01b0382161480610747575061074781336105b1565b6107b95760405162461bcd60e51b815260206004820152603d60248201527f4552433732313a20617070726f76652063616c6c6572206973206e6f7420746f60448201527f6b656e206f776e6572206f7220617070726f76656420666f7220616c6c0000006064820152608401610722565b6107c38383611261565b505050565b6107d233826112cf565b6107ee5760405162461bcd60e51b815260040161072290612856565b6107c383838361134d565b6001600160a01b037f00000000000000000000000001670090000000000000000000000000000100971630036108415760405162461bcd60e51b8152600401610722906128a3565b7f00000000000000000000000001670090000000000000000000000000000100976001600160a01b03166108895f80516020612d84833981519152546001600160a01b031690565b6001600160a01b0316146108af5760405162461bcd60e51b8152600401610722906128ef565b6108b8816114bf565b604080515f808252602082019092526108d3918391906114c7565b50565b5f6108e2848484611631565b90505b9392505050565b61090060c954610100900460ff1660021490565b61091d5760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166101001790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610969335f61171f565b565b600261097960c95460ff1690565b60ff160361099a5760405163dfc60d8560e01b815260040160405180910390fd5b6109a46002611727565b6109b860c954610100900460ff1660021490565b156109d65760405163bae6e2a960e01b815260040160405180910390fd5b6b195c98cdcc8c57dd985d5b1d60a21b6109f1816001610ed4565b6001600160a01b0316336001600160a01b031614610a2257604051630d85cccf60e11b815260040160405180910390fd5b610a2c838361173d565b50610a376001611727565b5050565b6107c383838360405180602001604052805f815250610ee0565b6001600160a01b037f0000000000000000000000000167009000000000000000000000000000010097163003610a9d5760405162461bcd60e51b8152600401610722906128a3565b7f00000000000000000000000001670090000000000000000000000000000100976001600160a01b0316610ae55f80516020612d84833981519152546001600160a01b031690565b6001600160a01b031614610b0b5760405162461bcd60e51b8152600401610722906128ef565b610b14826114bf565b610a37828260016114c7565b5f306001600160a01b037f00000000000000000000000001670090000000000000000000000000000100971614610bbf5760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610722565b505f80516020612d8483398151915290565b5f81815261012f60205260408120546001600160a01b0316806106685760405162461bcd60e51b8152602060048201526018602482015277115490cdcc8c4e881a5b9d985b1a59081d1bdad95b88125160421b6044820152606401610722565b5f6001600160a01b038216610c9a5760405162461bcd60e51b815260206004820152602960248201527f4552433732313a2061646472657373207a65726f206973206e6f7420612076616044820152683634b21037bbb732b960b91b6064820152608401610722565b506001600160a01b03165f908152610130602052604090205490565b610cbe611756565b6109695f6117b0565b60655433906001600160a01b03168114610d355760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b6064820152608401610722565b6108d3816117b0565b610d5260c954610100900460ff1660021490565b15610d705760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a161096933600161171f565b6060610684610dca6117c9565b6117d9565b6002610ddd60c95460ff1690565b60ff1603610dfe5760405163dfc60d8560e01b815260040160405180910390fd5b610e086002611727565b610e1c60c954610100900460ff1660021490565b15610e3a5760405163bae6e2a960e01b815260040160405180910390fd5b6b195c98cdcc8c57dd985d5b1d60a21b610e55816001610ed4565b6001600160a01b0316336001600160a01b031614610e8657604051630d85cccf60e11b815260040160405180910390fd5b826001600160a01b0316610e9983610bd1565b6001600160a01b031614610ec05760405163358bf3d960e01b815260040160405180910390fd5b610a2c8261180c565b610a373383836118ae565b5f6108e5468484611631565b610eea33836112cf565b610f065760405162461bcd60e51b815260040161072290612856565b610f128484848461197c565b50505050565b61015f5461016054606091610f38916001600160a01b03909116906119af565b610f41836119f6565b604051602001610f5292919061293b565b6040516020818303038152906040529050919050565b5f54610100900460ff1615808015610f8657505f54600160ff909116105b80610f9f5750303b158015610f9f57505f5460ff166001145b6110025760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610722565b5f805460ff191660011790558015611023575f805461ff0019166101001790555b61102d8585611a86565b6110378787611ac2565b6110418284611b21565b61015f80546001600160a01b0319166001600160a01b03871617905561016084905580156110a8575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050505050565b6110b9611756565b606580546001600160a01b0383166001600160a01b031990911681179091556110ea6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b606061012d805461113290612969565b80601f016020809104026020016040519081016040528092919081815260200182805461115e90612969565b80156111a95780601f10611180576101008083540402835291602001916111a9565b820191905f5260205f20905b81548152906001019060200180831161118c57829003601f168201915b5050505050905090565b606082515f036111d1575060408051602081019091525f8152610668565b826111db836119f6565b6040516020016111ec9291906129a1565b6040516020818303038152906040529050610668565b5f81815261012f60205260409020546001600160a01b03166108d35760405162461bcd60e51b8152602060048201526018602482015277115490cdcc8c4e881a5b9d985b1a59081d1bdad95b88125160421b6044820152606401610722565b5f8181526101316020526040902080546001600160a01b0319166001600160a01b038416908117909155819061129682610bd1565b6001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b5f806112da83610bd1565b9050806001600160a01b0316846001600160a01b0316148061132157506001600160a01b038082165f908152610132602090815260408083209388168352929052205460ff165b806113455750836001600160a01b031661133a84610689565b6001600160a01b0316145b949350505050565b826001600160a01b031661136082610bd1565b6001600160a01b0316146113865760405162461bcd60e51b815260040161072290612a01565b6001600160a01b0382166113e85760405162461bcd60e51b8152602060048201526024808201527f4552433732313a207472616e7366657220746f20746865207a65726f206164646044820152637265737360e01b6064820152608401610722565b6113f58383836001611b51565b826001600160a01b031661140882610bd1565b6001600160a01b03161461142e5760405162461bcd60e51b815260040161072290612a01565b5f8181526101316020908152604080832080546001600160a01b03199081169091556001600160a01b03878116808652610130855283862080545f190190559087168086528386208054600101905586865261012f90945282852080549092168417909155905184937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4505050565b6108d3611756565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156114fa576107c383611bac565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611554575060408051601f3d908101601f1916820190925261155191810190612a46565b60015b6115b75760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610722565b5f80516020612d8483398151915281146116255760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610722565b506107c3838383611c47565b6097545f906001600160a01b031661165c57604051638ed88b2560e01b815260040160405180910390fd5b609754604051630a3dc4f360e21b815267ffffffffffffffff86166004820152602481018590526001600160a01b03909116906328f713cc90604401602060405180830381865afa1580156116b3573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116d79190612a5d565b9050811580156116ee57506001600160a01b038116155b156108e557604051632b0d65db60e01b815267ffffffffffffffff8516600482015260248101849052604401610722565b610a37611756565b60c9805460ff191660ff92909216919091179055565b610a37828260405180602001604052805f815250611c6b565b6033546001600160a01b031633146109695760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610722565b606580546001600160a01b03191690556108d381611c9d565b606061012e805461113290612969565b606081515f036117f657505060408051602081019091525f815290565b81604051602001610f529190612a78565b919050565b5f61181682610bd1565b9050611825815f846001611b51565b61182e82610bd1565b5f8381526101316020908152604080832080546001600160a01b03199081169091556001600160a01b038516808552610130845282852080545f1901905587855261012f909352818420805490911690555192935084927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908390a45050565b816001600160a01b0316836001600160a01b03160361190f5760405162461bcd60e51b815260206004820152601960248201527f4552433732313a20617070726f766520746f2063616c6c6572000000000000006044820152606401610722565b6001600160a01b038381165f8181526101326020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b61198784848461134d565b61199384848484611cee565b610f125760405162461bcd60e51b815260040161072290612a9d565b60606119c5836001600160a01b03166014611deb565b6119ce836119f6565b6040516020016119df929190612aef565b604051602081830303815290604052905092915050565b60605f611a0283611f81565b60010190505f8167ffffffffffffffff811115611a2157611a216125db565b6040519080825280601f01601f191660200182016040528015611a4b576020820181803683370190505b5090508181016020015b5f19016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a8504945084611a5557509392505050565b6001600160a01b0382161580611a9a575080155b80611aa457504681145b15610a375760405163c118d2f360e01b815260040160405180910390fd5b5f54610100900460ff16611ae85760405162461bcd60e51b815260040161072290612b5e565b611af182612058565b6001600160a01b038116611b18576040516375cabfef60e11b815260040160405180910390fd5b610a3781612088565b5f54610100900460ff16611b475760405162461bcd60e51b815260040161072290612b5e565b610a3782826120f9565b306001600160a01b03841603611b7a57604051630183150560e21b815260040160405180910390fd5b611b8e60c954610100900460ff1660021490565b15610f125760405163bae6e2a960e01b815260040160405180910390fd5b6001600160a01b0381163b611c195760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610722565b5f80516020612d8483398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b611c508361213a565b5f82511180611c5c5750805b156107c357610f128383612179565b611c75838361219e565b611c815f848484611cee565b6107c35760405162461bcd60e51b815260040161072290612a9d565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f6001600160a01b0384163b15611de057604051630a85bd0160e11b81526001600160a01b0385169063150b7a0290611d31903390899088908890600401612ba9565b6020604051808303815f875af1925050508015611d6b575060408051601f3d908101601f19168201909252611d6891810190612bdb565b60015b611dc6573d808015611d98576040519150601f19603f3d011682016040523d82523d5f602084013e611d9d565b606091505b5080515f03611dbe5760405162461bcd60e51b815260040161072290612a9d565b805181602001fd5b6001600160e01b031916630a85bd0160e11b149050611345565b506001949350505050565b60605f611df9836002612c0a565b611e04906002612c21565b67ffffffffffffffff811115611e1c57611e1c6125db565b6040519080825280601f01601f191660200182016040528015611e46576020820181803683370190505b509050600360fc1b815f81518110611e6057611e60612c34565b60200101906001600160f81b03191690815f1a905350600f60fb1b81600181518110611e8e57611e8e612c34565b60200101906001600160f81b03191690815f1a9053505f611eb0846002612c0a565b611ebb906001612c21565b90505b6001811115611f32576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110611eef57611eef612c34565b1a60f81b828281518110611f0557611f05612c34565b60200101906001600160f81b03191690815f1a90535060049490941c93611f2b81612c48565b9050611ebe565b5083156108e55760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610722565b5f8072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b8310611fbf5772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef81000000008310611feb576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc10000831061200957662386f26fc10000830492506010015b6305f5e1008310612021576305f5e100830492506008015b612710831061203557612710830492506004015b60648310612047576064830492506002015b600a83106106685760010192915050565b6120766001600160a01b0382161561207057816117b0565b336117b0565b5060c9805461ff001916610100179055565b5f54610100900460ff166120ae5760405162461bcd60e51b815260040161072290612b5e565b67ffffffffffffffff4611156120d75760405163a12e8fa960e01b815260040160405180910390fd5b609780546001600160a01b0319166001600160a01b0392909216919091179055565b5f54610100900460ff1661211f5760405162461bcd60e51b815260040161072290612b5e565b61012d61212c8382612ca8565b5061012e6107c38282612ca8565b61214381611bac565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606108e58383604051806060016040528060278152602001612da460279139612337565b6001600160a01b0382166121f45760405162461bcd60e51b815260206004820181905260248201527f4552433732313a206d696e7420746f20746865207a65726f20616464726573736044820152606401610722565b5f81815261012f60205260409020546001600160a01b0316156122595760405162461bcd60e51b815260206004820152601c60248201527f4552433732313a20746f6b656e20616c7265616479206d696e746564000000006044820152606401610722565b6122665f83836001611b51565b5f81815261012f60205260409020546001600160a01b0316156122cb5760405162461bcd60e51b815260206004820152601c60248201527f4552433732313a20746f6b656e20616c7265616479206d696e746564000000006044820152606401610722565b6001600160a01b0382165f818152610130602090815260408083208054600101905584835261012f90915280822080546001600160a01b0319168417905551839291907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a45050565b60605f80856001600160a01b0316856040516123539190612d68565b5f60405180830381855af49150503d805f811461238b576040519150601f19603f3d011682016040523d82523d5f602084013e612390565b606091505b50915091506123a1868383876123ab565b9695505050505050565b606083156124195782515f03612412576001600160a01b0385163b6124125760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610722565b5081611345565b611345838381511561242e5781518083602001fd5b8060405162461bcd60e51b815260040161072291906124c5565b6001600160e01b0319811681146108d3575f80fd5b5f6020828403121561246d575f80fd5b81356108e581612448565b5f5b8381101561249257818101518382015260200161247a565b50505f910152565b5f81518084526124b1816020860160208601612478565b601f01601f19169290920160200192915050565b602081525f6108e5602083018461249a565b5f602082840312156124e7575f80fd5b5035919050565b6001600160a01b03811681146108d3575f80fd5b5f8060408385031215612513575f80fd5b823561251e816124ee565b946020939093013593505050565b5f805f6060848603121561253e575f80fd5b8335612549816124ee565b92506020840135612559816124ee565b929592945050506040919091013590565b5f6020828403121561257a575f80fd5b81356108e5816124ee565b80358015158114611807575f80fd5b5f805f606084860312156125a6575f80fd5b833567ffffffffffffffff811681146125bd575f80fd5b9250602084013591506125d260408501612585565b90509250925092565b634e487b7160e01b5f52604160045260245ffd5b5f82601f8301126125fe575f80fd5b813567ffffffffffffffff80821115612619576126196125db565b604051601f8301601f19908116603f01168101908282118183101715612641576126416125db565b81604052838152866020858801011115612659575f80fd5b836020870160208301375f602085830101528094505050505092915050565b5f8060408385031215612689575f80fd5b8235612694816124ee565b9150602083013567ffffffffffffffff8111156126af575f80fd5b6126bb858286016125ef565b9150509250929050565b5f80604083850312156126d6575f80fd5b82356126e1816124ee565b91506126ef60208401612585565b90509250929050565b5f8060408385031215612709575f80fd5b823591506126ef60208401612585565b5f805f806080858703121561272c575f80fd5b8435612737816124ee565b93506020850135612747816124ee565b925060408501359150606085013567ffffffffffffffff811115612769575f80fd5b612775878288016125ef565b91505092959194509250565b5f8060408385031215612792575f80fd5b823561279d816124ee565b915060208301356127ad816124ee565b809150509250929050565b5f805f805f8060c087890312156127cd575f80fd5b86356127d8816124ee565b955060208701356127e8816124ee565b945060408701356127f8816124ee565b935060608701359250608087013567ffffffffffffffff8082111561281b575f80fd5b6128278a838b016125ef565b935060a089013591508082111561283c575f80fd5b5061284989828a016125ef565b9150509295509295509295565b6020808252602d908201527f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560408201526c1c881bdc88185c1c1c9bdd9959609a1b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b5f835161294c818460208801612478565b835190830190612960818360208801612478565b01949350505050565b600181811c9082168061297d57607f821691505b60208210810361299b57634e487b7160e01b5f52602260045260245ffd5b50919050565b670213934b233b2b2160c51b81525f83516129c3816008850160208801612478565b634051c55b60df1b60089184019182015283516129e781600d840160208801612478565b602960f81b600d9290910191820152600e01949350505050565b60208082526025908201527f4552433732313a207472616e736665722066726f6d20696e636f72726563742060408201526437bbb732b960d91b606082015260800190565b5f60208284031215612a56575f80fd5b5051919050565b5f60208284031215612a6d575f80fd5b81516108e5816124ee565b5f8251612a89818460208701612478565b610b9d60f21b920191825250600201919050565b60208082526032908201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560408201527131b2b4bb32b91034b6b83632b6b2b73a32b960711b606082015260800190565b6832ba3432b932bab69d60b91b81525f8351612b12816009850160208801612478565b600160fe1b6009918401918201528351612b3381600a840160208801612478565b712f746f6b656e5552493f75696e743235363d60701b600a9290910191820152601c01949350505050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6001600160a01b03858116825284166020820152604081018390526080606082018190525f906123a19083018461249a565b5f60208284031215612beb575f80fd5b81516108e581612448565b634e487b7160e01b5f52601160045260245ffd5b808202811582820484141761066857610668612bf6565b8082018082111561066857610668612bf6565b634e487b7160e01b5f52603260045260245ffd5b5f81612c5657612c56612bf6565b505f190190565b601f8211156107c357805f5260205f20601f840160051c81016020851015612c825750805b601f840160051c820191505b81811015612ca1575f8155600101612c8e565b5050505050565b815167ffffffffffffffff811115612cc257612cc26125db565b612cd681612cd08454612969565b84612c5d565b602080601f831160018114612d09575f8415612cf25750858301515b5f19600386901b1c1916600185901b178555612d60565b5f85815260208120601f198616915b82811015612d3757888601518255948401946001909101908401612d18565b5085821015612d5457878501515f19600388901b60f8161c191681555b505060018460011b0185555b505050505050565b5f8251612d79818460208701612478565b919091019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212203f79b375075ab76c20863b31403aa53cc4810d01808c370f49d624c8b1fd8f8164736f6c63430008180033", - "balance": "0x0" - }, - "0x0167009000000000000000000000000000010098": { - "contractName": "BridgedERC1155", - "storage": {}, - "code": "0x6080604052600436106101c4575f3560e01c8063715018a6116100f2578063a86f9d9e11610092578063ef8c4ae611610062578063ef8c4ae61461050a578063f242432a14610529578063f2fde38b14610548578063f5298aca14610567575f80fd5b8063a86f9d9e14610467578063d81d0a1514610486578063e30c3978146104a5578063e985e9c5146104c2575f80fd5b80638456cb59116100cd5780638456cb59146104035780638da5cb5b1461041757806395d89b4114610434578063a22cb46514610448575f80fd5b8063715018a6146103bb57806379ba5097146103cf5780637cf8ed0d146103e3575f80fd5b80633ab76e9f116101685780634e1273f4116101385780634e1273f4146103485780634f1ef2861461037457806352d1902d146103875780635c975abb1461039b575f80fd5b80633ab76e9f146102c85780633eb6b8cf146102ff5780633f4ba83a1461031e57806349d1260514610332575f80fd5b80630e89341c116101a35780630e89341c1461024a578063156e29f6146102695780632eb2c2d61461028a5780633659cfe6146102a9575f80fd5b8062fdd58e146101c857806301ffc9a7146101fa57806306fdde0314610229575b5f80fd5b3480156101d3575f80fd5b506101e76101e236600461276f565b610586565b6040519081526020015b60405180910390f35b348015610205575f80fd5b506102196102143660046127ae565b610620565b60405190151581526020016101f1565b348015610234575f80fd5b5061023d61066f565b6040516101f19190612816565b348015610255575f80fd5b5061023d610264366004612828565b61070c565b348015610274575f80fd5b5061028861028336600461283f565b61079f565b005b348015610295575f80fd5b506102886102a43660046129b5565b610881565b3480156102b4575f80fd5b506102886102c3366004612a5b565b6108cd565b3480156102d3575f80fd5b506097546102e7906001600160a01b031681565b6040516001600160a01b0390911681526020016101f1565b34801561030a575f80fd5b506102e7610319366004612a85565b6109aa565b348015610329575f80fd5b506102886109c0565b34801561033d575f80fd5b506101e76101605481565b348015610353575f80fd5b50610367610362366004612acb565b610a3f565b6040516101f19190612bcb565b610288610382366004612bdd565b610b5e565b348015610392575f80fd5b506101e7610c2d565b3480156103a6575f80fd5b5061021960c954610100900460ff1660021490565b3480156103c6575f80fd5b50610288610cdf565b3480156103da575f80fd5b50610288610cf0565b3480156103ee575f80fd5b5061015f546102e7906001600160a01b031681565b34801561040e575f80fd5b50610288610d67565b348015610422575f80fd5b506033546001600160a01b03166102e7565b34801561043f575f80fd5b5061023d610de6565b348015610453575f80fd5b50610288610462366004612c1f565b610e7a565b348015610472575f80fd5b506102e7610481366004612c52565b610e85565b348015610491575f80fd5b506102886104a0366004612c73565b610e91565b3480156104b0575f80fd5b506065546001600160a01b03166102e7565b3480156104cd575f80fd5b506102196104dc366004612ce3565b6001600160a01b039182165f90815261012e6020908152604080832093909416825291909152205460ff1690565b348015610515575f80fd5b50610288610524366004612d1a565b610f63565b348015610534575f80fd5b50610288610543366004612db7565b6110d0565b348015610553575f80fd5b50610288610562366004612a5b565b611115565b348015610572575f80fd5b5061028861058136600461283f565b611186565b5f6001600160a01b0383166105f55760405162461bcd60e51b815260206004820152602a60248201527f455243313135353a2061646472657373207a65726f206973206e6f742061207660448201526930b634b21037bbb732b960b11b60648201526084015b60405180910390fd5b505f81815261012d602090815260408083206001600160a01b03861684529091529020545b92915050565b5f6001600160e01b03198216636cdb3d1360e11b148061065057506001600160e01b031982166303a24d0760e21b145b8061061a57506301ffc9a760e01b6001600160e01b031983161461061a565b6060610707610162805461068290612e1a565b80601f01602080910402602001604051908101604052809291908181526020018280546106ae90612e1a565b80156106f95780601f106106d0576101008083540402835291602001916106f9565b820191905f5260205f20905b8154815290600101906020018083116106dc57829003601f168201915b505050505061016054611249565b905090565b606061012f805461071c90612e1a565b80601f016020809104026020016040519081016040528092919081815260200182805461074890612e1a565b80156107935780601f1061076a57610100808354040283529160200191610793565b820191905f5260205f20905b81548152906001019060200180831161077657829003601f168201915b50505050509050919050565b60026107ad60c95460ff1690565b60ff16036107ce5760405163dfc60d8560e01b815260040160405180910390fd5b6107d86002611298565b6107ec60c954610100900460ff1660021490565b1561080a5760405163bae6e2a960e01b815260040160405180910390fd5b6c195c98cc4c4d4d57dd985d5b1d609a1b610826816001610e85565b6001600160a01b0316336001600160a01b03161461085757604051630d85cccf60e11b815260040160405180910390fd5b61087184848460405180602001604052805f8152506112ae565b5061087c6001611298565b505050565b6001600160a01b03851633148061089d575061089d85336104dc565b6108b95760405162461bcd60e51b81526004016105ec90612e52565b6108c6858585858561138b565b5050505050565b6001600160a01b037f00000000000000000000000001670090000000000000000000000000000100981630036109155760405162461bcd60e51b81526004016105ec90612ea0565b7f00000000000000000000000001670090000000000000000000000000000100986001600160a01b031661095d5f80516020613514833981519152546001600160a01b031690565b6001600160a01b0316146109835760405162461bcd60e51b81526004016105ec90612eec565b61098c8161152e565b604080515f808252602082019092526109a791839190611536565b50565b5f6109b68484846116a0565b90505b9392505050565b6109d460c954610100900460ff1660021490565b6109f15760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166101001790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610a3d335f61178c565b565b60608151835114610aa45760405162461bcd60e51b815260206004820152602960248201527f455243313135353a206163636f756e747320616e6420696473206c656e677468604482015268040dad2e6dac2e8c6d60bb1b60648201526084016105ec565b5f83516001600160401b03811115610abe57610abe612871565b604051908082528060200260200182016040528015610ae7578160200160208202803683370190505b5090505f5b8451811015610b5657610b31858281518110610b0a57610b0a612f38565b6020026020010151858381518110610b2457610b24612f38565b6020026020010151610586565b828281518110610b4357610b43612f38565b6020908102919091010152600101610aec565b509392505050565b6001600160a01b037f0000000000000000000000000167009000000000000000000000000000010098163003610ba65760405162461bcd60e51b81526004016105ec90612ea0565b7f00000000000000000000000001670090000000000000000000000000000100986001600160a01b0316610bee5f80516020613514833981519152546001600160a01b031690565b6001600160a01b031614610c145760405162461bcd60e51b81526004016105ec90612eec565b610c1d8261152e565b610c2982826001611536565b5050565b5f306001600160a01b037f00000000000000000000000001670090000000000000000000000000000100981614610ccc5760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c000000000000000060648201526084016105ec565b505f805160206135148339815191525b90565b610ce7611790565b610a3d5f6117ea565b60655433906001600160a01b03168114610d5e5760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016105ec565b6109a7816117ea565b610d7b60c954610100900460ff1660021490565b15610d995760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a1610a3d33600161178c565b60606107076101618054610df990612e1a565b80601f0160208091040260200160405190810160405280929190818152602001828054610e2590612e1a565b8015610e705780601f10610e4757610100808354040283529160200191610e70565b820191905f5260205f20905b815481529060010190602001808311610e5357829003601f168201915b5050505050611803565b610c2933838361184c565b5f6109b94684846116a0565b6002610e9f60c95460ff1690565b60ff1603610ec05760405163dfc60d8560e01b815260040160405180910390fd5b610eca6002611298565b610ede60c954610100900460ff1660021490565b15610efc5760405163bae6e2a960e01b815260040160405180910390fd5b6c195c98cc4c4d4d57dd985d5b1d609a1b610f18816001610e85565b6001600160a01b0316336001600160a01b031614610f4957604051630d85cccf60e11b815260040160405180910390fd5b61087184848460405180602001604052805f81525061192c565b5f54610100900460ff1615808015610f8157505f54600160ff909116105b80610f9a5750303b158015610f9a57505f5460ff166001145b610ffd5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016105ec565b5f805460ff19166001179055801561101e575f805461ff0019166101001790555b6110288585611a74565b6110328787611ab0565b61104461103f8686611b0f565b611b56565b61015f80546001600160a01b0319166001600160a01b0387161790556101608490556101616110738482612f90565b506101626110818382612f90565b5080156110c7575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050505050565b6001600160a01b0385163314806110ec57506110ec85336104dc565b6111085760405162461bcd60e51b81526004016105ec90612e52565b6108c68585858585611b85565b61111d611790565b606580546001600160a01b0383166001600160a01b0319909116811790915561114e6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b600261119460c95460ff1690565b60ff16036111b55760405163dfc60d8560e01b815260040160405180910390fd5b6111bf6002611298565b6111d360c954610100900460ff1660021490565b156111f15760405163bae6e2a960e01b815260040160405180910390fd5b6c195c98cc4c4d4d57dd985d5b1d609a1b61120d816001610e85565b6001600160a01b0316336001600160a01b03161461123e57604051630d85cccf60e11b815260040160405180910390fd5b610871848484611cbf565b606082515f03611267575060408051602081019091525f815261061a565b8261127183611e4e565b60405160200161128292919061304b565b604051602081830303815290604052905061061a565b60c9805460ff191660ff92909216919091179055565b6001600160a01b0384166112d45760405162461bcd60e51b81526004016105ec906130ab565b335f6112df85611edd565b90505f6112eb85611edd565b90506112fb835f89858589611f26565b5f86815261012d602090815260408083206001600160a01b038b1684529091528120805487929061132d908490613100565b909155505060408051878152602081018790526001600160a01b03808a16925f92918716917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a46110c7835f89898989611f81565b81518351146113ac5760405162461bcd60e51b81526004016105ec90613113565b6001600160a01b0384166113d25760405162461bcd60e51b81526004016105ec9061315b565b336113e1818787878787611f26565b5f5b84518110156114c0575f8582815181106113ff576113ff612f38565b602002602001015190505f85838151811061141c5761141c612f38565b6020908102919091018101515f84815261012d835260408082206001600160a01b038e16835290935291909120549091508181101561146d5760405162461bcd60e51b81526004016105ec906131a0565b5f83815261012d602090815260408083206001600160a01b038e8116855292528083208585039055908b168252812080548492906114ac908490613100565b9091555050600190930192506113e3915050565b50846001600160a01b0316866001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb87876040516115109291906131ea565b60405180910390a46115268187878787876120db565b505050505050565b6109a7611790565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156115695761087c83612195565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156115c3575060408051601f3d908101601f191682019092526115c091810190613217565b60015b6116265760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b60648201526084016105ec565b5f8051602061351483398151915281146116945760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b60648201526084016105ec565b5061087c838383612230565b6097545f906001600160a01b03166116cb57604051638ed88b2560e01b815260040160405180910390fd5b609754604051630a3dc4f360e21b81526001600160401b0386166004820152602481018590526001600160a01b03909116906328f713cc90604401602060405180830381865afa158015611721573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611745919061322e565b90508115801561175c57506001600160a01b038116155b156109b957604051632b0d65db60e01b81526001600160401b0385166004820152602481018490526044016105ec565b610c295b6033546001600160a01b03163314610a3d5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016105ec565b606580546001600160a01b03191690556109a78161225a565b606081515f0361182057505060408051602081019091525f815290565b816040516020016118319190613249565b6040516020818303038152906040529050919050565b919050565b816001600160a01b0316836001600160a01b0316036118bf5760405162461bcd60e51b815260206004820152602960248201527f455243313135353a2073657474696e6720617070726f76616c20737461747573604482015268103337b91039b2b63360b91b60648201526084016105ec565b6001600160a01b038381165f81815261012e6020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b6001600160a01b0384166119525760405162461bcd60e51b81526004016105ec906130ab565b81518351146119735760405162461bcd60e51b81526004016105ec90613113565b33611982815f87878787611f26565b5f5b8451811015611a0e5783818151811061199f5761199f612f38565b602002602001015161012d5f8784815181106119bd576119bd612f38565b602002602001015181526020019081526020015f205f886001600160a01b03166001600160a01b031681526020019081526020015f205f828254611a019190613100565b9091555050600101611984565b50846001600160a01b03165f6001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8787604051611a5e9291906131ea565b60405180910390a46108c6815f878787876120db565b6001600160a01b0382161580611a88575080155b80611a9257504681145b15610c295760405163c118d2f360e01b815260040160405180910390fd5b5f54610100900460ff16611ad65760405162461bcd60e51b81526004016105ec9061326e565b611adf826122ab565b6001600160a01b038116611b06576040516375cabfef60e11b815260040160405180910390fd5b610c29816122db565b6060611b25836001600160a01b0316601461234b565b611b2e83611e4e565b604051602001611b3f9291906132b9565b604051602081830303815290604052905092915050565b5f54610100900460ff16611b7c5760405162461bcd60e51b81526004016105ec9061326e565b6109a7816124e0565b6001600160a01b038416611bab5760405162461bcd60e51b81526004016105ec9061315b565b335f611bb685611edd565b90505f611bc285611edd565b9050611bd2838989858589611f26565b5f86815261012d602090815260408083206001600160a01b038c16845290915290205485811015611c155760405162461bcd60e51b81526004016105ec906131a0565b5f87815261012d602090815260408083206001600160a01b038d8116855292528083208985039055908a16825281208054889290611c54908490613100565b909155505060408051888152602081018890526001600160a01b03808b16928c821692918816917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a4611cb4848a8a8a8a8a611f81565b505050505050505050565b6001600160a01b038316611d215760405162461bcd60e51b815260206004820152602360248201527f455243313135353a206275726e2066726f6d20746865207a65726f206164647260448201526265737360e81b60648201526084016105ec565b335f611d2c84611edd565b90505f611d3884611edd565b9050611d5683875f858560405180602001604052805f815250611f26565b5f85815261012d602090815260408083206001600160a01b038a16845290915290205484811015611dd55760405162461bcd60e51b8152602060048201526024808201527f455243313135353a206275726e20616d6f756e7420657863656564732062616c604482015263616e636560e01b60648201526084016105ec565b5f86815261012d602090815260408083206001600160a01b038b81168086529184528285208a8703905582518b81529384018a90529092908816917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a460408051602081019091525f90526110c7565b60605f611e5a8361250f565b60010190505f816001600160401b03811115611e7857611e78612871565b6040519080825280601f01601f191660200182016040528015611ea2576020820181803683370190505b5090508181016020015b5f19016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a8504945084611eac57509392505050565b6040805160018082528183019092526060915f91906020808301908036833701905050905082815f81518110611f1557611f15612f38565b602090810291909101015292915050565b306001600160a01b03851603611f4f57604051630183150560e21b815260040160405180910390fd5b611f6360c954610100900460ff1660021490565b156115265760405163bae6e2a960e01b815260040160405180910390fd5b6001600160a01b0384163b156115265760405163f23a6e6160e01b81526001600160a01b0385169063f23a6e6190611fc59089908990889088908890600401613328565b6020604051808303815f875af1925050508015611fff575060408051601f3d908101601f19168201909252611ffc9181019061336c565b60015b6120ab5761200b613387565b806308c379a003612044575061201f61339f565b8061202a5750612046565b8060405162461bcd60e51b81526004016105ec9190612816565b505b60405162461bcd60e51b815260206004820152603460248201527f455243313135353a207472616e7366657220746f206e6f6e2d455243313135356044820152732932b1b2b4bb32b91034b6b83632b6b2b73a32b960611b60648201526084016105ec565b6001600160e01b0319811663f23a6e6160e01b146110c75760405162461bcd60e51b81526004016105ec90613427565b6001600160a01b0384163b156115265760405163bc197c8160e01b81526001600160a01b0385169063bc197c819061211f908990899088908890889060040161346f565b6020604051808303815f875af1925050508015612159575060408051601f3d908101601f191682019092526121569181019061336c565b60015b6121655761200b613387565b6001600160e01b0319811663bc197c8160e01b146110c75760405162461bcd60e51b81526004016105ec90613427565b6001600160a01b0381163b6122025760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016105ec565b5f8051602061351483398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b612239836125e6565b5f825111806122455750805b1561087c576122548383612625565b50505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b6122c96001600160a01b038216156122c357816117ea565b336117ea565b5060c9805461ff001916610100179055565b5f54610100900460ff166123015760405162461bcd60e51b81526004016105ec9061326e565b6001600160401b034611156123295760405163a12e8fa960e01b815260040160405180910390fd5b609780546001600160a01b0319166001600160a01b0392909216919091179055565b60605f6123598360026134cc565b612364906002613100565b6001600160401b0381111561237b5761237b612871565b6040519080825280601f01601f1916602001820160405280156123a5576020820181803683370190505b509050600360fc1b815f815181106123bf576123bf612f38565b60200101906001600160f81b03191690815f1a905350600f60fb1b816001815181106123ed576123ed612f38565b60200101906001600160f81b03191690815f1a9053505f61240f8460026134cc565b61241a906001613100565b90505b6001811115612491576f181899199a1a9b1b9c1cb0b131b232b360811b85600f166010811061244e5761244e612f38565b1a60f81b82828151811061246457612464612f38565b60200101906001600160f81b03191690815f1a90535060049490941c9361248a816134e3565b905061241d565b5083156109b95760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e7460448201526064016105ec565b5f54610100900460ff166125065760405162461bcd60e51b81526004016105ec9061326e565b6109a78161264a565b5f8072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b831061254d5772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef81000000008310612579576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc10000831061259757662386f26fc10000830492506010015b6305f5e10083106125af576305f5e100830492506008015b61271083106125c357612710830492506004015b606483106125d5576064830492506002015b600a831061061a5760010192915050565b6125ef81612195565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606109b9838360405180606001604052806027815260200161353460279139612657565b61012f610c298282612f90565b60605f80856001600160a01b03168560405161267391906134f8565b5f60405180830381855af49150503d805f81146126ab576040519150601f19603f3d011682016040523d82523d5f602084013e6126b0565b606091505b50915091506126c1868383876126cb565b9695505050505050565b606083156127395782515f03612732576001600160a01b0385163b6127325760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016105ec565b5081612743565b612743838361274b565b949350505050565b81511561202a5781518083602001fd5b6001600160a01b03811681146109a7575f80fd5b5f8060408385031215612780575f80fd5b823561278b8161275b565b946020939093013593505050565b6001600160e01b0319811681146109a7575f80fd5b5f602082840312156127be575f80fd5b81356109b981612799565b5f5b838110156127e35781810151838201526020016127cb565b50505f910152565b5f81518084526128028160208601602086016127c9565b601f01601f19169290920160200192915050565b602081525f6109b960208301846127eb565b5f60208284031215612838575f80fd5b5035919050565b5f805f60608486031215612851575f80fd5b833561285c8161275b565b95602085013595506040909401359392505050565b634e487b7160e01b5f52604160045260245ffd5b601f8201601f191681016001600160401b03811182821017156128aa576128aa612871565b6040525050565b5f6001600160401b038211156128c9576128c9612871565b5060051b60200190565b5f82601f8301126128e2575f80fd5b813560206128ef826128b1565b6040516128fc8282612885565b80915083815260208101915060208460051b87010193508684111561291f575f80fd5b602086015b8481101561293b5780358352918301918301612924565b509695505050505050565b5f82601f830112612955575f80fd5b81356001600160401b0381111561296e5761296e612871565b604051612985601f8301601f191660200182612885565b818152846020838601011115612999575f80fd5b816020850160208301375f918101602001919091529392505050565b5f805f805f60a086880312156129c9575f80fd5b85356129d48161275b565b945060208601356129e48161275b565b935060408601356001600160401b03808211156129ff575f80fd5b612a0b89838a016128d3565b94506060880135915080821115612a20575f80fd5b612a2c89838a016128d3565b93506080880135915080821115612a41575f80fd5b50612a4e88828901612946565b9150509295509295909350565b5f60208284031215612a6b575f80fd5b81356109b98161275b565b80358015158114611847575f80fd5b5f805f60608486031215612a97575f80fd5b83356001600160401b0381168114612aad575f80fd5b925060208401359150612ac260408501612a76565b90509250925092565b5f8060408385031215612adc575f80fd5b82356001600160401b0380821115612af2575f80fd5b818501915085601f830112612b05575f80fd5b81356020612b12826128b1565b604051612b1f8282612885565b83815260059390931b8501820192828101915089841115612b3e575f80fd5b948201945b83861015612b65578535612b568161275b565b82529482019490820190612b43565b96505086013592505080821115612b7a575f80fd5b50612b87858286016128d3565b9150509250929050565b5f815180845260208085019450602084015f5b83811015612bc057815187529582019590820190600101612ba4565b509495945050505050565b602081525f6109b96020830184612b91565b5f8060408385031215612bee575f80fd5b8235612bf98161275b565b915060208301356001600160401b03811115612c13575f80fd5b612b8785828601612946565b5f8060408385031215612c30575f80fd5b8235612c3b8161275b565b9150612c4960208401612a76565b90509250929050565b5f8060408385031215612c63575f80fd5b82359150612c4960208401612a76565b5f805f60608486031215612c85575f80fd5b8335612c908161275b565b925060208401356001600160401b0380821115612cab575f80fd5b612cb7878388016128d3565b93506040860135915080821115612ccc575f80fd5b50612cd9868287016128d3565b9150509250925092565b5f8060408385031215612cf4575f80fd5b8235612cff8161275b565b91506020830135612d0f8161275b565b809150509250929050565b5f805f805f8060c08789031215612d2f575f80fd5b8635612d3a8161275b565b95506020870135612d4a8161275b565b94506040870135612d5a8161275b565b93506060870135925060808701356001600160401b0380821115612d7c575f80fd5b612d888a838b01612946565b935060a0890135915080821115612d9d575f80fd5b50612daa89828a01612946565b9150509295509295509295565b5f805f805f60a08688031215612dcb575f80fd5b8535612dd68161275b565b94506020860135612de68161275b565b9350604086013592506060860135915060808601356001600160401b03811115612e0e575f80fd5b612a4e88828901612946565b600181811c90821680612e2e57607f821691505b602082108103612e4c57634e487b7160e01b5f52602260045260245ffd5b50919050565b6020808252602e908201527f455243313135353a2063616c6c6572206973206e6f7420746f6b656e206f776e60408201526d195c881bdc88185c1c1c9bdd995960921b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b634e487b7160e01b5f52603260045260245ffd5b601f82111561087c57805f5260205f20601f840160051c81016020851015612f715750805b601f840160051c820191505b818110156108c6575f8155600101612f7d565b81516001600160401b03811115612fa957612fa9612871565b612fbd81612fb78454612e1a565b84612f4c565b602080601f831160018114612ff0575f8415612fd95750858301515b5f19600386901b1c1916600185901b178555611526565b5f85815260208120601f198616915b8281101561301e57888601518255948401946001909101908401612fff565b508582101561303b57878501515f19600388901b60f8161c191681555b5050505050600190811b01905550565b670213934b233b2b2160c51b81525f835161306d8160088501602088016127c9565b634051c55b60df1b600891840191820152835161309181600d8401602088016127c9565b602960f81b600d9290910191820152600e01949350505050565b60208082526021908201527f455243313135353a206d696e7420746f20746865207a65726f206164647265736040820152607360f81b606082015260800190565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561061a5761061a6130ec565b60208082526028908201527f455243313135353a2069647320616e6420616d6f756e7473206c656e677468206040820152670dad2e6dac2e8c6d60c31b606082015260800190565b60208082526025908201527f455243313135353a207472616e7366657220746f20746865207a65726f206164604082015264647265737360d81b606082015260800190565b6020808252602a908201527f455243313135353a20696e73756666696369656e742062616c616e636520666f60408201526939103a3930b739b332b960b11b606082015260800190565b604081525f6131fc6040830185612b91565b828103602084015261320e8185612b91565b95945050505050565b5f60208284031215613227575f80fd5b5051919050565b5f6020828403121561323e575f80fd5b81516109b98161275b565b5f825161325a8184602087016127c9565b610b9d60f21b920191825250600201919050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6832ba3432b932bab69d60b91b81525f83516132dc8160098501602088016127c9565b600160fe1b60099184019182015283516132fd81600a8401602088016127c9565b712f746f6b656e5552493f75696e743235363d60701b600a9290910191820152601c01949350505050565b6001600160a01b03868116825285166020820152604081018490526060810183905260a0608082018190525f90613361908301846127eb565b979650505050505050565b5f6020828403121561337c575f80fd5b81516109b981612799565b5f60033d1115610cdc5760045f803e505f5160e01c90565b5f60443d10156133ac5790565b6040516003193d81016004833e81513d6001600160401b0381602484011181841117156133db57505050505090565b82850191508151818111156133f35750505050505090565b843d870101602082850101111561340d5750505050505090565b61341c60208286010187612885565b509095945050505050565b60208082526028908201527f455243313135353a204552433131353552656365697665722072656a656374656040820152676420746f6b656e7360c01b606082015260800190565b6001600160a01b0386811682528516602082015260a0604082018190525f9061349a90830186612b91565b82810360608401526134ac8186612b91565b905082810360808401526134c081856127eb565b98975050505050505050565b808202811582820484141761061a5761061a6130ec565b5f816134f1576134f16130ec565b505f190190565b5f82516135098184602087016127c9565b919091019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220f0bffdb132665b3a5f95537107ba4fba343a91059f2539e704cb09cb4f121f8964736f6c63430008180033", - "balance": "0x0" - }, - "0x0167009000000000000000000000000000000005": { - "contractName": "SignalServiceImpl", - "storage": { - "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190" - }, - "code": "0x60806040526004361061017b575f3560e01c8063715018a6116100cd5780639b527cfa11610087578063e30c397811610062578063e30c397814610479578063f09a401614610496578063f2fde38b146104b5578063fe9fbb80146104d4575f80fd5b80639b527cfa146103fd578063a86f9d9e1461041c578063dfc8ff1d1461043b575f80fd5b8063715018a61461036657806379ba50971461037a5780638456cb591461038e5780638da5cb5b146103a2578063910af6ed146103bf57806391f3f74b146103de575f80fd5b80633eb6b8cf116101385780634f90a674116101135780634f90a674146102e657806352d1902d146103135780635c975abb1461032757806366ca2bc014610347575f80fd5b80633eb6b8cf146102a05780633f4ba83a146102bf5780634f1ef286146102d3575f80fd5b80632d1fb3891461017f57806332676bc6146101a0578063355bcc3d146101d45780633659cfe61461022b5780633ab76e9f1461024a5780633ced0e0814610281575b5f80fd5b34801561018a575f80fd5b5061019e6101993660046135c3565b610502565b005b3480156101ab575f80fd5b506101bf6101ba3660046135f6565b6105a8565b60405190151581526020015b60405180910390f35b3480156101df575f80fd5b506102136101ee366004613636565b60fb60209081525f92835260408084209091529082529020546001600160401b031681565b6040516001600160401b0390911681526020016101cb565b348015610236575f80fd5b5061019e610245366004613650565b6105be565b348015610255575f80fd5b50609754610269906001600160a01b031681565b6040516001600160a01b0390911681526020016101cb565b34801561028c575f80fd5b506101bf61029b36600461366b565b6106a4565b3480156102ab575f80fd5b506102696102ba3660046136ac565b6106eb565b3480156102ca575f80fd5b5061019e610701565b61019e6102e13660046137bc565b610780565b3480156102f1575f80fd5b5061030561030036600461366b565b61084f565b6040519081526020016101cb565b34801561031e575f80fd5b50610305610895565b348015610332575f80fd5b506101bf60c954610100900460ff1660021490565b348015610352575f80fd5b50610305610361366004613808565b610946565b348015610371575f80fd5b5061019e610952565b348015610385575f80fd5b5061019e610963565b348015610399575f80fd5b5061019e6109da565b3480156103ad575f80fd5b506033546001600160a01b0316610269565b3480156103ca575f80fd5b5061019e6103d936600461381f565b610a59565b3480156103e9575f80fd5b506103056103f83660046138b3565b610cf3565b348015610408575f80fd5b506103056104173660046138ef565b610d5e565b348015610427575f80fd5b5061026961043636600461391f565b610d8a565b348015610446575f80fd5b5061045a6104553660046138ef565b610d96565b604080516001600160401b0390931683526020830191909152016101cb565b348015610484575f80fd5b506065546001600160a01b0316610269565b3480156104a1575f80fd5b5061019e6104b0366004613940565b610e2a565b3480156104c0575f80fd5b5061019e6104cf366004613650565b610f39565b3480156104df575f80fd5b506101bf6104ee366004613650565b60fc6020525f908152604090205460ff1681565b61050a610faa565b6001600160a01b0382165f90815260fc602052604090205481151560ff90911615150361054a576040516398f26f4560e01b815260040160405180910390fd5b6001600160a01b0382165f81815260fc6020908152604091829020805460ff191685151590811790915591519182527f4c0079b9bcd37cd5d29a13938effd97c881798cbc6bd52a3026a29d94b27d1bf910160405180910390a25050565b5f6105b38383611004565b151590505b92915050565b6001600160a01b037f000000000000000000000000016700900000000000000000000000000000000516300361060f5760405162461bcd60e51b815260040161060690613977565b60405180910390fd5b7f00000000000000000000000001670090000000000000000000000000000000056001600160a01b03166106575f80516020613fd6833981519152546001600160a01b031690565b6001600160a01b03161461067d5760405162461bcd60e51b8152600401610606906139c3565b61068681611066565b604080515f808252602082019092526106a19183919061106e565b50565b5f818082036106c657604051630426d36960e31b815260040160405180910390fd5b5f6106d2878787610d5e565b9050836106df3083611004565b14979650505050505050565b5f6106f78484846111d8565b90505b9392505050565b61071560c954610100900460ff1660021490565b6107325760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166101001790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a161077e335f6112c4565b565b6001600160a01b037f00000000000000000000000001670090000000000000000000000000000000051630036107c85760405162461bcd60e51b815260040161060690613977565b7f00000000000000000000000001670090000000000000000000000000000000056001600160a01b03166108105f80516020613fd6833981519152546001600160a01b031690565b6001600160a01b0316146108365760405162461bcd60e51b8152600401610606906139c3565b61083f82611066565b61084b8282600161106e565b5050565b335f90815260fc602052604081205460ff1661087e57604051631f67751f60e01b815260040160405180910390fd5b61088a858585856112dd565b90505b949350505050565b5f306001600160a01b037f000000000000000000000000016700900000000000000000000000000000000516146109345760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610606565b505f80516020613fd683398151915290565b5f6105b83383846113bf565b61095a610faa565b61077e5f611498565b60655433906001600160a01b031681146109d15760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b6064820152608401610606565b6106a181611498565b6109ee60c954610100900460ff1660021490565b15610a0c5760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a161077e3360016112c4565b836001600160a01b038116610a81576040516327e0ab1560e21b815260040160405180910390fd5b835f819003610aa357604051630426d36960e31b815260040160405180910390fd5b5f610ab084860186613abe565b905080515f03610ad357604051630b92daef60e21b815260040160405180910390fd5b878787805f610af4856d7369676e616c5f7365727669636560901b836106eb565b9050610b2e6040805160c0810182525f80825260208201819052918101829052906060820190815260200160608152602001606081525090565b5f5b8751811015610cae57878181518110610b4b57610b4b613bfc565b602002602001015191505f610b648888888887896114b1565b90505f60018a51610b759190613c24565b831490508015610bb15783516001600160401b03164614610ba9576040516338bf822760e21b815260040160405180910390fd5b309450610c10565b83516001600160401b03161580610bd1575083516001600160401b031646145b15610bef57604051637556223560e11b815260040160405180910390fd5b8351610c0d906d7369676e616c5f7365727669636560901b5f6106eb565b94505b5f8460800151515f14159050610c2e858b8760200151868587611550565b5f81610c5a577fc6cdc4f2acf13acb10f410085b821f7b7113b303e9a4799023f928317396aaf5610c7c565b7f73e6d340850343cc6f001515dc593377337c95a6ffe034fe1e844d4dab5da1695b9050610c8d8b828860200151610d5e565b985085604001519750855f01519a5086995050505050806001019050610b30565b50821580610cc55750610cc13085611004565b8314155b15610ce35760405163738afa0560e01b815260040160405180910390fd5b5050505050505050505050505050565b6040516514d251d3905360d21b60208201526001600160c01b031960c085901b1660268201526bffffffffffffffffffffffff19606084901b16602e820152604281018290525f906062015b6040516020818303038152906040528051906020012090509392505050565b604080516001600160401b03808616602083015291810184905290821660608201525f90608001610d3f565b5f6106fa4684846111d8565b5f80826001600160401b03165f03610dd2576001600160401b038086165f90815260fb6020908152604080832088845290915290205416610dd4565b825b91506001600160401b03821615610e22575f610df1868685610d5e565b9050610dfd3082611004565b91505f829003610e205760405163738afa0560e01b815260040160405180910390fd5b505b935093915050565b5f54610100900460ff1615808015610e4857505f54600160ff909116105b80610e615750303b158015610e6157505f5460ff166001145b610ec45760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610606565b5f805460ff191660011790558015610ee5575f805461ff0019166101001790555b610eef8383611662565b8015610f34575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050565b610f41610faa565b606580546001600160a01b0383166001600160a01b03199091168117909155610f726033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6033546001600160a01b0316331461077e5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610606565b5f826001600160a01b03811661102d576040516327e0ab1560e21b815260040160405180910390fd5b825f81900361104f57604051630426d36960e31b815260040160405180910390fd5b5f61105b468787610cf3565b549695505050505050565b6106a1610faa565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156110a157610f34836116c1565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156110fb575060408051601f3d908101601f191682019092526110f891810190613c37565b60015b61115e5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610606565b5f80516020613fd683398151915281146111cc5760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610606565b50610f3483838361175c565b6097545f906001600160a01b031661120357604051638ed88b2560e01b815260040160405180910390fd5b609754604051630a3dc4f360e21b81526001600160401b0386166004820152602481018590526001600160a01b03909116906328f713cc90604401602060405180830381865afa158015611259573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061127d9190613c4e565b90508115801561129457506001600160a01b038116155b156106fa57604051632b0d65db60e01b81526001600160401b038516600482015260248101849052604401610606565b60405163198bb9dd60e11b815260040160405180910390fd5b5f6112e9858585610d5e565b90506112f63082846113bf565b506001600160401b038581165f90815260fb6020908152604080832088845290915290205481851691161015611360576001600160401b038581165f90815260fb602090815260408083208884529091529020805467ffffffffffffffff19169185169190911790555b83836001600160401b0316866001600160401b03167fde247c825b1fb2d7ff9e0e771cba6f9e757ad04479fcdc135d88ae91fd50b37d85856040516113af929190918252602082015260400190565b60405180910390a4949350505050565b5f836001600160a01b0381166113e8576040516327e0ab1560e21b815260040160405180910390fd5b835f81900361140a57604051630426d36960e31b815260040160405180910390fd5b835f81900361142c57604051630426d36960e31b815260040160405180910390fd5b611437468888610cf3565b858155604080516001600160a01b038a16815260208101899052908101829052606081018790529094507f0ad2d108660a211f47bf7fb43a0443cae181624995d3d42b88ee6879d200e9739060800160405180910390a15050509392505050565b606580546001600160a01b03191690556106a181611786565b5f856001600160a01b0381166114da576040516327e0ab1560e21b815260040160405180910390fd5b855f8190036114fc57604051630426d36960e31b815260040160405180910390fd5b855f81900361151e57604051630426d36960e31b815260040160405180910390fd5b6115428660400151866115328d8d8d610cf3565b8a8a608001518b60a001516117d7565b9a9950505050505050505050565b5f60038760600151600381111561156957611569613c69565b148061158a575060028760600151600381111561158857611588613c69565b145b90508080156115965750825b80156115a0575081155b156115d7576115d5867f73e6d340850343cc6f001515dc593377337c95a6ffe034fe1e844d4dab5da169878a604001516112dd565b505b5f6003886060015160038111156115f0576115f0613c69565b1480611611575060018860600151600381111561160f5761160f613c69565b145b905080801561162557508380611625575082155b1561165857611656877fc6cdc4f2acf13acb10f410085b821f7b7113b303e9a4799023f928317396aaf588886112dd565b505b5050505050505050565b5f54610100900460ff166116885760405162461bcd60e51b815260040161060690613c7d565b611691826118e4565b6001600160a01b0381166116b8576040516375cabfef60e11b815260040160405180910390fd5b61084b81611914565b6001600160a01b0381163b61172e5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610606565b5f80516020613fd683398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b61176583611984565b5f825111806117715750805b15610f345761178083836119c3565b50505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f82515f1461187f576040516bffffffffffffffffffffffff19606088901b1660208201525f9061181b90603401604051602081830303815290604052858a6119e8565b905080515f0361183e57604051630414cd5b60e31b815260040160405180910390fd5b5f61184882611a0a565b905061186d8160028151811061186057611860613bfc565b6020026020010151611a1d565b61187690613cc8565b92505050611882565b50855b5f6118b98660405160200161189991815260200190565b60408051601f198184030181529190526118b287611b3d565b8585611b50565b9050806118d957604051638d9a4db360e01b815260040160405180910390fd5b509695505050505050565b6119026001600160a01b038216156118fc5781611498565b33611498565b5060c9805461ff001916610100179055565b5f54610100900460ff1661193a5760405162461bcd60e51b815260040161060690613c7d565b6001600160401b034611156119625760405163a12e8fa960e01b815260040160405180910390fd5b609780546001600160a01b0319166001600160a01b0392909216919091179055565b61198d816116c1565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606106fa8383604051806060016040528060278152602001613ff660279139611b73565b60605f6119f485611bdd565b9050611a01818585611c0f565b95945050505050565b60606105b8611a188361247c565b6124cd565b60605f805f611a2b856126ea565b919450925090505f816001811115611a4557611a45613c69565b14611ab85760405162461bcd60e51b815260206004820152603960248201527f524c505265616465723a206465636f646564206974656d207479706520666f7260448201527f206279746573206973206e6f7420612064617461206974656d000000000000006064820152608401610606565b611ac28284613cee565b855114611b2e5760405162461bcd60e51b815260206004820152603460248201527f524c505265616465723a2062797465732076616c756520636f6e7461696e732060448201527330b71034b73b30b634b2103932b6b0b4b73232b960611b6064820152608401610606565b611a0185602001518484612d91565b60606105b8611b4b83612e21565b612f3c565b5f80611b5b86611bdd565b9050611b6981868686612f94565b9695505050505050565b60605f80856001600160a01b031685604051611b8f9190613d23565b5f60405180830381855af49150503d805f8114611bc7576040519150601f19603f3d011682016040523d82523d5f602084013e611bcc565b606091505b5091509150611b6986838387612fba565b60608180519060200120604051602001611bf991815260200190565b6040516020818303038152906040529050919050565b60605f845111611c595760405162461bcd60e51b81526020600482015260156024820152744d65726b6c65547269653a20656d707479206b657960581b6044820152606401610606565b5f611c6384613032565b90505f611c6f8661311c565b90505f84604051602001611c8591815260200190565b60405160208183030381529060405290505f805b8451811015612425575f858281518110611cb557611cb5613bfc565b602002602001015190508451831115611d275760405162461bcd60e51b815260206004820152602e60248201527f4d65726b6c65547269653a206b657920696e646578206578636565647320746f60448201526d0e8c2d840d6caf240d8cadccee8d60931b6064820152608401610606565b825f03611dc55780518051602091820120604051611d7492611d4e92910190815260200190565b604051602081830303815290604052858051602091820120825192909101919091201490565b611dc05760405162461bcd60e51b815260206004820152601d60248201527f4d65726b6c65547269653a20696e76616c696420726f6f7420686173680000006044820152606401610606565b611ebb565b805151602011611e4b5780518051602091820120604051611def92611d4e92910190815260200190565b611dc05760405162461bcd60e51b815260206004820152602760248201527f4d65726b6c65547269653a20696e76616c6964206c6172676520696e7465726e6044820152660c2d840d0c2e6d60cb1b6064820152608401610606565b805184516020808701919091208251919092012014611ebb5760405162461bcd60e51b815260206004820152602660248201527f4d65726b6c65547269653a20696e76616c696420696e7465726e616c206e6f646044820152650ca40d0c2e6d60d31b6064820152608401610606565b611ec760106001613cee565b8160200151510361205f5784518303611ff957611ef4816020015160108151811061186057611860613bfc565b96505f875111611f6c5760405162461bcd60e51b815260206004820152603b60248201527f4d65726b6c65547269653a2076616c7565206c656e677468206d75737420626560448201527f2067726561746572207468616e207a65726f20286272616e63682900000000006064820152608401610606565b60018651611f7a9190613c24565b8214611fee5760405162461bcd60e51b815260206004820152603a60248201527f4d65726b6c65547269653a2076616c7565206e6f6465206d757374206265206c60448201527f617374206e6f646520696e2070726f6f6620286272616e6368290000000000006064820152608401610606565b5050505050506106fa565b5f85848151811061200c5761200c613bfc565b602001015160f81c60f81b60f81c90505f82602001518260ff168151811061203657612036613bfc565b602002602001015190506120498161317d565b9550612056600186613cee565b9450505061241c565b6002816020015151036123c3575f612076826131a1565b90505f815f8151811061208b5761208b613bfc565b016020015160f81c90505f6120a1600283613d52565b6120ac906002613d73565b90505f6120bc848360ff166131c4565b90505f6120c98a896131c4565b90505f6120d683836131f9565b90508083511461214e5760405162461bcd60e51b815260206004820152603a60248201527f4d65726b6c65547269653a20706174682072656d61696e646572206d7573742060448201527f736861726520616c6c206e6962626c65732077697468206b65790000000000006064820152608401610606565b60ff851660021480612163575060ff85166003145b1561230357808251146121de5760405162461bcd60e51b815260206004820152603d60248201527f4d65726b6c65547269653a206b65792072656d61696e646572206d757374206260448201527f65206964656e746963616c20746f20706174682072656d61696e6465720000006064820152608401610606565b6121f8876020015160018151811061186057611860613bfc565b9c505f8d51116122705760405162461bcd60e51b815260206004820152603960248201527f4d65726b6c65547269653a2076616c7565206c656e677468206d75737420626560448201527f2067726561746572207468616e207a65726f20286c65616629000000000000006064820152608401610606565b60018c5161227e9190613c24565b88146122f25760405162461bcd60e51b815260206004820152603860248201527f4d65726b6c65547269653a2076616c7565206e6f6465206d757374206265206c60448201527f617374206e6f646520696e2070726f6f6620286c6561662900000000000000006064820152608401610606565b5050505050505050505050506106fa565b60ff85161580612316575060ff85166001145b1561235557612342876020015160018151811061233557612335613bfc565b602002602001015161317d565b995061234e818a613cee565b98506123b8565b60405162461bcd60e51b815260206004820152603260248201527f4d65726b6c65547269653a2072656365697665642061206e6f64652077697468604482015271040c2dc40eadcd6dcdeeedc40e0e4caccd2f60731b6064820152608401610606565b50505050505061241c565b60405162461bcd60e51b815260206004820152602860248201527f4d65726b6c65547269653a20726563656976656420616e20756e706172736561604482015267626c65206e6f646560c01b6064820152608401610606565b50600101611c99565b5060405162461bcd60e51b815260206004820152602560248201527f4d65726b6c65547269653a2072616e206f7574206f662070726f6f6620656c656044820152646d656e747360d81b6064820152608401610606565b604080518082019091525f80825260208201525f8251116124af5760405162461bcd60e51b815260040161060690613d8c565b50604080518082019091528151815260209182019181019190915290565b60605f805f6124db856126ea565b9194509250905060018160018111156124f6576124f6613c69565b146125695760405162461bcd60e51b815260206004820152603860248201527f524c505265616465723a206465636f646564206974656d207479706520666f7260448201527f206c697374206973206e6f742061206c697374206974656d00000000000000006064820152608401610606565b84516125758385613cee565b146125dd5760405162461bcd60e51b815260206004820152603260248201527f524c505265616465723a206c697374206974656d2068617320616e20696e76616044820152713634b2103230ba30903932b6b0b4b73232b960711b6064820152608401610606565b604080516020808252610420820190925290816020015b604080518082019091525f80825260208201528152602001906001900390816125f45790505093505f835b86518110156126de575f806126636040518060400160405280858c5f01516126479190613c24565b8152602001858c6020015161265c9190613cee565b90526126ea565b50915091506040518060400160405280838361267f9190613cee565b8152602001848b602001516126949190613cee565b8152508885815181106126a9576126a9613bfc565b60209081029190910101526126bf600185613cee565b93506126cb8183613cee565b6126d59084613cee565b9250505061261f565b50845250919392505050565b5f805f80845f01511161270f5760405162461bcd60e51b815260040161060690613d8c565b602084015180515f1a607f8111612731575f60015f9450945094505050612d8a565b60b7811161288a575f612745608083613c24565b905080875f0151116127c35760405162461bcd60e51b815260206004820152604e60248201525f80516020613fb683398151915260448201527f742062652067726561746572207468616e20737472696e67206c656e6774682060648201526d2873686f727420737472696e672960901b608482015260a401610606565b6001838101516001600160f81b03191690821415806127f05750600160ff1b6001600160f81b0319821610155b6128785760405162461bcd60e51b815260206004820152604d60248201527f524c505265616465723a20696e76616c6964207072656669782c2073696e676c60448201527f652062797465203c203078383020617265206e6f74207072656669786564202860648201526c73686f727420737472696e672960981b608482015260a401610606565b506001955093505f9250612d8a915050565b60bf8111612ac3575f61289e60b783613c24565b905080875f01511161291f5760405162461bcd60e51b815260206004820152605160248201525f80516020613fb683398151915260448201527f74206265203e207468616e206c656e677468206f6620737472696e67206c656e60648201527067746820286c6f6e6720737472696e672960781b608482015260a401610606565b60018301516001600160f81b0319165f8190036129a45760405162461bcd60e51b815260206004820152604a60248201525f80516020613fb683398151915260448201527f74206e6f74206861766520616e79206c656164696e67207a65726f7320286c6f6064820152696e6720737472696e672960b01b608482015260a401610606565b600184015160088302610100031c60378111612a265760405162461bcd60e51b815260206004820152604860248201525f80516020613fb683398151915260448201527f742062652067726561746572207468616e20353520627974657320286c6f6e6760648201526720737472696e672960c01b608482015260a401610606565b612a308184613cee565b895111612aa75760405162461bcd60e51b815260206004820152604c60248201525f80516020613fb683398151915260448201527f742062652067726561746572207468616e20746f74616c206c656e677468202860648201526b6c6f6e6720737472696e672960a01b608482015260a401610606565b612ab2836001613cee565b975095505f9450612d8a9350505050565b60f78111612b62575f612ad760c083613c24565b905080875f015111612b515760405162461bcd60e51b815260206004820152604a60248201525f80516020613fb683398151915260448201527f742062652067726561746572207468616e206c697374206c656e677468202873606482015269686f7274206c6973742960b01b608482015260a401610606565b600195509350849250612d8a915050565b5f612b6e60f783613c24565b905080875f015111612beb5760405162461bcd60e51b815260206004820152604d60248201525f80516020613fb683398151915260448201527f74206265203e207468616e206c656e677468206f66206c697374206c656e677460648201526c6820286c6f6e67206c6973742960981b608482015260a401610606565b60018301516001600160f81b0319165f819003612c6e5760405162461bcd60e51b815260206004820152604860248201525f80516020613fb683398151915260448201527f74206e6f74206861766520616e79206c656164696e67207a65726f7320286c6f6064820152676e67206c6973742960c01b608482015260a401610606565b600184015160088302610100031c60378111612cee5760405162461bcd60e51b815260206004820152604660248201525f80516020613fb683398151915260448201527f742062652067726561746572207468616e20353520627974657320286c6f6e67606482015265206c6973742960d01b608482015260a401610606565b612cf88184613cee565b895111612d6d5760405162461bcd60e51b815260206004820152604a60248201525f80516020613fb683398151915260448201527f742062652067726561746572207468616e20746f74616c206c656e67746820286064820152696c6f6e67206c6973742960b01b608482015260a401610606565b612d78836001613cee565b9750955060019450612d8a9350505050565b9193909250565b6060816001600160401b03811115612dab57612dab6136e5565b6040519080825280601f01601f191660200182016040528015612dd5576020820181803683370190505b50905081156106fa575f612de98486613cee565b9050602082015f5b84811015612e09578281015182820152602001612df1565b84811115612e17575f858301525b5050509392505050565b60605f82604051602001612e3791815260200190565b60405160208183030381529060405290505f5b6020811015612e8257818181518110612e6557612e65613bfc565b01602001516001600160f81b0319165f03612e8257600101612e4a565b612e8d816020613c24565b6001600160401b03811115612ea457612ea46136e5565b6040519080825280601f01601f191660200182016040528015612ece576020820181803683370190505b5092505f5b8351811015612f34578282612ee781613dfc565b935081518110612ef957612ef9613bfc565b602001015160f81c60f81b848281518110612f1657612f16613bfc565b60200101906001600160f81b03191690815f1a905350600101612ed3565b505050919050565b606081516001148015612f6857506080825f81518110612f5e57612f5e613bfc565b016020015160f81c105b15612f71575090565b612f7d8251608061327c565b82604051602001611bf9929190613e14565b919050565b5f61088a84612fa4878686611c0f565b8051602091820120825192909101919091201490565b606083156130285782515f03613021576001600160a01b0385163b6130215760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610606565b508161088d565b61088d8383613421565b8051606090806001600160401b0381111561304f5761304f6136e5565b60405190808252806020026020018201604052801561309457816020015b604080518082019091526060808252602082015281526020019060019003908161306d5790505b5091505f5b818110156131155760405180604001604052808583815181106130be576130be613bfc565b602002602001015181526020016130ed8684815181106130e0576130e0613bfc565b6020026020010151611a0a565b81525083828151811061310257613102613bfc565b6020908102919091010152600101613099565b5050919050565b606080604051905082518060011b603f8101601f191683016040528083525060208401602083015f5b83811015613172578060011b8201818401515f1a8060041c8253600f811660018301535050600101613145565b509295945050505050565b60606020825f0151106131985761319382611a1d565b6105b8565b6105b88261344b565b60606105b86131bf83602001515f8151811061186057611860613bfc565b61311c565b6060825182106131e2575060408051602081019091525f81526105b8565b6106fa83838486516131f49190613c24565b61345f565b5f80825184511061320b57825161320e565b83515b90505b8082108015613265575082828151811061322d5761322d613bfc565b602001015160f81c60f81b6001600160f81b03191684838151811061325457613254613bfc565b01602001516001600160f81b031916145b1561327557816001019150613211565b5092915050565b606060388310156132e057604080516001808252818301909252906020820181803683370190505090506132b08284613e42565b60f81b815f815181106132c5576132c5613bfc565b60200101906001600160f81b03191690815f1a9053506105b8565b5f60015b6132ee8186613e5b565b1561331457816132fd81613dfc565b925061330d905061010082613e6e565b90506132e4565b61331f826001613cee565b6001600160401b03811115613336576133366136e5565b6040519080825280601f01601f191660200182016040528015613360576020820181803683370190505b50925061336d8483613e42565b613378906037613e42565b60f81b835f8151811061338d5761338d613bfc565b60200101906001600160f81b03191690815f1a905350600190505b818111613419576101006133bc8284613c24565b6133c890610100613f65565b6133d29087613e5b565b6133dc9190613f70565b60f81b8382815181106133f1576133f1613bfc565b60200101906001600160f81b03191690815f1a9053508061341181613dfc565b9150506133a8565b505092915050565b8151156134315781518083602001fd5b8060405162461bcd60e51b81526004016106069190613f83565b60606105b882602001515f845f0151612d91565b60608182601f0110156134a55760405162461bcd60e51b815260206004820152600e60248201526d736c6963655f6f766572666c6f7760901b6044820152606401610606565b8282840110156134e85760405162461bcd60e51b815260206004820152600e60248201526d736c6963655f6f766572666c6f7760901b6044820152606401610606565b8183018451101561352f5760405162461bcd60e51b8152602060048201526011602482015270736c6963655f6f75744f66426f756e647360781b6044820152606401610606565b60608215801561354d5760405191505f825260208201604052613597565b6040519150601f8416801560200281840101858101878315602002848b0101015b8183101561358657805183526020928301920161356e565b5050858452601f01601f1916604052505b50949350505050565b6001600160a01b03811681146106a1575f80fd5b80358015158114612f8f575f80fd5b5f80604083850312156135d4575f80fd5b82356135df816135a0565b91506135ed602084016135b4565b90509250929050565b5f8060408385031215613607575f80fd5b8235613612816135a0565b946020939093013593505050565b80356001600160401b0381168114612f8f575f80fd5b5f8060408385031215613647575f80fd5b61361283613620565b5f60208284031215613660575f80fd5b81356106fa816135a0565b5f805f806080858703121561367e575f80fd5b61368785613620565b93506020850135925061369c60408601613620565b9396929550929360600135925050565b5f805f606084860312156136be575f80fd5b6136c784613620565b9250602084013591506136dc604085016135b4565b90509250925092565b634e487b7160e01b5f52604160045260245ffd5b60405160c081016001600160401b038111828210171561371b5761371b6136e5565b60405290565b604051601f8201601f191681016001600160401b0381118282101715613749576137496136e5565b604052919050565b5f82601f830112613760575f80fd5b81356001600160401b03811115613779576137796136e5565b61378c601f8201601f1916602001613721565b8181528460208386010111156137a0575f80fd5b816020850160208301375f918101602001919091529392505050565b5f80604083850312156137cd575f80fd5b82356137d8816135a0565b915060208301356001600160401b038111156137f2575f80fd5b6137fe85828601613751565b9150509250929050565b5f60208284031215613818575f80fd5b5035919050565b5f805f805f60808688031215613833575f80fd5b61383c86613620565b9450602086013561384c816135a0565b93506040860135925060608601356001600160401b038082111561386e575f80fd5b818801915088601f830112613881575f80fd5b81358181111561388f575f80fd5b8960208285010111156138a0575f80fd5b9699959850939650602001949392505050565b5f805f606084860312156138c5575f80fd5b6138ce84613620565b925060208401356138de816135a0565b929592945050506040919091013590565b5f805f60608486031215613901575f80fd5b61390a84613620565b9250602084013591506136dc60408501613620565b5f8060408385031215613930575f80fd5b823591506135ed602084016135b4565b5f8060408385031215613951575f80fd5b823561395c816135a0565b9150602083013561396c816135a0565b809150509250929050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b5f6001600160401b03821115613a2757613a276136e5565b5060051b60200190565b803560048110612f8f575f80fd5b5f82601f830112613a4e575f80fd5b81356020613a63613a5e83613a0f565b613721565b82815260059290921b84018101918181019086841115613a81575f80fd5b8286015b848110156118d95780356001600160401b03811115613aa2575f80fd5b613ab08986838b0101613751565b845250918301918301613a85565b5f6020808385031215613acf575f80fd5b82356001600160401b0380821115613ae5575f80fd5b818501915085601f830112613af8575f80fd5b8135613b06613a5e82613a0f565b81815260059190911b83018401908481019088831115613b24575f80fd5b8585015b83811015613bef57803585811115613b3e575f80fd5b860160c0818c03601f19011215613b53575f80fd5b613b5b6136f9565b613b66898301613620565b81526040613b75818401613620565b8a8301526060808401358284015260809150613b92828501613a31565b9083015260a08381013589811115613ba8575f80fd5b613bb68f8d83880101613a3f565b838501525060c0840135915088821115613bce575f80fd5b613bdc8e8c84870101613a3f565b9083015250845250918601918601613b28565b5098975050505050505050565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b818103818111156105b8576105b8613c10565b5f60208284031215613c47575f80fd5b5051919050565b5f60208284031215613c5e575f80fd5b81516106fa816135a0565b634e487b7160e01b5f52602160045260245ffd5b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b80516020808301519190811015613ce8575f198160200360031b1b821691505b50919050565b808201808211156105b8576105b8613c10565b5f5b83811015613d1b578181015183820152602001613d03565b50505f910152565b5f8251613d34818460208701613d01565b9190910192915050565b634e487b7160e01b5f52601260045260245ffd5b5f60ff831680613d6457613d64613d3e565b8060ff84160691505092915050565b60ff82811682821603908111156105b8576105b8613c10565b6020808252604a908201527f524c505265616465723a206c656e677468206f6620616e20524c50206974656d60408201527f206d7573742062652067726561746572207468616e207a65726f20746f206265606082015269206465636f6461626c6560b01b608082015260a00190565b5f60018201613e0d57613e0d613c10565b5060010190565b5f8351613e25818460208801613d01565b835190830190613e39818360208801613d01565b01949350505050565b60ff81811683821601908111156105b8576105b8613c10565b5f82613e6957613e69613d3e565b500490565b80820281158282048414176105b8576105b8613c10565b600181815b80851115613ebf57815f1904821115613ea557613ea5613c10565b80851615613eb257918102915b93841c9390800290613e8a565b509250929050565b5f82613ed5575060016105b8565b81613ee157505f6105b8565b8160018114613ef75760028114613f0157613f1d565b60019150506105b8565b60ff841115613f1257613f12613c10565b50506001821b6105b8565b5060208310610133831016604e8410600b8410161715613f40575081810a6105b8565b613f4a8383613e85565b805f1904821115613f5d57613f5d613c10565b029392505050565b5f6106fa8383613ec7565b5f82613f7e57613f7e613d3e565b500690565b602081525f8251806020840152613fa1816040850160208701613d01565b601f01601f1916919091016040019291505056fe524c505265616465723a206c656e677468206f6620636f6e74656e74206d7573360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122026509c7d0962754acadcf1ba2e83cec7c8d29b86f2088c00b216a1d1edf88cb264736f6c63430008180033", - "balance": "0x0" - }, - "0x1670090000000000000000000000000000000005": { - "contractName": "SignalService", - "storage": { - "0x00000000000000000000000000000000000000000000000000000000000000c9": "0x0000000000000000000000000000000000000000000000000000000000000101", - "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190", - "0x0000000000000000000000000000000000000000000000000000000000000097": "0x0000000000000000000000001670090000000000000000000000000000000006", - "0x031b527db15e04fbfff38f22f062b0041c6d7dbbf534c333afd08c8be13df0c6": "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0167009000000000000000000000000000000005" - }, - "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156070573d5ff35b3d5ffdfea2646970667358221220ee2b34631d0aeafcbca8c09b3d006837becb1e761da7ef1dfde69d83e98a206164736f6c63430008180033", - "balance": "0x0" - }, - "0x0167009000000000000000000000000000010001": { - "contractName": "TaikoL2Impl", - "storage": { - "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190" - }, - "code": "0x6080604052600436106101af575f3560e01c806379ba5097116100e7578063c3f909d411610087578063e30c397811610062578063e30c3978146104f7578063f2fde38b14610514578063f535bd5614610533578063f940e38514610552575f80fd5b8063c3f909d41461047b578063da69d3db146104c3578063dac5df78146104e2575f80fd5b80638da5cb5b116100c25780638da5cb5b146103de5780639ee512f2146103fb578063a7e022d114610420578063a86f9d9e1461045c575f80fd5b806379ba50971461038b5780638456cb591461039f5780638551f41e146103b3575f80fd5b80633f4ba83a11610152578063539b8ade1161012d578063539b8ade146103125780635950f9f1146103385780635c975abb14610357578063715018a614610377575f80fd5b80633f4ba83a146102d75780634f1ef286146102eb57806352d1902d146102fe575f80fd5b806333d5ac9b1161018d57806333d5ac9b1461023a5780633659cfe6146102605780633ab76e9f146102815780633eb6b8cf146102b8575f80fd5b806312622e5b146101b357806323ac7136146101ef5780632f9804731461021c575b5f80fd5b3480156101be575f80fd5b5060fe546101d2906001600160401b031681565b6040516001600160401b0390911681526020015b60405180910390f35b3480156101fa575f80fd5b5061020e610209366004611ece565b610571565b6040519081526020016101e6565b348015610227575f80fd5b505f5b60405190151581526020016101e6565b348015610245575f80fd5b5060fd546101d290600160401b90046001600160401b031681565b34801561026b575f80fd5b5061027f61027a366004611efb565b6105cd565b005b34801561028c575f80fd5b506097546102a0906001600160a01b031681565b6040516001600160a01b0390911681526020016101e6565b3480156102c3575f80fd5b506102a06102d2366004611f23565b6106b3565b3480156102e2575f80fd5b5061027f6106c9565b61027f6102f9366004611f74565b610748565b348015610309575f80fd5b5061020e610817565b34801561031d575f80fd5b5060fd546101d290600160801b90046001600160401b031681565b348015610343575f80fd5b5061027f610352366004612031565b6108c8565b348015610362575f80fd5b5061022a60c954610100900460ff1660021490565b348015610382575f80fd5b5061027f610af1565b348015610396575f80fd5b5061027f610b02565b3480156103aa575f80fd5b5061027f610b79565b3480156103be575f80fd5b5061020e6103cd366004612086565b60fb6020525f908152604090205481565b3480156103e9575f80fd5b506033546001600160a01b03166102a0565b348015610406575f80fd5b506102a071777735367b36bc9b61c50022d9d0700db4ec81565b34801561042b575f80fd5b5061043f61043a3660046120b0565b610bf8565b604080519283526001600160401b039091166020830152016101e6565b348015610467575f80fd5b506102a06104763660046120e1565b610c6d565b348015610486575f80fd5b5061048f610c82565b60408051825163ffffffff16815260208084015160ff1690820152918101516001600160401b0316908201526060016101e6565b3480156104ce575f80fd5b5061027f6104dd36600461210f565b610cce565b3480156104ed575f80fd5b5061020e60fc5481565b348015610502575f80fd5b506065546001600160a01b03166102a0565b34801561051f575f80fd5b5061027f61052e366004611efb565b610fd7565b34801561053e575f80fd5b5060fd546101d2906001600160401b031681565b34801561055d575f80fd5b5061027f61056c366004612147565b611048565b5f43826001600160401b03161061058957505f919050565b4361059683610100612187565b6001600160401b0316106105b257506001600160401b03164090565b506001600160401b03165f90815260fb602052604090205490565b6001600160a01b037f000000000000000000000000016700900000000000000000000000000001000116300361061e5760405162461bcd60e51b8152600401610615906121ae565b60405180910390fd5b7f00000000000000000000000001670090000000000000000000000000000100016001600160a01b03166106665f80516020612424833981519152546001600160a01b031690565b6001600160a01b03161461068c5760405162461bcd60e51b8152600401610615906121fa565b61069581611207565b604080515f808252602082019092526106b09183919061120f565b50565b5f6106bf848484611379565b90505b9392505050565b6106dd60c954610100900460ff1660021490565b6106fa5760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166101001790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610746335f611465565b565b6001600160a01b037f00000000000000000000000001670090000000000000000000000000000100011630036107905760405162461bcd60e51b8152600401610615906121ae565b7f00000000000000000000000001670090000000000000000000000000000100016001600160a01b03166107d85f80516020612424833981519152546001600160a01b031690565b6001600160a01b0316146107fe5760405162461bcd60e51b8152600401610615906121fa565b61080782611207565b6108138282600161120f565b5050565b5f306001600160a01b037f000000000000000000000000016700900000000000000000000000000001000116146108b65760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610615565b505f8051602061242483398151915290565b5f54610100900460ff16158080156108e657505f54600160ff909116105b806108ff5750303b1580156108ff57505f5460ff166001145b6109625760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610615565b5f805460ff191660011790558015610983575f805461ff0019166101001790555b61098d858561146d565b6001600160401b03831615806109ab575046836001600160401b0316145b156109c9576040516308279a2560e31b815260040160405180910390fd5b6001461115806109df57506001600160401b0346115b156109fd57604051638f972ecb60e01b815260040160405180910390fd5b4315610a475743600103610a2e575f610a17600143612246565b5f81815260fb602052604090209040905550610a47565b604051635a0f9e4160e11b815260040160405180910390fd5b60fe80546001600160401b0380861667ffffffffffffffff199283161790925560fd805492851692909116919091179055610a81436114cc565b5060fc5560fd80546001600160c01b0316600160c01b426001600160401b0316021790558015610aea575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b610af961155c565b6107465f6115b6565b60655433906001600160a01b03168114610b705760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b6064820152608401610615565b6106b0816115b6565b610b8d60c954610100900460ff1660021490565b15610bab5760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a1610746336001611465565b5f805f610c03610c82565b805160fd549192505f9163ffffffff90911690610c3090600160401b90046001600160401b031688612259565b610c3a9190612279565b82516020840151604085015160fd54939450610c60936001600160401b0316858a6115cf565b9097909650945050505050565b5f610c79468484611379565b90505b92915050565b604080516060810182525f8082526020820181905291810191909152610cc960408051606081018252630393870081526008602082015264044acfc6c09181019190915290565b905090565b6002610cdc60c95460ff1690565b60ff1603610cfd5760405163dfc60d8560e01b815260040160405180910390fd5b610d07600261167a565b831580610d12575082155b80610d2457506001600160401b038216155b80610d3f575043600114158015610d3f575063ffffffff8116155b15610d5d5760405163053fd54760e01b815260040160405180910390fd5b3371777735367b36bc9b61c50022d9d0700db4ec14610d8f57604051636494e9f760e01b815260040160405180910390fd5b5f1943015f80610d9e836114cc565b915091508160fc5414610dc45760405163d719258d60e01b815260040160405180910390fd5b5f610dcf8686610bf8565b60fd805467ffffffffffffffff19166001600160401b03929092169190911790559050488114610e12576040516336d54d4f60e11b815260040160405180910390fd5b60fd546001600160401b03600160401b90910481169087161115610f2257610e4b6d7369676e616c5f7365727669636560901b5f610c6d565b60fe546040516313e4299d60e21b81526001600160401b0391821660048201527f73e6d340850343cc6f001515dc593377337c95a6ffe034fe1e844d4dab5da16960248201529088166044820152606481018990526001600160a01b039190911690634f90a674906084016020604051808303815f875af1158015610ed2573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ef691906122a4565b5060fd80546fffffffffffffffff00000000000000001916600160401b6001600160401b038916021790555b5f84815260fb602090815260409182902086409081905560fc85905560fd80546fffffffffffffffffffffffffffffffff8116600160c01b918290046001600160401b03908116600160801b026001600160c01b031691909117428216929092029190911791829055845192835216918101919091527f41c3f410f5c8ac36bb46b1dccef0de0f964087c9e688795fa02ecfa2c20b3fe4910160405180910390a150505050610fd1600161167a565b50505050565b610fdf61155c565b606580546001600160a01b0383166001600160a01b031990911681179091556110106033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b693bb4ba34323930bbb2b960b11b6110686033546001600160a01b031690565b6001600160a01b0316336001600160a01b0316141580156110a5575061108f816001610c6d565b6001600160a01b0316336001600160a01b031614155b156110c357604051630d85cccf60e11b815260040160405180910390fd5b60026110d160c95460ff1690565b60ff16036110f25760405163dfc60d8560e01b815260040160405180910390fd5b6110fc600261167a565b61111060c954610100900460ff1660021490565b1561112e5760405163bae6e2a960e01b815260040160405180910390fd5b6001600160a01b0382166111555760405163053fd54760e01b815260040160405180910390fd5b6001600160a01b03831661117b576111766001600160a01b03831647611690565b6111f8565b6040516370a0823160e01b81523060048201526111f89083906001600160a01b038616906370a0823190602401602060405180830381865afa1580156111c3573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111e791906122a4565b6001600160a01b038616919061169b565b611202600161167a565b505050565b6106b061155c565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff161561124257611202836116ed565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa92505050801561129c575060408051601f3d908101601f19168201909252611299918101906122a4565b60015b6112ff5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610615565b5f80516020612424833981519152811461136d5760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610615565b50611202838383611788565b6097545f906001600160a01b03166113a457604051638ed88b2560e01b815260040160405180910390fd5b609754604051630a3dc4f360e21b81526001600160401b0386166004820152602481018590526001600160a01b03909116906328f713cc90604401602060405180830381865afa1580156113fa573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061141e91906122bb565b90508115801561143557506001600160a01b038116155b156106c257604051632b0d65db60e01b81526001600160401b038516600482015260248101849052604401610615565b61081361155c565b5f54610100900460ff166114935760405162461bcd60e51b8152600401610615906122d6565b61149c826117ac565b6001600160a01b0381166114c3576040516375cabfef60e11b815260040160405180910390fd5b610813816117dc565b5f806114d6611e93565b5f5b60ff811080156114eb5750806001018510155b1561151c575f198186030180408360ff8306610100811061150e5761150e612335565b6020020152506001016114d8565b5046611fe08201526120008120925083408161153960ff87612349565b610100811061154a5761154a612335565b60200201526120009020919391925050565b6033546001600160a01b031633146107465760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610615565b606580546001600160a01b03191690556106b08161184c565b5f80806115eb63ffffffff85166001600160401b03881661235c565b9050846001600160401b03168111611604576001611617565b6116176001600160401b03861682612246565b90506116396001600160401b038089169061163390849061189d565b906118b1565b91506116606001600160401b03831661165b63ffffffff8c1660ff8c1661236f565b6118c6565b9250825f0361166e57600192505b50965096945050505050565b60c9805460ff191660ff92909216919091179055565b61081382825a61190f565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b179052611202908490611952565b6001600160a01b0381163b61175a5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610615565b5f8051602061242483398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b61179183611a25565b5f8251118061179d5750805b1561120257610fd18383611a64565b6117ca6001600160a01b038216156117c457816115b6565b336115b6565b5060c9805461ff001916610100179055565b5f54610100900460ff166118025760405162461bcd60e51b8152600401610615906122d6565b6001600160401b0346111561182a5760405163a12e8fa960e01b815260040160405180910390fd5b609780546001600160a01b0319166001600160a01b0392909216919091179055565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f8183116118ab5782610c79565b50919050565b5f8183116118bf5781610c79565b5090919050565b5f815f036118e757604051636296f1b960e11b815260040160405180910390fd5b81670de0b6b3a76400006118fb8585611a89565b6119059190612386565b610c799190612386565b815f0361191b57505050565b61193583838360405180602001604052805f815250611ad8565b61120257604051634c67134d60e11b815260040160405180910390fd5b5f6119a6826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316611b159092919063ffffffff16565b905080515f14806119c65750808060200190518101906119c69190612399565b6112025760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610615565b611a2e816116ed565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060610c79838360405180606001604052806027815260200161244460279139611b23565b5f8082611a9e670de0b6b3a76400008661236f565b611aa89190612386565b9050680755bf798b4a1bf1e4811115611ac75750680755bf798b4a1bf1e45b611ad081611b97565b949350505050565b5f6001600160a01b038516611b0057604051634c67134d60e11b815260040160405180910390fd5b5f80835160208501878988f195945050505050565b60606106bf84845f85611d1f565b60605f80856001600160a01b031685604051611b3f91906123d6565b5f60405180830381855af49150503d805f8114611b77576040519150601f19603f3d011682016040523d82523d5f602084013e611b7c565b606091505b5091509150611b8d86838387611df6565b9695505050505050565b5f680248ce36a70cb26b3e198213611bb057505f919050565b680755bf798b4a1bf1e58212611bd957604051631a93c68960e11b815260040160405180910390fd5b6503782dace9d9604e83901b0591505f60606bb17217f7d1cf79abc9e3b39884821b056001605f1b01901d6bb17217f7d1cf79abc9e3b39881029093036c240c330e9fb2d9cbaf0fd5aafb1981018102606090811d6d0277594991cfc85f6e2461837cd9018202811d6d1a521255e34f6a5061b25ef1c9c319018202811d6db1bbb201f443cf962f1a1d3db4a5018202811d6e02c72388d9f74f51a9331fed693f1419018202811d6e05180bb14799ab47a8a8cb2a527d57016d02d16720577bd19bf614176fe9ea6c10fe68e7fd37d0007b713f765084018402831d9081019084016d01d3967ed30fc4f89c02bab5708119010290911d6e0587f503bb6ea29d25fcb740196450019091026d360d7aeea093263ecc6e0ecb291760621b010574029d9dc38563c32e5c2f6dc192ee70ef65f9978af30260c3939093039290921c92915050565b606082471015611d805760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610615565b5f80866001600160a01b03168587604051611d9b91906123d6565b5f6040518083038185875af1925050503d805f8114611dd5576040519150601f19603f3d011682016040523d82523d5f602084013e611dda565b606091505b5091509150611deb87838387611df6565b979650505050505050565b60608315611e645782515f03611e5d576001600160a01b0385163b611e5d5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610615565b5081611ad0565b611ad08383815115611e795781518083602001fd5b8060405162461bcd60e51b815260040161061591906123f1565b604051806120000160405280610100906020820280368337509192915050565b80356001600160401b0381168114611ec9575f80fd5b919050565b5f60208284031215611ede575f80fd5b610c7982611eb3565b6001600160a01b03811681146106b0575f80fd5b5f60208284031215611f0b575f80fd5b81356106c281611ee7565b80151581146106b0575f80fd5b5f805f60608486031215611f35575f80fd5b611f3e84611eb3565b9250602084013591506040840135611f5581611f16565b809150509250925092565b634e487b7160e01b5f52604160045260245ffd5b5f8060408385031215611f85575f80fd5b8235611f9081611ee7565b915060208301356001600160401b0380821115611fab575f80fd5b818501915085601f830112611fbe575f80fd5b813581811115611fd057611fd0611f60565b604051601f8201601f19908116603f01168101908382118183101715611ff857611ff8611f60565b81604052828152886020848701011115612010575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f805f8060808587031215612044575f80fd5b843561204f81611ee7565b9350602085013561205f81611ee7565b925061206d60408601611eb3565b915061207b60608601611eb3565b905092959194509250565b5f60208284031215612096575f80fd5b5035919050565b803563ffffffff81168114611ec9575f80fd5b5f80604083850312156120c1575f80fd5b6120ca83611eb3565b91506120d86020840161209d565b90509250929050565b5f80604083850312156120f2575f80fd5b82359150602083013561210481611f16565b809150509250929050565b5f805f8060808587031215612122575f80fd5b843593506020850135925061213960408601611eb3565b915061207b6060860161209d565b5f8060408385031215612158575f80fd5b823561216381611ee7565b9150602083013561210481611ee7565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b038181168382160190808211156121a7576121a7612173565b5092915050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b81810381811115610c7c57610c7c612173565b6001600160401b038281168282160390808211156121a7576121a7612173565b6001600160401b0381811683821602808216919082811461229c5761229c612173565b505092915050565b5f602082840312156122b4575f80fd5b5051919050565b5f602082840312156122cb575f80fd5b81516106c281611ee7565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b634e487b7160e01b5f52601260045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b5f8261235757612357612321565b500690565b80820180821115610c7c57610c7c612173565b8082028115828204841417610c7c57610c7c612173565b5f8261239457612394612321565b500490565b5f602082840312156123a9575f80fd5b81516106c281611f16565b5f5b838110156123ce5781810151838201526020016123b6565b50505f910152565b5f82516123e78184602087016123b4565b9190910192915050565b602081525f825180602084015261240f8160408501602087016123b4565b601f01601f1916919091016040019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212200910e22634b6f1551e873c664bfc622939fb47d120f9d846eadfca2805ec4fe264736f6c63430008180033", - "balance": "0x0" - }, - "0x1670090000000000000000000000000000010001": { - "contractName": "TaikoL2", - "storage": { - "0x00000000000000000000000000000000000000000000000000000000000000c9": "0x0000000000000000000000000000000000000000000000000000000000000101", - "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190", - "0x0000000000000000000000000000000000000000000000000000000000000097": "0x0000000000000000000000001670090000000000000000000000000000010002", - "0x00000000000000000000000000000000000000000000000000000000000000fe": "0x0000000000000000000000000000000000000000000000000000000000004268", - "0x00000000000000000000000000000000000000000000000000000000000000fd": "0x00000000000000000000000000000000000000000000000000000004a817c800", - "0x00000000000000000000000000000000000000000000000000000000000000fc": "0x12ca4c463583981a753aba62115b12209171346133eaa90b752fb316efd4d2be", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0167009000000000000000000000000000010001" - }, - "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156070573d5ff35b3d5ffdfea2646970667358221220ee2b34631d0aeafcbca8c09b3d006837becb1e761da7ef1dfde69d83e98a206164736f6c63430008180033", - "balance": "0x0" - }, - "0x0167009000000000000000000000000000010002": { - "contractName": "RollupAddressManagerImpl", - "storage": { - "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190" - }, - "code": "0x6080604052600436106100fa575f3560e01c80635c975abb116100925780638da5cb5b116100625780638da5cb5b14610299578063a86f9d9e146102b6578063d8f4648f146102d5578063e30c3978146102f4578063f2fde38b14610311575f80fd5b80635c975abb1461022d578063715018a61461025d57806379ba5097146102715780638456cb5914610285575f80fd5b80633eb6b8cf116100cd5780633eb6b8cf146101c55780633f4ba83a146101e45780634f1ef286146101f857806352d1902d1461020b575f80fd5b806319ab453c146100fe57806328f713cc1461011f5780633659cfe6146101875780633ab76e9f146101a6575b5f80fd5b348015610109575f80fd5b5061011d610118366004610f59565b610330565b005b34801561012a575f80fd5b5061016a610139366004610f90565b67ffffffffffffffff919091165f90815260fb6020908152604080832093835292905220546001600160a01b031690565b6040516001600160a01b0390911681526020015b60405180910390f35b348015610192575f80fd5b5061011d6101a1366004610f59565b610442565b3480156101b1575f80fd5b5060975461016a906001600160a01b031681565b3480156101d0575f80fd5b5061016a6101df366004610fc7565b61051f565b3480156101ef575f80fd5b5061011d610535565b61011d610206366004611014565b6105b4565b348015610216575f80fd5b5061021f61067f565b60405190815260200161017e565b348015610238575f80fd5b5061024d60c954610100900460ff1660021490565b604051901515815260200161017e565b348015610268575f80fd5b5061011d610730565b34801561027c575f80fd5b5061011d610741565b348015610290575f80fd5b5061011d6107b8565b3480156102a4575f80fd5b506033546001600160a01b031661016a565b3480156102c1575f80fd5b5061016a6102d03660046110d2565b610837565b3480156102e0575f80fd5b5061011d6102ef3660046110fc565b610843565b3480156102ff575f80fd5b506065546001600160a01b031661016a565b34801561031c575f80fd5b5061011d61032b366004610f59565b610921565b5f54610100900460ff161580801561034e57505f54600160ff909116105b806103675750303b15801561036757505f5460ff166001145b6103cf5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b5f805460ff1916600117905580156103f0575f805461ff0019166101001790555b6103f982610992565b801561043e575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6001600160a01b037f000000000000000000000000016700900000000000000000000000000001000216300361048a5760405162461bcd60e51b81526004016103c690611139565b7f00000000000000000000000001670090000000000000000000000000000100026001600160a01b03166104d25f80516020611273833981519152546001600160a01b031690565b6001600160a01b0316146104f85760405162461bcd60e51b81526004016103c690611185565b610501816109c2565b604080515f8082526020820190925261051c918391906109ca565b50565b5f61052b848484610b39565b90505b9392505050565b61054960c954610100900460ff1660021490565b6105665760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166101001790556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a16105b2335f610c27565b565b6001600160a01b037f00000000000000000000000001670090000000000000000000000000000100021630036105fc5760405162461bcd60e51b81526004016103c690611139565b7f00000000000000000000000001670090000000000000000000000000000100026001600160a01b03166106445f80516020611273833981519152546001600160a01b031690565b6001600160a01b03161461066a5760405162461bcd60e51b81526004016103c690611185565b610673826109c2565b61043e828260016109ca565b5f306001600160a01b037f0000000000000000000000000167009000000000000000000000000000010002161461071e5760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c000000000000000060648201526084016103c6565b505f8051602061127383398151915290565b610738610c3f565b6105b25f610c99565b60655433906001600160a01b031681146107af5760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016103c6565b61051c81610c99565b6107cc60c954610100900460ff1660021490565b156107ea5760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a16105b2336001610c27565b5f61052e468484610b39565b61084b610c3f565b67ffffffffffffffff83165f90815260fb602090815260408083208584529091529020546001600160a01b0390811690821681900361089d576040516327b026fb60e21b815260040160405180910390fd5b67ffffffffffffffff84165f81815260fb6020908152604080832087845282529182902080546001600160a01b0319166001600160a01b038781169182179092558351908152908516918101919091528592917f500dcd607a98daece9bccc2511bf6032471252929de73caf507aae0e082f8453910160405180910390a350505050565b610929610c3f565b606580546001600160a01b0383166001600160a01b0319909116811790915561095a6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6109b06001600160a01b038216156109aa5781610c99565b33610c99565b5060c9805461ff001916610100179055565b61051c610c3f565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff1615610a02576109fd83610cb2565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015610a5c575060408051601f3d908101601f19168201909252610a59918101906111d1565b60015b610abf5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b60648201526084016103c6565b5f805160206112738339815191528114610b2d5760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b60648201526084016103c6565b506109fd838383610d4d565b6097545f906001600160a01b0316610b6457604051638ed88b2560e01b815260040160405180910390fd5b609754604051630a3dc4f360e21b815267ffffffffffffffff86166004820152602481018590526001600160a01b03909116906328f713cc90604401602060405180830381865afa158015610bbb573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610bdf91906111e8565b905081158015610bf657506001600160a01b038116155b1561052e57604051632b0d65db60e01b815267ffffffffffffffff85166004820152602481018490526044016103c6565b60405162580a9560e71b815260040160405180910390fd5b6033546001600160a01b031633146105b25760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103c6565b606580546001600160a01b031916905561051c81610d77565b6001600160a01b0381163b610d1f5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016103c6565b5f8051602061127383398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b610d5683610dc8565b5f82511180610d625750805b156109fd57610d718383610e07565b50505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b610dd181610cb2565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606061052e83836040518060600160405280602781526020016112936027913960605f80856001600160a01b031685604051610e439190611225565b5f60405180830381855af49150503d805f8114610e7b576040519150601f19603f3d011682016040523d82523d5f602084013e610e80565b606091505b5091509150610e9186838387610e9b565b9695505050505050565b60608315610f095782515f03610f02576001600160a01b0385163b610f025760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016103c6565b5081610f13565b610f138383610f1b565b949350505050565b815115610f2b5781518083602001fd5b8060405162461bcd60e51b81526004016103c69190611240565b6001600160a01b038116811461051c575f80fd5b5f60208284031215610f69575f80fd5b813561052e81610f45565b803567ffffffffffffffff81168114610f8b575f80fd5b919050565b5f8060408385031215610fa1575f80fd5b610faa83610f74565b946020939093013593505050565b80358015158114610f8b575f80fd5b5f805f60608486031215610fd9575f80fd5b610fe284610f74565b925060208401359150610ff760408501610fb8565b90509250925092565b634e487b7160e01b5f52604160045260245ffd5b5f8060408385031215611025575f80fd5b823561103081610f45565b9150602083013567ffffffffffffffff8082111561104c575f80fd5b818501915085601f83011261105f575f80fd5b81358181111561107157611071611000565b604051601f8201601f19908116603f0116810190838211818310171561109957611099611000565b816040528281528860208487010111156110b1575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f80604083850312156110e3575f80fd5b823591506110f360208401610fb8565b90509250929050565b5f805f6060848603121561110e575f80fd5b61111784610f74565b925060208401359150604084013561112e81610f45565b809150509250925092565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b5f602082840312156111e1575f80fd5b5051919050565b5f602082840312156111f8575f80fd5b815161052e81610f45565b5f5b8381101561121d578181015183820152602001611205565b50505f910152565b5f8251611236818460208701611203565b9190910192915050565b602081525f825180602084015261125e816040850160208701611203565b601f01601f1916919091016040019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212203cf7c7909aa3a171d69bba3422ad3ee05166e507361c8bce5bed32f66ed9173764736f6c63430008180033", - "balance": "0x0" - }, - "0x1670090000000000000000000000000000010002": { - "contractName": "RollupAddressManager", - "storage": { - "0x00000000000000000000000000000000000000000000000000000000000000c9": "0x0000000000000000000000000000000000000000000000000000000000000101", - "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190", - "0xd330e44a3603b4246ee804ea67699b051c53a81e87cb0e4159648e14c2c7ff54": "0x0000000000000000000000001670090000000000000000000000000000010001", - "0x06e004775639cdb0e38f2c8a0f80bf9e3f0bafc0587c4deccd476e1b0b083676": "0x0000000000000000000000001670090000000000000000000000000000000001", - "0x57e7be70ddd7eb8855d6252773a58a2cd3685df17defaa305b7a91e54f33be8c": "0x0000000000000000000000001670090000000000000000000000000000000005", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0167009000000000000000000000000000010002" - }, - "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156070573d5ff35b3d5ffdfea2646970667358221220ee2b34631d0aeafcbca8c09b3d006837becb1e761da7ef1dfde69d83e98a206164736f6c63430008180033", - "balance": "0x0" - }, - "0x3De27cC792B5a5886E85806316E7230Fd350BdaA": { - "contractName": "LibNetwork", - "storage": {}, - "code": "0x730000000000000000000000000000000000000000301460806040525f80fdfea26469706673582212200e91ea97951a08e1a55500bea198d5173bbd28c3741a91cc9a0898a286c7802864736f6c63430008180033", - "balance": "0x0" - }, - "0x0167009000000000000000000000000000010099": { - "contractName": "RegularERC20", - "storage": { - "0x0000000000000000000000000000000000000000000000000000000000000003": "0x526567756c617245524332300000000000000000000000000000000000000018", - "0x0000000000000000000000000000000000000000000000000000000000000004": "0x52474c0000000000000000000000000000000000000000000000000000000006", - "0x0000000000000000000000000000000000000000000000000000000000000002": "0x00000000000000000000000000000000000000000000000000000000001f4000", - "0x7e98f6668cc63185c0ed735323fd659178e6c2a72e12a0915ffe1e12f257e25f": "0x00000000000000000000000000000000000000000000000000000000000fa000", - "0x36b8d434cffc56d906e4123d42c8917d6bf64c3eead76e75f37db4862e4ed11e": "0x00000000000000000000000000000000000000000000000000000000000fa000" - }, - "code": "0x608060405234801561000f575f80fd5b50600436106100a6575f3560e01c8063395093511161006e578063395093511461011f57806370a082311461013257806395d89b411461015a578063a457c2d714610162578063a9059cbb14610175578063dd62ed3e14610188575f80fd5b806306fdde03146100aa578063095ea7b3146100c857806318160ddd146100eb57806323b872dd146100fd578063313ce56714610110575b5f80fd5b6100b261019b565b6040516100bf919061068a565b60405180910390f35b6100db6100d63660046106f1565b61022b565b60405190151581526020016100bf565b6002545b6040519081526020016100bf565b6100db61010b366004610719565b610244565b604051601281526020016100bf565b6100db61012d3660046106f1565b610267565b6100ef610140366004610752565b6001600160a01b03165f9081526020819052604090205490565b6100b2610288565b6100db6101703660046106f1565b610297565b6100db6101833660046106f1565b610316565b6100ef610196366004610772565b610323565b6060600380546101aa906107a3565b80601f01602080910402602001604051908101604052809291908181526020018280546101d6906107a3565b80156102215780601f106101f857610100808354040283529160200191610221565b820191905f5260205f20905b81548152906001019060200180831161020457829003601f168201915b5050505050905090565b5f3361023881858561034d565b60019150505b92915050565b5f33610251858285610470565b61025c8585856104e8565b506001949350505050565b5f336102388185856102798383610323565b61028391906107db565b61034d565b6060600480546101aa906107a3565b5f33816102a48286610323565b9050838110156103095760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b61025c828686840361034d565b5f336102388185856104e8565b6001600160a01b039182165f90815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166103af5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610300565b6001600160a01b0382166104105760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610300565b6001600160a01b038381165f8181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b5f61047b8484610323565b90505f1981146104e257818110156104d55760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610300565b6104e2848484840361034d565b50505050565b6001600160a01b03831661054c5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610300565b6001600160a01b0382166105ae5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610300565b6001600160a01b0383165f90815260208190526040902054818110156106255760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610300565b6001600160a01b038481165f81815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a36104e2565b5f602080835283518060208501525f5b818110156106b65785810183015185820160400152820161069a565b505f604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b03811681146106ec575f80fd5b919050565b5f8060408385031215610702575f80fd5b61070b836106d6565b946020939093013593505050565b5f805f6060848603121561072b575f80fd5b610734846106d6565b9250610742602085016106d6565b9150604084013590509250925092565b5f60208284031215610762575f80fd5b61076b826106d6565b9392505050565b5f8060408385031215610783575f80fd5b61078c836106d6565b915061079a602084016106d6565b90509250929050565b600181811c908216806107b757607f821691505b6020821081036107d557634e487b7160e01b5f52602260045260245ffd5b50919050565b8082018082111561023e57634e487b7160e01b5f52601160045260245ffdfea2646970667358221220147ef409e997b57527888b48fd0fb61bc86cc4d715fa732d06bbff34ee44453764736f6c63430008180033", - "balance": "0x0" - } - } -} diff --git a/src/Nethermind/Chains/taiko-hoodi.json b/src/Nethermind/Chains/taiko-hoodi.json new file mode 100644 index 000000000000..5317f3ed5e52 --- /dev/null +++ b/src/Nethermind/Chains/taiko-hoodi.json @@ -0,0 +1,287 @@ +{ + "name": "Taiko Hoodi", + "dataDir": "taiko-hoodi", + "engine": { + "Taiko": { + "ontakeTransition": "0x0", + "pacayaTransition": "0x0", + "shastaTimestamp": "0x69849450", + "taikoL2Address": "0x1670130000000000000000000000000000010001" + } + }, + "params": { + "chainId": "167013", + "maxCodeSize": "0x6000", + "maxCodeSizeTransition": "0x0", + "eip150Transition": "0x0", + "eip160Transition": "0x0", + "eip161abcTransition": "0x0", + "eip161dTransition": "0x0", + "eip155Transition": "0x0", + "eip140Transition": "0x0", + "eip211Transition": "0x0", + "eip214Transition": "0x0", + "eip658Transition": "0x0", + "eip145Transition": "0x0", + "eip1014Transition": "0x0", + "eip1052Transition": "0x0", + "eip1283Transition": "0x0", + "eip1283DisableTransition": "0x0", + "eip152Transition": "0x0", + "eip1108Transition": "0x0", + "eip1344Transition": "0x0", + "eip1884Transition": "0x0", + "eip2028Transition": "0x0", + "eip2200Transition": "0x0", + "eip2565Transition": "0x0", + "eip2929Transition": "0x0", + "eip2930Transition": "0x0", + "eip1559Transition": "0x0", + "eip1559FeeCollectorTransition": "0x0", + "feeCollector": "0x1670130000000000000000000000000000010001", + "eip1559ElasticityMultiplier": "0x2", + "eip1559BaseFeeMaxChangeDenominator": "0x8", + "eip1559BaseFeeMinValue": "0x86ff51", + "eip3198Transition": "0x0", + "eip3529Transition": "0x0", + "eip3541Transition": "0x0", + "eip4895TransitionTimestamp": "0x0", + "eip3651TransitionTimestamp": "0x0", + "eip3855TransitionTimestamp": "0x0", + "eip3860TransitionTimestamp": "0x0", + "terminalTotalDifficulty": "0", + "eip1559BaseFeeMinValueTransition": "0xcd340" + }, + "genesis": { + "seal": { + "ethereum": { + "nonce": "0x0000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "number": "0x0", + "difficulty": "0x0", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x", + "baseFeePerGas": "0x989680", + "gasLimit": "0xe4e1c0" + }, + "nodes": [ + "enode://eb5079aae185d5d8afa01bfd2d349da5b476609aced2b57c90142556cf0ee4a152bcdd724627a7de97adfc2a68af5742a8f58781366e6a857d4bde98de6fe986@34.44.53.195:30303", + "enode://b054002f068f30568aad39271462c053463edb4a3d3c19b71b475fa044805d7e2fda39c482eba183f9d1f76fb579a8e47c0c054bb819c2bbcb331c0aac7464c2@34.27.167.246:30303" + ], + "accounts": { + "0xCAc37818c3f8d5bc6d18526563D011F904f815cd": { + "balance": "0x3782dace9d90000000" + }, + "0x839d8B5Ffa587725948267D4b78C96e71416497d": { + "balance": "0x3782dace9d90000000" + }, + "0x0167013000000000000000000000000000000006": { + "contractName": "SharedResolverImpl", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190" + }, + "code": "0x6080604052600436106100fa575f3560e01c80636c6563f6116100925780638abf6077116100625780638abf6077146102515780638da5cb5b14610265578063b490d87f14610282578063e30c3978146102a1578063f2fde38b146102be575f5ffd5b80636c6563f6146101f6578063715018a61461021557806379ba5097146102295780638456cb591461023d575f5ffd5b80633f4ba83a116100cd5780633f4ba83a1461018d5780634f1ef286146101a157806352d1902d146101b45780635c975abb146101d6575f5ffd5b806304f3bcec146100fe57806319ab453c146101295780633075db561461014a5780633659cfe61461016e575b5f5ffd5b348015610109575f5ffd5b50305b6040516001600160a01b0390911681526020015b60405180910390f35b348015610134575f5ffd5b50610148610143366004610e60565b6102dd565b005b348015610155575f5ffd5b5061015e6103ef565b6040519015158152602001610120565b348015610179575f5ffd5b50610148610188366004610e60565b610407565b348015610198575f5ffd5b506101486104ce565b6101486101af366004610e8d565b610552565b3480156101bf575f5ffd5b506101c8610607565b604051908152602001610120565b3480156101e1575f5ffd5b5061015e60c954610100900460ff1660021490565b348015610201575f5ffd5b5061010c610210366004610f51565b6106b8565b348015610220575f5ffd5b50610148610709565b348015610234575f5ffd5b5061014861071a565b348015610248575f5ffd5b50610148610791565b34801561025c575f5ffd5b5061010c610810565b348015610270575f5ffd5b506033546001600160a01b031661010c565b34801561028d575f5ffd5b5061014861029c366004610f8b565b61081e565b3480156102ac575f5ffd5b506065546001600160a01b031661010c565b3480156102c9575f5ffd5b506101486102d8366004610e60565b61089f565b5f54610100900460ff16158080156102fb57505f54600160ff909116105b806103145750303b15801561031457505f5460ff166001145b61037c5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b5f805460ff19166001179055801561039d575f805461ff0019166101001790555b6103a682610910565b80156103eb575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b5f60026103fe60c95460ff1690565b60ff1614905090565b6001600160a01b037f000000000000000000000000016701300000000000000000000000000000000616300361044f5760405162461bcd60e51b815260040161037390610fbd565b7f00000000000000000000000001670130000000000000000000000000000000066001600160a01b031661048161096e565b6001600160a01b0316146104a75760405162461bcd60e51b815260040161037390611009565b6104b081610989565b604080515f808252602082019092526104cb91839190610991565b50565b6104e260c954610100900460ff1660021490565b6104ff5760405163bae6e2a960e01b815260040160405180910390fd5b61051360c9805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610550335f610b00565b565b6001600160a01b037f000000000000000000000000016701300000000000000000000000000000000616300361059a5760405162461bcd60e51b815260040161037390610fbd565b7f00000000000000000000000001670130000000000000000000000000000000066001600160a01b03166105cc61096e565b6001600160a01b0316146105f25760405162461bcd60e51b815260040161037390611009565b6105fb82610989565b6103eb82826001610991565b5f306001600160a01b037f000000000000000000000000016701300000000000000000000000000000000616146106a65760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610373565b505f5160206111275f395f51905f5290565b5f83815260fb602090815260408083208584529091529020546001600160a01b0316801515806106e55750815b61070257604051631692906160e11b815260040160405180910390fd5b9392505050565b610711610b19565b6105505f610b73565b60655433906001600160a01b031681146107885760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b6064820152608401610373565b6104cb81610b73565b6107a560c954610100900460ff1660021490565b156107c35760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a1610550336001610b00565b5f61081961096e565b905090565b610826610b19565b5f83815260fb6020908152604080832085845282529182902080546001600160a01b038581166001600160a01b0319831681179093558451928352169181018290529091849186917f3fd0559a7b01eb7106f9d9ce79ec76bb44f608a295878cce50856e54dba83d35910160405180910390a350505050565b6108a7610b19565b606580546001600160a01b0383166001600160a01b031990911681179091556108d86033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b5f54610100900460ff166109365760405162461bcd60e51b815260040161037390611055565b61093e610b8c565b61095c6001600160a01b038216156109565781610b73565b33610b73565b5060c9805461ff001916610100179055565b5f5160206111275f395f51905f52546001600160a01b031690565b6104cb610b19565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156109c9576109c483610bb2565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015610a23575060408051601f3d908101601f19168201909252610a20918101906110a0565b60015b610a865760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610373565b5f5160206111275f395f51905f528114610af45760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610373565b506109c4838383610c4d565b604051630c2b8f8f60e11b815260040160405180910390fd5b6033546001600160a01b031633146105505760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610373565b606580546001600160a01b03191690556104cb81610c77565b5f54610100900460ff166105505760405162461bcd60e51b815260040161037390611055565b6001600160a01b0381163b610c1f5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610373565b5f5160206111275f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b610c5683610cc8565b5f82511180610c625750805b156109c457610c718383610d07565b50505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b610cd181610bb2565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606061070283836040518060600160405280602781526020016111476027913960605f5f856001600160a01b031685604051610d4391906110d9565b5f60405180830381855af49150503d805f8114610d7b576040519150601f19603f3d011682016040523d82523d5f602084013e610d80565b606091505b5091509150610d9186838387610d9b565b9695505050505050565b60608315610e095782515f03610e02576001600160a01b0385163b610e025760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610373565b5081610e13565b610e138383610e1b565b949350505050565b815115610e2b5781518083602001fd5b8060405162461bcd60e51b815260040161037391906110f4565b80356001600160a01b0381168114610e5b575f5ffd5b919050565b5f60208284031215610e70575f5ffd5b61070282610e45565b634e487b7160e01b5f52604160045260245ffd5b5f5f60408385031215610e9e575f5ffd5b610ea783610e45565b9150602083013567ffffffffffffffff811115610ec2575f5ffd5b8301601f81018513610ed2575f5ffd5b803567ffffffffffffffff811115610eec57610eec610e79565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610f1b57610f1b610e79565b604052818152828201602001871015610f32575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f5f5f60608486031215610f63575f5ffd5b833592506020840135915060408401358015158114610f80575f5ffd5b809150509250925092565b5f5f5f60608486031215610f9d575f5ffd5b8335925060208401359150610fb460408501610e45565b90509250925092565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f602082840312156110b0575f5ffd5b5051919050565b5f5b838110156110d15781810151838201526020016110b9565b50505f910152565b5f82516110ea8184602087016110b7565b9190910192915050565b602081525f82518060208401526111128160408501602087016110b7565b601f01601f1916919091016040019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212203c364628b47f58f18110ccb14bff9bcef5ebda8cf5ca50013c8ed8384ae4bdd464736f6c634300081b0033", + "balance": "0x0" + }, + "0x1670130000000000000000000000000000000006": { + "contractName": "SharedResolver", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000c9": "0x0000000000000000000000000000000000000000000000000000000000000101", + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190", + "0x1fd4f8aa15619802dcb55d6c851af651ac835d9fe7dc456a2a320fe1e7ce194b": "0x0000000000000000000000001670130000000000000000000000000000000001", + "0xf31733730bd987807ec3984c8ea2a1da175ef14db3517d7f359499457a16278f": "0x0000000000000000000000001670130000000000000000000000000000000002", + "0x888bafed116796fefd02b515de1ec0c47a5bfd451fcf9a7435474b255d70c869": "0x0000000000000000000000001670130000000000000000000000000000000003", + "0xe8ad6216cffa898bd75c5166f78889ae3b55e2e123daaf3cd383c9572471d1ef": "0x0000000000000000000000001670130000000000000000000000000000000004", + "0x96584a0117748840ea3710035480e8e9cd718737d5d8957a6bffb6161d8a348b": "0x0000000000000000000000001670130000000000000000000000000000000005", + "0x129a8ff4a734ce6002812278cb9a9609c3840fc0c38e18075889e36a3e6b3c96": "0x0000000000000000000000000167013000000000000000000000000000010096", + "0x8d8577536de64f0c7c3cc2197980ed1684c9b6a67177cd400bf722d8bfea9ccc": "0x0000000000000000000000000167013000000000000000000000000000010097", + "0xbd838f13a7d08992b5f19bb6f28b7d16787f4117f9565d4748f8bb45efde414c": "0x0000000000000000000000000167013000000000000000000000000000010098", + "0x217f440aaa3002cdfb2ee9ebce80b5991546524a0b76b2ccefea002962b55a19": "0x0000000000000000000000001670130000000000000000000000000000010001", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0167013000000000000000000000000000000006" + }, + "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033", + "balance": "0x0" + }, + "0x0167013000000000000000000000000000000001": { + "contractName": "BridgeImpl", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190" + }, + "code": "0x608060405260043610610207575f3560e01c8063715018a611610113578063a730cdfb1161009d578063d0496d6a1161006d578063d0496d6a14610620578063d1aaa5df14610668578063e30c397814610688578063eefbf17e146106a5578063f2fde38b146106e3575f5ffd5b8063a730cdfb146105b6578063b8acae0e146105cc578063be880c81146105eb578063c012fa7714610601575f5ffd5b80638abf6077116100e35780638abf6077146105095780638da5cb5b1461051d5780638e3881a91461053a578063913b16cb146105785780639efc7a2e14610597575f5ffd5b8063715018a61461049957806379ba5097146104ad5780637cbadfaa146104c15780638456cb59146104f5575f5ffd5b80633f4ba83a116101945780635862f6e1116101645780635862f6e1146103d55780635c975abb146103f457806360620c6b1461041457806362d094531461043357806364d391b414610466575f5ffd5b80633f4ba83a14610376578063422770fa1461038a5780634f1ef286146103ae57806352d1902d146103c1575f5ffd5b80631bdb0037116101da5780631bdb0037146102aa5780632035065e146102cb5780633075db56146102f85780633659cfe61461031c5780633c6cf4731461033b575f5ffd5b80630432873c1461020b57806304f3bcec1461022c578063069489a21461027757806319ab453c1461028b575b5f5ffd5b348015610216575f5ffd5b5061022a610225366004612ec7565b610702565b005b348015610237575f5ffd5b507f00000000000000000000000016701300000000000000000000000000000000065b6040516001600160a01b0390911681526020015b60405180910390f35b348015610282575f5ffd5b5061022a610a09565b348015610296575f5ffd5b5061022a6102a5366004612f34565b610ac0565b6102bd6102b8366004612f4f565b610b86565b60405161026e9291906130db565b3480156102d6575f5ffd5b506102ea6102e53660046130f3565b610f3c565b60405161026e9291906131b0565b348015610303575f5ffd5b5061030c61141a565b604051901515815260200161026e565b348015610327575f5ffd5b5061022a610336366004612f34565b611432565b348015610346575f5ffd5b506103696103553660046131db565b60fc6020525f908152604090205460ff1681565b60405161026e91906131f2565b348015610381575f5ffd5b5061022a6114f9565b348015610395575f5ffd5b506103a062030d4081565b60405190815260200161026e565b61022a6103bc3660046132c6565b61157d565b3480156103cc575f5ffd5b506103a0611632565b3480156103e0575f5ffd5b5061030c6103ef3660046130f3565b6116e4565b3480156103ff575f5ffd5b5061030c60c954610100900460ff1660021490565b34801561041f575f5ffd5b5061030c61042e366004612f4f565b61175f565b34801561043e575f5ffd5b5061025a7f000000000000000000000000167013000000000000000000000000000000000581565b348015610471575f5ffd5b5061025a7f000000000000000000000000000000000000000000000000000000000000000081565b3480156104a4575f5ffd5b5061022a611833565b3480156104b8575f5ffd5b5061022a611844565b3480156104cc575f5ffd5b506104e06104db3660046131db565b6118bb565b60405163ffffffff909116815260200161026e565b348015610500575f5ffd5b5061022a6118d9565b348015610514575f5ffd5b5061025a611958565b348015610528575f5ffd5b506033546001600160a01b031661025a565b348015610545575f5ffd5b50610559610554366004613328565b611966565b6040805192151583526001600160a01b0390911660208301520161026e565b348015610583575f5ffd5b5061022a610592366004612f4f565b611992565b3480156105a2575f5ffd5b5061022a6105b13660046130f3565b611b84565b3480156105c1575f5ffd5b506104e06201d4c081565b3480156105d7575f5ffd5b5061030c6105e63660046130f3565b611ea8565b3480156105f6575f5ffd5b506104e0620c350081565b34801561060c575f5ffd5b506103a061061b366004613441565b611f0f565b34801561062b575f5ffd5b50610634611f3e565b60408051825181526020808401516001600160a01b031690820152918101516001600160401b03169082015260600161026e565b348015610673575f5ffd5b506103a06106823660046131db565b60031890565b348015610693575f5ffd5b506065546001600160a01b031661025a565b3480156106b0575f5ffd5b5060fb546106cb90600160401b90046001600160401b031681565b6040516001600160401b03909116815260200161026e565b3480156106ee575f5ffd5b5061022a6106fd366004612f34565b611fe5565b61071260e0830160c08401613328565b46816001600160401b03161461073b57604051631c6c777560e31b815260040160405180910390fd5b61074b60a0840160808501613328565b6001600160401b0381161580610769575046816001600160401b0316145b1561078757604051631c6c777560e31b815260040160405180910390fd5b61079b60c954610100900460ff1660021490565b156107b95760405163bae6e2a960e01b815260040160405180910390fd5b60026107c760c95460ff1690565b60ff16036107e85760405163dfc60d8560e01b815260040160405180910390fd5b6107f26002612056565b5f6107ff61061b86613472565b905061080c81600161206c565b61081a8561012001356120be565b610837576040516335856fbd60e21b815260040160405180910390fd5b5f610862867f0000000000000000000000001670130000000000000000000000000000000005612183565b156108ad576108a68661012001356188b860405180602001604052805f8152508960e00160208101906108959190612f34565b6001600160a01b031692919061227a565b9050610927565b6108bd606087016040880161347d565b63ffffffff1615806108cc5750845b80156108fa57506108e4610100870160e08801612f34565b6001600160a01b0316336001600160a01b031614155b15610918576040516372b6e1c360e11b815260040160405180910390fd5b61092486835a5f6122b7565b90505b801561093d576109388260026123db565b6109f7565b84156109de5761094e8260036123db565b60405163019b28af60e61b81526003831860048201527f00000000000000000000000016701300000000000000000000000000000000056001600160a01b0316906366ca2bc0906024016020604051808303815f875af11580156109b4573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109d89190613496565b506109f7565b60405163161e3ead60e01b815260040160405180910390fd5b5050610a036001612056565b50505050565b610a11612498565b5f54600290610100900460ff16158015610a3157505f5460ff8083169116105b610a565760405162461bcd60e51b8152600401610a4d906134ad565b60405180910390fd5b5f805460fb805467ffffffffffffffff1916905560ff82815561010083815561ff001991851661ffff19909316831717169091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a150565b5f54610100900460ff1615808015610ade57505f54600160ff909116105b80610af75750303b158015610af757505f5460ff166001145b610b135760405162461bcd60e51b8152600401610a4d906134ad565b5f805460ff191660011790558015610b34575f805461ff0019166101001790555b610b3d826124f2565b8015610b82575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b60408051610160810182525f8082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e0820183905261010082018390526101208201839052610140820152610bec60c0840160a08501612f34565b6001600160a01b038116610c135760405163538ba4f960e01b815260040160405180910390fd5b610c24610100850160e08601612f34565b6001600160a01b038116610c4b5760405163538ba4f960e01b815260040160405180910390fd5b610c5b60e0860160c08701613328565b6001600160401b0381161580610c79575046816001600160401b0316145b15610c9757604051631c6c777560e31b815260040160405180910390fd5b610cab60c954610100900460ff1660021490565b15610cc95760405163bae6e2a960e01b815260040160405180910390fd5b6002610cd760c95460ff1690565b60ff1603610cf85760405163dfc60d8560e01b815260040160405180910390fd5b610d026002612056565b610d12606087016040880161347d565b63ffffffff165f03610d5a57610d2e6040870160208801613328565b6001600160401b031615610d555760405163c9f5178760e01b815260040160405180910390fd5b610d82565b610d6386612550565b5f03610d82576040516308c2ad5360e01b815260040160405180910390fd5b5f610d9661055460e0890160c08a01613328565b50905080610db757604051631c6c777560e31b815260040160405180910390fd5b34610dc86040890160208a01613328565b610de0906001600160401b03166101208a013561350f565b14610dfe57604051634ac2abdf60e11b815260040160405180910390fd5b610e0787613472565b60fb8054919650600160401b9091046001600160401b0316906008610e2b83613522565b82546101009290920a6001600160401b03818102199093169183160217909155908116865233606087015246166080860152610e6685611f0f565b9550857fe33fd33b4f45b95b1c196242240c5b5233129d724b578f95b66ce8d8aae9351786604051610e98919061354c565b60405180910390a260405163019b28af60e61b8152600481018790527f00000000000000000000000016701300000000000000000000000000000000056001600160a01b0316906366ca2bc0906024016020604051808303815f875af1158015610f04573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f289190613496565b5050610f346001612056565b505050915091565b5f5f610f5260c954610100900460ff1660021490565b15610f705760405163bae6e2a960e01b815260040160405180910390fd5b6002610f7e60c95460ff1690565b60ff1603610f9f5760405163dfc60d8560e01b815260040160405180910390fd5b610fa96002612056565b5f5a905046610fbe60e0880160c08901613328565b6001600160401b031614610fe557604051631c6c777560e31b815260040160405180910390fd5b610ff560a0870160808801613328565b6001600160401b0316158061102157504661101660a0880160808901613328565b6001600160401b0316145b1561103f57604051631c6c777560e31b815260040160405180910390fd5b604080516080810182525f808252602082018190529181018290526060810191909152611073610100880160e08901612f34565b6001600160a01b03163314801560608301526110e257611099606088016040890161347d565b63ffffffff165f036110be576040516372b6e1c360e11b815260040160405180910390fd5b62030d408511156110e257604051631e3b03c960e01b815260040160405180910390fd5b5f6110ef61061b89613472565b90506110fb815f61206c565b63ffffffff861660208301526111437f00000000000000000000000016701300000000000000000000000000000000058261113c60a08c0160808d01613328565b8a8a6125a3565b63ffffffff1660408084019190915261118290611165908a0160208b01613328565b61117d906001600160401b03166101208b013561350f565b6120be565b61119f576040516335856fbd60e21b815260040160405180910390fd5b5f6111ca897f0000000000000000000000001670130000000000000000000000000000000005612183565b156111e35750600294506001935061012088013561122a565b5f83606001516111f3575a6111fc565b6111fc8a612550565b905061120e8a848387606001516122b7565b1561121f57600296505f9550611228565b60019650600295505b505b61123a60408a0160208b01613328565b6001600160401b0316156113985761125860408a0160208b01613328565b61126b906001600160401b03168261350f565b905082606001518015611292575061128960608a0160408b0161347d565b63ffffffff1615155b1561139857604083015163ffffffff16614e20025a6112cf6112b86101408d018d61355e565b6020601f909101819004026101a00160041b919050565b63ffffffff9081168701919091036201d4c00181168086525f9183916112f89183919061264e16565b0390505f61130c60608d0160408e0161347d565b63ffffffff1661132260408e0160208f01613328565b6001600160401b031683028161133a5761133a6135a7565b0490505f48830290505f61137e8e602001602081019061135a9190613328565b6001600160401b0316848410156113765784840160011c611378565b845b90612663565b9586900395905061139233826188b8612677565b50505050505b6113c0816188b86113b06101008d0160e08e01612f34565b6001600160a01b03169190612677565b6113ca82876123db565b817f8580f507761043ecdd2bdca084d6fb0109150b3d9842d854d34e3dea6d69387d8a856040516113fc92919061373f565b60405180910390a2505050506114126001612056565b935093915050565b5f600261142960c95460ff1690565b60ff1614905090565b6001600160a01b037f000000000000000000000000016701300000000000000000000000000000000116300361147a5760405162461bcd60e51b8152600401610a4d90613793565b7f00000000000000000000000001670130000000000000000000000000000000016001600160a01b03166114ac6126ba565b6001600160a01b0316146114d25760405162461bcd60e51b8152600401610a4d906137df565b6114db816126d5565b604080515f808252602082019092526114f6918391906126dd565b50565b61150d60c954610100900460ff1660021490565b61152a5760405163bae6e2a960e01b815260040160405180910390fd5b61153e60c9805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a161157b335f612847565b565b6001600160a01b037f00000000000000000000000001670130000000000000000000000000000000011630036115c55760405162461bcd60e51b8152600401610a4d90613793565b7f00000000000000000000000001670130000000000000000000000000000000016001600160a01b03166115f76126ba565b6001600160a01b03161461161d5760405162461bcd60e51b8152600401610a4d906137df565b611626826126d5565b610b82828260016126dd565b5f306001600160a01b037f000000000000000000000000016701300000000000000000000000000000000116146116d15760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610a4d565b505f5160206139f95f395f51905f525b90565b5f466116f660a0860160808701613328565b6001600160401b03161461170b57505f611758565b6117557f000000000000000000000000167013000000000000000000000000000000000561173e61068261061b88613472565b61174e60e0880160c08901613328565b86866128f4565b90505b9392505050565b5f4661177160a0840160808501613328565b6001600160401b03161461178657505f919050565b6001600160a01b037f0000000000000000000000001670130000000000000000000000000000000005166332676bc6306117c261061b86613472565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401602060405180830381865afa158015611809573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061182d919061382b565b92915050565b61183b612498565b61157b5f612976565b60655433906001600160a01b031681146118b25760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b6064820152608401610a4d565b6114f681612976565b5f61182d620c35006101a06020601f8601819004020160041b613846565b6118ed60c954610100900460ff1660021490565b1561190b5760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a161157b336001612847565b5f6119616126ba565b905090565b5f5f61197d836562726964676560d01b600161298f565b6001600160a01b038116151594909350915050565b6119a260e0820160c08301613328565b46816001600160401b0316146119cb57604051631c6c777560e31b815260040160405180910390fd5b6119db60a0830160808401613328565b6001600160401b03811615806119f9575046816001600160401b0316145b15611a1757604051631c6c777560e31b815260040160405180910390fd5b611a2b60c954610100900460ff1660021490565b15611a495760405163bae6e2a960e01b815260040160405180910390fd5b6002611a5760c95460ff1690565b60ff1603611a785760405163dfc60d8560e01b815260040160405180910390fd5b611a826002612056565b611a93610100840160e08501612f34565b6001600160a01b0316336001600160a01b031614611ac4576040516372b6e1c360e11b815260040160405180910390fd5b5f611ad161061b85613472565b9050611ade81600161206c565b611ae98160036123db565b60405163019b28af60e61b81526003821860048201527f00000000000000000000000016701300000000000000000000000000000000056001600160a01b0316906366ca2bc0906024016020604051808303815f875af1158015611b4f573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b739190613496565b5050611b7f6001612056565b505050565b611b9460a0840160808501613328565b46816001600160401b031614611bbd57604051631c6c777560e31b815260040160405180910390fd5b611bcd60e0850160c08601613328565b6001600160401b0381161580611beb575046816001600160401b0316145b15611c0957604051631c6c777560e31b815260040160405180910390fd5b611c1d60c954610100900460ff1660021490565b15611c3b5760405163bae6e2a960e01b815260040160405180910390fd5b6002611c4960c95460ff1690565b60ff1603611c6a5760405163dfc60d8560e01b815260040160405180910390fd5b611c746002612056565b5f611c8161061b87613472565b9050611c8d815f61206c565b604051631933b5e360e11b8152306004820152602481018290527f00000000000000000000000016701300000000000000000000000000000000056001600160a01b0316906332676bc690604401602060405180830381865afa158015611cf6573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d1a919061382b565b611d375760405163ab035ad560e01b815260040160405180910390fd5b611d767f000000000000000000000000167013000000000000000000000000000000000560038318611d6f60e08a0160c08b01613328565b88886125a3565b50611d828160046123db565b611d908661012001356120be565b611dad576040516335856fbd60e21b815260040160405180910390fd5b611dd662bc399d60e11b611dc76080890160608a01612f34565b6001600160a01b031690612a31565b15611e7a57611df58130611df060a08a0160808b01613328565b612b01565b611e056080870160608801612f34565b6001600160a01b0316630178733a87610120013588846040518463ffffffff1660e01b8152600401611e38929190613862565b5f604051808303818588803b158015611e4f575f5ffd5b505af1158015611e61573d5f5f3e3d5ffd5b5050505050611e755f195f1b5f1980612b01565b611e96565b611e966101208701356188b86113b060c08a0160a08b01612f34565b50611ea16001612056565b5050505050565b5f46611eba60e0860160c08701613328565b6001600160401b031614611ecf57505f611758565b6117557f0000000000000000000000001670130000000000000000000000000000000005611eff61061b87613472565b61174e60a0880160808901613328565b5f81604051602001611f219190613883565b604051602081830303815290604052805190602001209050919050565b604080516060810182525f8082526020820181905291810191909152611fb5604080516060810182525f8082526020820181905291810191909152506040805160608101825260fd54815260fe546001600160a01b0381166020830152600160a01b90046001600160401b03169181019190915290565b80519091501580611fc7575080515f19145b156116e157604051635ceed17360e01b815260040160405180910390fd5b611fed612498565b606580546001600160a01b0383166001600160a01b0319909116811790915561201e6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60c9805460ff191660ff92909216919091179055565b80600481111561207e5761207e613188565b5f83815260fc602052604090205460ff1660048111156120a0576120a0613188565b14610b82576040516319d893ad60e21b815260040160405180910390fd5b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166120f557506001919050565b6040516315c638fb60e31b81525f6004820152602481018390527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063ae31c7d8906044015f604051808303815f87803b15801561215a575f5ffd5b505af192505050801561216b575060015b61217657505f919050565b506001919050565b919050565b5f8061219761012085016101008601612f34565b6001600160a01b0316036121ad5750600161182d565b306121c061012085016101008601612f34565b6001600160a01b0316036121d65750600161182d565b6001600160a01b0382166121f261012085016101008601612f34565b6001600160a01b0316036122085750600161182d565b600461221861014085018561355e565b90501015801561224f5750637f07c94760e01b61223961014085018561355e565b612242916138b9565b6001600160e01b03191614155b8015611758575061175861226b61012085016101008601612f34565b6001600160a01b03163b151590565b5f6001600160a01b0385166122a257604051634c67134d60e11b815260040160405180910390fd5b5f5f835160208501878988f195945050505050565b5f306122c96080870160608801612f34565b6001600160a01b0316036122df576122df6138f1565b6101208501351580156122ff57506122fb61014086018661355e565b1590505b1561230c575060016123d3565b825f0361231a57505f6123d3565b61233e8461232e6080880160608901612f34565b611df060a0890160808a01613328565b5f61235161012087016101008801612f34565b90506101208601355f61236861014089018961355e565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92018290525084519495509384935083925090506020850186888cf194505a905085156123c2576123c28188612b56565b6123ce5f198080612b01565b505050505b949350505050565b8060048111156123ed576123ed613188565b5f83815260fc602052604090205460ff16600481111561240f5761240f613188565b0361242d576040516319d893ad60e21b815260040160405180910390fd5b5f82815260fc60205260409020805482919060ff1916600183600481111561245757612457613188565b0217905550817f6c51882bc2ed67617f77a1e9b9a25d2caad8448647ecb093b357a603b25756348260405161248c91906131f2565b60405180910390a25050565b6033546001600160a01b0316331461157b5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610a4d565b5f54610100900460ff166125185760405162461bcd60e51b8152600401610a4d90613905565b612520612b6a565b61253e6001600160a01b038216156125385781612976565b33612976565b5060c9805461ff001916610100179055565b5f8061256a61256361014085018561355e565b90506118bb565b63ffffffff1690508061259b612586606086016040870161347d565b63ffffffff168361264e90919063ffffffff16565b039392505050565b5f856001600160a01b031663910af6ed856125c8876562726964676560d01b5f61298f565b8887876040518663ffffffff1660e01b81526004016125eb959493929190613950565b6020604051808303815f875af1925050508015612625575060408051601f3d908101601f1916820190925261262291810190613496565b60015b612642576040516314504c7360e31b815260040160405180910390fd5b90505b95945050505050565b5f81831161265c5781611758565b5090919050565b5f8183116126715782611758565b50919050565b815f0361268357505050565b61269d83838360405180602001604052805f81525061227a565b611b7f57604051634c67134d60e11b815260040160405180910390fd5b5f5160206139f95f395f51905f52546001600160a01b031690565b6114f6612498565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff161561271057611b7f83612b90565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa92505050801561276a575060408051601f3d908101601f1916820190925261276791810190613496565b60015b6127cd5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610a4d565b5f5160206139f95f395f51905f52811461283b5760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610a4d565b50611b7f838383612c2b565b6033546001600160a01b038381169116148061288f575061287a6d636861696e5f7761746368646f6760901b6001612c4f565b6001600160a01b0316826001600160a01b0316145b15612898575050565b8080156128d257506128bd6e6272696467655f7761746368646f6760881b6001612c4f565b6001600160a01b0316826001600160a01b0316145b156128db575050565b6040516395383ea160e01b815260040160405180910390fd5b5f856001600160a01b031663ce9d082085612919876562726964676560d01b5f61298f565b8887876040518663ffffffff1660e01b815260040161293c959493929190613950565b5f6040518083038186803b158015612952575f5ffd5b505afa925050508015612963575060015b61296e57505f612645565b506001612645565b606580546001600160a01b03191690556114f681612ce8565b5f7f0000000000000000000000001670130000000000000000000000000000000006604051633632b1fb60e11b81526001600160401b03861660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa158015612a0d573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117559190613991565b6040516001600160e01b0319821660248201525f90819081906001600160a01b0386169060440160408051601f198184030181529181526020820180516001600160e01b03166301ffc9a760e01b17905251612a8d91906139ac565b5f60405180830381855afa9150503d805f8114612ac5576040519150601f19603f3d011682016040523d82523d5f602084013e612aca565b606091505b5091509150818015612add575080516020145b15612af95780806020019051810190612af6919061382b565b92505b505092915050565b604080516060810182528481526001600160a01b03909316602084018190526001600160401b03909216920182905260fd9290925560fe8054600160a01b9092026001600160e01b0319909216909217179055565b612b61603f826139c7565b821015610b8257fe5b5f54610100900460ff1661157b5760405162461bcd60e51b8152600401610a4d90613905565b6001600160a01b0381163b612bfd5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610a4d565b5f5160206139f95f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b612c3483612d39565b5f82511180612c405750805b15611b7f57610a038383612d78565b5f7f0000000000000000000000001670130000000000000000000000000000000006604051633632b1fb60e11b81524660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa158015612cc4573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117589190613991565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b612d4281612b90565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606117588383604051806060016040528060278152602001613a196027913960605f5f856001600160a01b031685604051612db491906139ac565b5f60405180830381855af49150503d805f8114612dec576040519150601f19603f3d011682016040523d82523d5f602084013e612df1565b606091505b5091509150612e0286838387612e0c565b9695505050505050565b60608315612e7a5782515f03612e73576001600160a01b0385163b612e735760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610a4d565b50816123d3565b6123d38383815115612e8f5781518083602001fd5b8060405162461bcd60e51b8152600401610a4d91906139e6565b5f6101608284031215612671575f5ffd5b80151581146114f6575f5ffd5b5f5f60408385031215612ed8575f5ffd5b82356001600160401b03811115612eed575f5ffd5b612ef985828601612ea9565b9250506020830135612f0a81612eba565b809150509250929050565b6001600160a01b03811681146114f6575f5ffd5b803561217e81612f15565b5f60208284031215612f44575f5ffd5b813561175881612f15565b5f60208284031215612f5f575f5ffd5b81356001600160401b03811115612f74575f5ffd5b6123d384828501612ea9565b5f5b83811015612f9a578181015183820152602001612f82565b50505f910152565b5f8151808452612fb9816020860160208601612f80565b601f01601f19169290920160200192915050565b80516001600160401b031682525f6020820151612ff560208501826001600160401b03169052565b50604082015161300d604085018263ffffffff169052565b50606082015161302860608501826001600160a01b03169052565b50608082015161304360808501826001600160401b03169052565b5060a082015161305e60a08501826001600160a01b03169052565b5060c082015161307960c08501826001600160401b03169052565b5060e082015161309460e08501826001600160a01b03169052565b506101008201516130b16101008501826001600160a01b03169052565b506101208201516101208401526101408201516101606101408501526123d3610160850182612fa2565b828152604060208201525f6117556040830184612fcd565b5f5f5f60408486031215613105575f5ffd5b83356001600160401b0381111561311a575f5ffd5b61312686828701612ea9565b93505060208401356001600160401b03811115613141575f5ffd5b8401601f81018613613151575f5ffd5b80356001600160401b03811115613166575f5ffd5b866020828401011115613177575f5ffd5b939660209190910195509293505050565b634e487b7160e01b5f52602160045260245ffd5b600581106131ac576131ac613188565b9052565b604081016131be828561319c565b600483106131ce576131ce613188565b8260208301529392505050565b5f602082840312156131eb575f5ffd5b5035919050565b6020810161182d828461319c565b634e487b7160e01b5f52604160045260245ffd5b60405161016081016001600160401b038111828210171561323757613237613200565b60405290565b5f82601f83011261324c575f5ffd5b81356001600160401b0381111561326557613265613200565b604051601f8201601f19908116603f011681016001600160401b038111828210171561329357613293613200565b6040528181528382016020018510156132aa575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f5f604083850312156132d7575f5ffd5b82356132e281612f15565b915060208301356001600160401b038111156132fc575f5ffd5b6133088582860161323d565b9150509250929050565b80356001600160401b038116811461217e575f5ffd5b5f60208284031215613338575f5ffd5b61175882613312565b803563ffffffff8116811461217e575f5ffd5b5f6101608284031215613365575f5ffd5b61336d613214565b905061337882613312565b815261338660208301613312565b602082015261339760408301613341565b60408201526133a860608301612f29565b60608201526133b960808301613312565b60808201526133ca60a08301612f29565b60a08201526133db60c08301613312565b60c08201526133ec60e08301612f29565b60e08201526133fe6101008301612f29565b61010082015261012082810135908201526101408201356001600160401b03811115613428575f5ffd5b6134348482850161323d565b6101408301525092915050565b5f60208284031215613451575f5ffd5b81356001600160401b03811115613466575f5ffd5b6123d384828501613354565b5f61182d3683613354565b5f6020828403121561348d575f5ffd5b61175882613341565b5f602082840312156134a6575f5ffd5b5051919050565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561182d5761182d6134fb565b5f6001600160401b0382166001600160401b038103613543576135436134fb565b60010192915050565b602081525f6117586020830184612fcd565b5f5f8335601e19843603018112613573575f5ffd5b8301803591506001600160401b0382111561358c575f5ffd5b6020019150368190038213156135a0575f5ffd5b9250929050565b634e487b7160e01b5f52601260045260245ffd5b5f5f8335601e198436030181126135d0575f5ffd5b83016020810192503590506001600160401b038111156135ee575f5ffd5b8036038213156135a0575f5ffd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b61363e8261363183613312565b6001600160401b03169052565b5f61364b60208301613312565b6001600160401b0316602084015261366560408301613341565b63ffffffff16604084015261367c60608301612f29565b6001600160a01b0316606084015261369660808301613312565b6001600160401b031660808401526136b060a08301612f29565b6001600160a01b031660a08401526136ca60c08301613312565b6001600160401b031660c08401526136e460e08301612f29565b6001600160a01b031660e08401526136ff6101008301612f29565b6001600160a01b031661010084015261012082810135908401526137276101408301836135bb565b610160610140860152612af6610160860182846135fc565b60a081525f61375160a0830185613624565b905063ffffffff835116602083015263ffffffff602084015116604083015263ffffffff60408401511660608301526060830151151560808301529392505050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b5f6020828403121561383b575f5ffd5b815161175881612eba565b63ffffffff818116838216019081111561182d5761182d6134fb565b604081525f6138746040830185613624565b90508260208301529392505050565b60408152600d60408201526c5441494b4f5f4d45535341474560981b6060820152608060208201525f6117586080830184612fcd565b80356001600160e01b031981169060048410156138ea576001600160e01b0319600485900360031b81901b82161691505b5092915050565b634e487b7160e01b5f52600160045260245ffd5b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6001600160401b038616815260018060a01b0385166020820152836040820152608060608201525f6139866080830184866135fc565b979650505050505050565b5f602082840312156139a1575f5ffd5b815161175881612f15565b5f82516139bd818460208701612f80565b9190910192915050565b5f826139e157634e487b7160e01b5f52601260045260245ffd5b500490565b602081525f6117586020830184612fa256fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220cc4b5f2ca3dea7d8410946ddf36b41dd85a2f9a585e5380cc0eb3a85999d017064736f6c634300081b0033", + "balance": "0x0" + }, + "0x1670130000000000000000000000000000000001": { + "contractName": "Bridge", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000c9": "0x0000000000000000000000000000000000000000000000000000000000000101", + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0167013000000000000000000000000000000001" + }, + "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033", + "balance": "0x033b2dc2c28c26a701e00000" + }, + "0x0167013000000000000000000000000000000002": { + "contractName": "ERC20VaultImpl", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190" + }, + "code": "0x6080604052600436106101ba575f3560e01c806352d1902d116100f25780638456cb5911610092578063a90018a111610062578063a90018a114610508578063d56ad7ac14610528578063e30c39781461053e578063f2fde38b1461055b575f5ffd5b80638456cb59146104935780638abf6077146104a75780638da5cb5b146104bb5780639aa8605c146104d8575f5ffd5b80636f390144116100cd5780636f39014414610439578063715018a61461045857806379ba50971461046c5780637f07c94714610480575f5ffd5b806352d1902d146103c55780635c975abb146103d957806367090ccf146103f9575f5ffd5b80630ecd8be91161015d5780633075db56116101385780633075db561461036b5780633659cfe61461037f5780633f4ba83a1461039e5780634f1ef286146103b2575f5ffd5b80630ecd8be91461031a5780630ed434201461033957806319ab453c1461034c575f5ffd5b806304f3bcec1161019857806304f3bcec14610254578063066fe7b41461028657806306fdde03146102cb5780630e7eeb79146102eb575f5ffd5b80630178733a146101be57806301ffc9a7146101d3578063040944ab14610207575b5f5ffd5b6101d16101cc366004613876565b61057a565b005b3480156101de575f5ffd5b506101f26101ed3660046138bc565b610743565b60405190151581526020015b60405180910390f35b348015610212575f5ffd5b5061023c6102213660046138e3565b6101316020525f90815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020016101fe565b34801561025f575f5ffd5b507f000000000000000000000000167013000000000000000000000000000000000661023c565b348015610291575f5ffd5b506102bd6102a036600461390e565b61013060209081525f928352604080842090915290825290205481565b6040519081526020016101fe565b3480156102d6575f5ffd5b506a195c98cc8c17dd985d5b1d60aa1b6102bd565b3480156102f6575f5ffd5b506101f261030536600461393c565b61012f6020525f908152604090205460ff1681565b348015610325575f5ffd5b5061023c610334366004613957565b610793565b6101d1610347366004613a64565b610ea1565b348015610357575f5ffd5b506101d161036636600461393c565b61118d565b348015610376575f5ffd5b506101f261129e565b34801561038a575f5ffd5b506101d161039936600461393c565b6112b6565b3480156103a9575f5ffd5b506101d161137a565b6101d16103c0366004613b6f565b6113fe565b3480156103d0575f5ffd5b506102bd6114b3565b3480156103e4575f5ffd5b506101f260c954610100900460ff1660021490565b348015610404575f5ffd5b5061023c61041336600461390e565b61012e60209081525f92835260408084209091529082529020546001600160a01b031681565b348015610444575f5ffd5b506102bd610453366004613bbb565b611564565b348015610463575f5ffd5b506101d16115ba565b348015610477575f5ffd5b506101d16115cb565b6101d161048e366004613c00565b611642565b34801561049e575f5ffd5b506101d1611857565b3480156104b2575f5ffd5b5061023c6118d6565b3480156104c6575f5ffd5b506033546001600160a01b031661023c565b3480156104e3575f5ffd5b506104f76104f236600461393c565b6118e4565b6040516101fe959493929190613cb9565b61051b610516366004613d12565b611a39565b6040516101fe9190613d2c565b348015610533575f5ffd5b506102bd6276a70081565b348015610549575f5ffd5b506065546001600160a01b031661023c565b348015610566575f5ffd5b506101d161057536600461393c565b611eee565b61058e60c954610100900460ff1660021490565b156105ac5760405163bae6e2a960e01b815260040160405180910390fd5b60026105ba60c95460ff1690565b60ff16036105db5760405163dfc60d8560e01b815260040160405180910390fd5b6105e56002611f5f565b6105ed611f75565b505f6105fd610140840184613e1c565b61060b916004908290613e65565b8101906106189190613e8c565b90505f5f5f838060200190518101906106319190613f38565b5094509450505092505f81836106479190614056565b90505f6106648561065e60c08b0160a08c0161393c565b8461206b565b60208601519091506106b7906001600160a01b03161561068957886101200135610698565b610698836101208b0135614069565b6106a860c08b0160a08c0161393c565b6001600160a01b031690612136565b6106c760c0890160a08a0161393c565b6001600160a01b0316877f3dea0f5955b148debf6212261e03bd80eaf8534bee43780452d16637dcc22dd587602001518488604051610727939291906001600160a01b039384168152919092166020820152604081019190915260600190565b60405180910390a350505050505061073f6001611f5f565b5050565b5f6001600160e01b0319821662bc399d60e11b148061077257506001600160e01b03198216637f07c94760e01b145b8061078d57506001600160e01b031982166301ffc9a760e01b145b92915050565b5f61079c612141565b60026107aa60c95460ff1690565b60ff16036107cb5760405163dfc60d8560e01b815260040160405180910390fd5b6107d56002611f5f565b6001600160a01b038216158061080b57506001600160a01b038281165f90815261012d6020526040902054600160401b90041615155b8061081e57506001600160a01b0382163b155b1561083c5760405163dc63f98760e01b815260040160405180910390fd5b5f61084d604085016020860161393c565b6001600160a01b0316148061087657504661086b602085018561407c565b6001600160401b0316145b1561089457604051638257f7f560e01b815260040160405180910390fd5b6001600160a01b0382165f90815261012f602052604090205460ff16156108ce576040516375c42fc160e01b815260040160405180910390fd5b5f610130816108e0602087018761407c565b6001600160401b031681526020019081526020015f205f85602001602081019061090a919061393c565b6001600160a01b0316815260208101919091526040015f205490506109326276a70082614056565b4210156109525760405163231d35fb60e11b815260040160405180910390fd5b61012e5f610963602087018761407c565b6001600160401b031681526020019081526020015f205f85602001602081019061098d919061393c565b6001600160a01b03908116825260208201929092526040015f20541691508115610cf3576001600160a01b038281165f90815261012d60209081526040808320815160a08101835281546001600160401b0381168252600160401b810490961693810193909352600160e01b90940460ff169082015260018301805492939192606084019190610a1c90614097565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4890614097565b8015610a935780601f10610a6a57610100808354040283529160200191610a93565b820191905f5260205f20905b815481529060010190602001808311610a7657829003601f168201915b50505050508152602001600282018054610aac90614097565b80601f0160208091040260200160405190810160405280929190818152602001828054610ad890614097565b8015610b235780601f10610afa57610100808354040283529160200191610b23565b820191905f5260205f20905b815481529060010190602001808311610b0657829003601f168201915b505050505081525050905080604051602001610b3f919061412e565b6040516020818303038152906040528051906020012085604051602001610b6691906141b4565b6040516020818303038152906040528051906020012014610b9a57604051632f9d1d7b60e11b815260040160405180910390fd5b6001600160a01b0383165f90815261012d6020526040812080546001600160e81b031916815590610bce600183018261381e565b610bdb600283015f61381e565b50506001600160a01b0383165f81815261012f60205260409020805460ff19166001179055610c119063b8f2e0c560e01b61219b565b8015610c325750610c326001600160a01b03851663b8f2e0c560e01b61219b565b15610cf15760405163b8f2e0c560e01b81526001600160a01b0385811660048301525f602483015284169063b8f2e0c5906044015f604051808303815f87803b158015610c7d575f5ffd5b505af1158015610c8f573d5f5f3e3d5ffd5b505060405163b8f2e0c560e01b81526001600160a01b038681166004830152600160248301528716925063b8f2e0c591506044015f604051808303815f87803b158015610cda575f5ffd5b505af1158015610cec573d5f5f3e3d5ffd5b505050505b505b6001600160a01b0383165f90815261012d602052604090208490610d178282614357565b5083905061012e5f610d2c602088018861407c565b6001600160401b031681526020019081526020015f205f866020016020810190610d56919061393c565b6001600160a01b03166001600160a01b031681526020019081526020015f205f6101000a8154816001600160a01b0302191690836001600160a01b03160217905550426101305f865f016020810190610daf919061407c565b6001600160401b031681526020019081526020015f205f866020016020810190610dd9919061393c565b6001600160a01b03166001600160a01b031681526020019081526020015f2081905550836020016020810190610e0f919061393c565b6001600160a01b0316610e25602086018661407c565b6001600160401b03167f031d68e1805917560c34a5f55a7dd91bef98f911190ed02cdbb53caedae6c39d8486610e5e60608a018a613e1c565b610e6b60808c018c613e1c565b610e7b60608e0160408f01614415565b604051610e8e9796959493929190614430565b60405180910390a35061078d6001611f5f565b6002610eaf60c95460ff1690565b60ff1603610ed05760405163dfc60d8560e01b815260040160405180910390fd5b610eda6002611f5f565b610eee60c954610100900460ff1660021490565b15610f0c5760405163bae6e2a960e01b815260040160405180910390fd5b60a08101511561104b575f610f29647461696b6f60d81b5f61226b565b9050806001600160a01b031663a4b235546040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f67573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f8b9190614487565b610fa857604051631874710f60e01b815260040160405180910390fd5b608082015160405163888775d960e01b81526001600160401b0390911660048201525f906001600160a01b0383169063888775d99060240161014060405180830381865afa158015610ffc573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061102091906144d3565b5160a0840151909150811461104857604051630dc3ea9f60e31b815260040160405180910390fd5b50505b5f611067825f0151836020015184604001518560600151611564565b5f81815261013160205260409020549091506001600160a01b0316156110a057604051638dd940f760e01b815260040160405180910390fd5b5f8181526101316020908152604090912080546001600160a01b031916331790558201516001600160a01b031661111e57816060015134146110f557604051634299323b60e11b815260040160405180910390fd5b611119826060015183604001516001600160a01b031661213690919063ffffffff16565b61114a565b61114a338360400151846060015185602001516001600160a01b0316612304909392919063ffffffff16565b60405133815281907f4e13900a0e52240bc42a70a941392f3766f6789416493003d0e9e400b0ef32ae9060200160405180910390a25061118a6001611f5f565b50565b5f54610100900460ff16158080156111ab57505f54600160ff909116105b806111c45750303b1580156111c457505f5460ff166001145b61122c5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b5f805460ff19166001179055801561124d575f805461ff0019166101001790555b61125682612375565b801561073f575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15050565b5f60026112ad60c95460ff1690565b60ff1614905090565b6001600160a01b037f00000000000000000000000001670130000000000000000000000000000000021630036112fe5760405162461bcd60e51b815260040161122390614596565b7f00000000000000000000000001670130000000000000000000000000000000026001600160a01b03166113306123d3565b6001600160a01b0316146113565760405162461bcd60e51b8152600401611223906145e2565b61135f816123ee565b604080515f8082526020820190925261118a918391906123f6565b61138e60c954610100900460ff1660021490565b6113ab5760405163bae6e2a960e01b815260040160405180910390fd5b6113bf60c9805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a16113fc335f612565565b565b6001600160a01b037f00000000000000000000000001670130000000000000000000000000000000021630036114465760405162461bcd60e51b815260040161122390614596565b7f00000000000000000000000001670130000000000000000000000000000000026001600160a01b03166114786123d3565b6001600160a01b03161461149e5760405162461bcd60e51b8152600401611223906145e2565b6114a7826123ee565b61073f828260016123f6565b5f306001600160a01b037f000000000000000000000000016701300000000000000000000000000000000216146115525760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401611223565b505f5160206150cb5f395f51905f5290565b6040805160208082018790526bffffffffffffffffffffffff19606087811b82168486015286901b166054830152606880830185905283518084039091018152608890920190925280519101205b949350505050565b6115c2612141565b6113fc5f61256d565b60655433906001600160a01b031681146116395760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b6064820152608401611223565b61118a8161256d565b61165660c954610100900460ff1660021490565b156116745760405163bae6e2a960e01b815260040160405180910390fd5b600261168260c95460ff1690565b60ff16036116a35760405163dfc60d8560e01b815260040160405180910390fd5b6116ad6002611f5f565b5f80808080806116bf87890189614639565b9550955095509550955095505f6116d4612586565b90506116df856126a5565b845f841561172657505f83815261013160205260409020546001600160a01b03168015611726575f8481526101316020526040902080546001600160a01b03191690559050805b5f806117328789614056565b905061173f8b858361206b565b91505f6117935f6001600160a01b03168d602001516001600160a01b0316146117685734611772565b6117728334614069565b5a60408051602081019091525f81526001600160a01b038e169291906126e1565b90506001600160a01b0384166117c157806117c157604051632cc319bb60e01b815260040160405180910390fd5b5050835160408086015160208d81015183516001600160a01b0388811682526001600160401b03909416928101929092528216818401528482166060820152608081018b905260a081018a905291518b821693918d16927f153364fe598dfe35e31cd640831e4a90a9effca5f12d8e8ccf2fcb2e14d35bb8919081900360c00190a45050505050505050505061073f6001611f5f565b61186b60c954610100900460ff1660021490565b156118895760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a16113fc336001612565565b5f6118df6123d3565b905090565b61012d6020525f9081526040902080546001820180546001600160401b03831693600160401b84046001600160a01b031693600160e01b900460ff1692909161192c90614097565b80601f016020809104026020016040519081016040528092919081815260200182805461195890614097565b80156119a35780601f1061197a576101008083540402835291602001916119a3565b820191905f5260205f20905b81548152906001019060200180831161198657829003601f168201915b5050505050908060020180546119b890614097565b80601f01602080910402602001604051908101604052809291908181526020018280546119e490614097565b8015611a2f5780601f10611a0657610100808354040283529160200191611a2f565b820191905f5260205f20905b815481529060010190602001808311611a1257829003601f168201915b5050505050905085565b60408051610160810182525f8082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e082018390526101008201839052610120820192909252610140810191909152611aa760c954610100900460ff1660021490565b15611ac55760405163bae6e2a960e01b815260040160405180910390fd5b6002611ad360c95460ff1690565b60ff1603611af45760405163dfc60d8560e01b815260040160405180910390fd5b611afe6002611f5f565b8160c001355f03611b2257604051634299323b60e11b815260040160405180910390fd5b5f80611b3460a085016080860161393c565b6001600160a01b031614611b48575f611b5a565b611b5a60e084013560c0850135614056565b905080611b6d608085016060860161407c565b6001600160401b0316611b809190614056565b341015611ba057604051630a97785560e21b815260040160405180910390fd5b5f611bb160a085016080860161393c565b6001600160a01b031614158015611bf4575061012f5f611bd760a086016080870161393c565b6001600160a01b0316815260208101919091526040015f205460ff165b15611c12576040516375c42fc160e01b815260040160405180910390fd5b611c37611c25606085016040860161393c565b611c32602086018661407c565b61271e565b505f611c4c6562726964676560d01b5f61226b565b90505f5f5f5f611c5c858861277c565b93509350935093505f6040518061016001604052805f6001600160401b03168152602001896060016020810190611c93919061407c565b6001600160401b03168152602001611cb160c08b0160a08c01614751565b63ffffffff1681525f60208083018290526040830191909152336060830152608090910190611ce2908b018b61407c565b6001600160401b031681526020015f6001600160a01b03168a6020016020810190611d0d919061393c565b6001600160a01b031603611d215733611d31565b611d3160408b0160208c0161393c565b6001600160a01b03168152602090810190611d6890611d52908c018c61407c565b6a195c98cc8c17dd985d5b1d60aa1b5b5f612c5c565b6001600160a01b03168152602001611d8660808b0160608c0161407c565b611d99906001600160401b031634614069565b81526020018681525090505f866001600160a01b0316631bdb003734846040518363ffffffff1660e01b8152600401611dd29190613d2c565b5f6040518083038185885af1158015611ded573d5f5f3e3d5ffd5b50505050506040513d5f823e601f3d908101601f19168201604052611e159190810190614777565b98509050611e2960608a0160408b0161393c565b6001600160a01b03168860a001516001600160a01b0316827f5f338013bf4edc4a223ee7b435dd1e5ba722013c222cfbbf3c66372fbf07f295885f01518d5f016020810190611e78919061407c565b8a602001518f6080016020810190611e90919061393c565b604080516001600160401b0395861681529390941660208401526001600160a01b0391821683850152166060820152608081018a905260a0810189905290519081900360c00190a450505050505050611ee96001611f5f565b919050565b611ef6612141565b606580546001600160a01b0383166001600160a01b03199091168117909155611f276033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60c9805460ff191660ff92909216919091179055565b604080516060810182525f80825260208201819052918101919091526562726964676560d01b611fa681600161226b565b6001600160a01b0316336001600160a01b031614611fd7576040516395383ea160e01b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa158015612013573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906120379190614897565b60208101519092506001600160a01b0316331461206757604051632583296b60e01b815260040160405180910390fd5b5090565b60208301515f906001600160a01b0316612097576120926001600160a01b03841683612136565b61212f565b46845f01516001600160401b0316036120c4575060208301516120926001600160a01b0382168484612cfe565b6120cd84612d2e565b6040516340c10f1960e01b81526001600160a01b03858116600483015260248201859052919250908216906340c10f19906044015f604051808303815f87803b158015612118575f5ffd5b505af115801561212a573d5f5f3e3d5ffd5b505050505b9392505050565b61073f82825a612d70565b6033546001600160a01b031633146113fc5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401611223565b6040516001600160e01b0319821660248201525f90819081906001600160a01b0386169060440160408051601f198184030181529181526020820180516001600160e01b03166301ffc9a760e01b179052516121f791906148ff565b5f60405180830381855afa9150503d805f811461222f576040519150601f19603f3d011682016040523d82523d5f602084013e612234565b606091505b5091509150818015612247575080516020145b1561226357808060200190518101906122609190614487565b92505b505092915050565b5f7f0000000000000000000000001670130000000000000000000000000000000006604051633632b1fb60e11b81524660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa1580156122e0573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061212f919061491a565b6040516001600160a01b038085166024830152831660448201526064810182905261236f9085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152612db3565b50505050565b5f54610100900460ff1661239b5760405162461bcd60e51b815260040161122390614935565b6123a3612e86565b6123c16001600160a01b038216156123bb578161256d565b3361256d565b5060c9805461ff001916610100179055565b5f5160206150cb5f395f51905f52546001600160a01b031690565b61118a612141565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff161561242e5761242983612eac565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015612488575060408051601f3d908101601f1916820190925261248591810190614980565b60015b6124eb5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401611223565b5f5160206150cb5f395f51905f5281146125595760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401611223565b50612429838383612f47565b61073f612141565b606580546001600160a01b031916905561118a81612f6b565b604080516060810182525f80825260208201819052918101919091526562726964676560d01b6125b781600161226b565b6001600160a01b0316336001600160a01b0316146125e8576040516395383ea160e01b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa158015612624573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906126489190614897565b91505f6126688360400151611d626a195c98cc8c17dd985d5b1d60aa1b90565b9050806001600160a01b031683602001516001600160a01b0316146126a057604051632583296b60e01b815260040160405180910390fd5b505090565b6001600160a01b03811615806126c357506001600160a01b03811630145b1561118a57604051635b50f3f960e01b815260040160405180910390fd5b5f6001600160a01b03851661270957604051634c67134d60e11b815260040160405180910390fd5b5f5f835160208501878988f195945050505050565b6001600160a01b038216158061275e5750612749816a195c98cc8c17dd985d5b1d60aa1b6001612c5c565b6001600160a01b0316826001600160a01b0316145b1561073f57604051635b50f3f960e01b815260040160405180910390fd5b6040805160a0810182525f8082526020820181905291810191909152606081810181905260808201819052905f8080806127bc60a088016080890161393c565b6001600160a01b0316036127dd578560c0013592508560e001359150612b3a565b5f61012d816127f260a08a0160808b0161393c565b6001600160a01b03908116825260208201929092526040015f2054600160401b90041614612a675761012d5f61282e60a0890160808a0161393c565b6001600160a01b03908116825260208083019390935260409182015f20825160a08101845281546001600160401b0381168252600160401b810490931694810194909452600160e01b90910460ff169183019190915260018101805460608401919061289990614097565b80601f01602080910402602001604051908101604052809291908181526020018280546128c590614097565b80156129105780601f106128e757610100808354040283529160200191612910565b820191905f5260205f20905b8154815290600101906020018083116128f357829003601f168201915b5050505050815260200160028201805461292990614097565b80601f016020809104026020016040519081016040528092919081815260200182805461295590614097565b80156129a05780601f10612977576101008083540402835291602001916129a0565b820191905f5260205f20905b81548152906001019060200180831161298357829003601f168201915b50505050508152505093505f8660e001358760c001356129c09190614056565b90506129e93330836129d860a08c0160808d0161393c565b6001600160a01b0316929190612304565b6129f960a088016080890161393c565b6001600160a01b03166342966c68826040518263ffffffff1660e01b8152600401612a2691815260200190565b5f604051808303815f87803b158015612a3d575f5ffd5b505af1158015612a4f573d5f5f3e3d5ffd5b505050508660c0013593508660e00135925050612b3a565b6040518060a00160405280466001600160401b03168152602001876080016020810190612a94919061393c565b6001600160a01b03168152602001612aba612ab560a08a0160808b0161393c565b612fbc565b60ff168152602001612ada612ad560a08a0160808b0161393c565b613070565b8152602001612af7612af260a08a0160808b0161393c565b61311a565b90529350612b18612b0e60a088016080890161393c565b8760c00135613160565b9250612b37612b2d60a088016080890161393c565b8760e00135613160565b91505b60e086013515612bd6575f876001600160a01b031663eefbf17e6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b81573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612ba59190614997565b6001600160401b03169050612bd2818660200151896040016020810190612bcc919061393c565b87611564565b9150505b30637f07c9478533612bee60608b0160408c0161393c565b878787604051602001612c06969594939291906149b2565b60408051601f1981840301815290829052612c23916024016149f9565b604051602081830303815290604052915060e01b6020820180516001600160e01b03838183161783525050505094505092959194509250565b5f7f0000000000000000000000001670130000000000000000000000000000000006604051633632b1fb60e11b81526001600160401b03861660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa158015612cda573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115b2919061491a565b6040516001600160a01b03831660248201526044810182905261242990849063a9059cbb60e01b90606401612338565b80516001600160401b03165f90815261012e60209081526040808320828501516001600160a01b0390811685529252909120541680611ee95761078d82613262565b815f03612d7c57505050565b612d9683838360405180602001604052805f8152506126e1565b61242957604051634c67134d60e11b815260040160405180910390fd5b5f612e07826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166134579092919063ffffffff16565b905080515f1480612e27575080806020019051810190612e279190614487565b6124295760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401611223565b5f54610100900460ff166113fc5760405162461bcd60e51b815260040161122390614935565b6001600160a01b0381163b612f195760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401611223565b5f5160206150cb5f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b612f5083613465565b5f82511180612f5c5750805b156124295761236f83836134a4565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b60408051600481526024810182526020810180516001600160e01b031663313ce56760e01b17905290515f91829182916001600160a01b0386169161300191906148ff565b5f60405180830381855afa9150503d805f8114613039576040519150601f19603f3d011682016040523d82523d5f602084013e61303e565b606091505b5091509150818015613051575080516020145b61305c5760126115b2565b808060200190518101906115b29190614a0b565b60408051600481526024810182526020810180516001600160e01b03166395d89b4160e01b17905290516060915f9182916001600160a01b038616916130b691906148ff565b5f60405180830381855afa9150503d805f81146130ee576040519150601f19603f3d011682016040523d82523d5f602084013e6130f3565b606091505b5091509150816131115760405180602001604052805f8152506115b2565b6115b2816134c9565b60408051600481526024810182526020810180516001600160e01b03166306fdde0360e01b17905290516060915f9182916001600160a01b038616916130b691906148ff565b5f815f0361316f57505f61078d565b6040516370a0823160e01b815230600482015283905f906001600160a01b038316906370a0823190602401602060405180830381865afa1580156131b5573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906131d99190614980565b90506131f06001600160a01b038316333087612304565b6040516370a0823160e01b815230600482015281906001600160a01b038416906370a0823190602401602060405180830381865afa158015613234573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906132589190614980565b6122609190614069565b5f5f6132766033546001600160a01b031690565b6020840151845160408087015160608801516080890151925161329f9695949390602401614a26565b60408051601f198184030181529190526020810180516001600160e01b0316636c0db62b60e01b17905290506132e56c0627269646765645f657263323609c1b5f61226b565b816040516132f290613855565b6132fd929190614a88565b604051809103905ff080158015613316573d5f5f3e3d5ffd5b506001600160a01b038082165f90815261012d602090815260409182902087518154928901519389015160ff16600160e01b0260ff60e01b1994909516600160401b026001600160e01b03199093166001600160401b039091161791909117919091169190911781556060850151919350849160018201906133989082614aab565b50608082015160028201906133ad9082614aab565b505083516001600160401b039081165f90815261012e6020908152604080832082890180516001600160a01b039081168652919093529281902080546001600160a01b0319168885169081179091559151885160608a015160808b0151848c01519451959850929095169516937fb6b427556e8cb0ebf9175da4bc48c64c4f56e44cfaf8c3ab5ebf8e2ea1309079936134499391929190614b65565b60405180910390a450919050565b60606115b284845f85613636565b61346e81612eac565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606061212f83836040518060600160405280602781526020016150eb6027913961370d565b606060408251106134e8578180602001905181019061078d9190614b9d565b8151602003613623575f5b60208160ff161080156135285750828160ff168151811061351657613516614bce565b01602001516001600160f81b03191615155b1561353f578061353781614be2565b9150506134f3565b5f8160ff166001600160401b0381111561355b5761355b61399e565b6040519080825280601f01601f191660200182016040528015613585576020820181803683370190505b5090505f91505b60208260ff161080156135c15750838260ff16815181106135af576135af614bce565b01602001516001600160f81b03191615155b1561212f57838260ff16815181106135db576135db614bce565b602001015160f81c60f81b818360ff16815181106135fb576135fb614bce565b60200101906001600160f81b03191690815f1a9053508161361b81614be2565b92505061358c565b505060408051602081019091525f815290565b6060824710156136975760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401611223565b5f5f866001600160a01b031685876040516136b291906148ff565b5f6040518083038185875af1925050503d805f81146136ec576040519150601f19603f3d011682016040523d82523d5f602084013e6136f1565b606091505b509150915061370287838387613781565b979650505050505050565b60605f5f856001600160a01b03168560405161372991906148ff565b5f60405180830381855af49150503d805f8114613761576040519150601f19603f3d011682016040523d82523d5f602084013e613766565b606091505b509150915061377786838387613781565b9695505050505050565b606083156137ef5782515f036137e8576001600160a01b0385163b6137e85760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401611223565b50816115b2565b6115b283838151156138045781518083602001fd5b8060405162461bcd60e51b815260040161122391906149f9565b50805461382a90614097565b5f825580601f10613839575050565b601f0160209004905f5260205f209081019061118a9190613862565b6104ca80614c0183390190565b5b80821115612067575f8155600101613863565b5f5f60408385031215613887575f5ffd5b82356001600160401b0381111561389c575f5ffd5b830161016081860312156138ae575f5ffd5b946020939093013593505050565b5f602082840312156138cc575f5ffd5b81356001600160e01b03198116811461212f575f5ffd5b5f602082840312156138f3575f5ffd5b5035919050565b6001600160a01b038116811461118a575f5ffd5b5f5f6040838503121561391f575f5ffd5b823591506020830135613931816138fa565b809150509250929050565b5f6020828403121561394c575f5ffd5b813561212f816138fa565b5f5f60408385031215613968575f5ffd5b82356001600160401b0381111561397d575f5ffd5b830160a0818603121561398e575f5ffd5b91506020830135613931816138fa565b634e487b7160e01b5f52604160045260245ffd5b60405160a081016001600160401b03811182821017156139d4576139d461399e565b60405290565b60405161014081016001600160401b03811182821017156139d4576139d461399e565b60405161016081016001600160401b03811182821017156139d4576139d461399e565b604051601f8201601f191681016001600160401b0381118282101715613a4857613a4861399e565b604052919050565b6001600160401b038116811461118a575f5ffd5b5f60c0828403128015613a75575f5ffd5b5060405160c081016001600160401b0381118282101715613a9857613a9861399e565b604052823581526020830135613aad816138fa565b60208201526040830135613ac0816138fa565b6040820152606083810135908201526080830135613add81613a50565b608082015260a0928301359281019290925250919050565b5f6001600160401b03821115613b0d57613b0d61399e565b50601f01601f191660200190565b5f82601f830112613b2a575f5ffd5b8135602083015f613b42613b3d84613af5565b613a20565b9050828152858383011115613b55575f5ffd5b828260208301375f92810160200192909252509392505050565b5f5f60408385031215613b80575f5ffd5b8235613b8b816138fa565b915060208301356001600160401b03811115613ba5575f5ffd5b613bb185828601613b1b565b9150509250929050565b5f5f5f5f60808587031215613bce575f5ffd5b843593506020850135613be0816138fa565b92506040850135613bf0816138fa565b9396929550929360600135925050565b5f5f60208385031215613c11575f5ffd5b82356001600160401b03811115613c26575f5ffd5b8301601f81018513613c36575f5ffd5b80356001600160401b03811115613c4b575f5ffd5b856020828401011115613c5c575f5ffd5b6020919091019590945092505050565b5f5b83811015613c86578181015183820152602001613c6e565b50505f910152565b5f8151808452613ca5816020860160208601613c6c565b601f01601f19169290920160200192915050565b6001600160401b03861681526001600160a01b038516602082015260ff8416604082015260a0606082018190525f90613cf490830185613c8e565b8281036080840152613d068185613c8e565b98975050505050505050565b5f610100828403128015613d24575f5ffd5b509092915050565b60208152613d466020820183516001600160401b03169052565b5f6020830151613d6160408401826001600160401b03169052565b50604083015163ffffffff811660608401525060608301516001600160a01b03811660808401525060808301516001600160401b03811660a08401525060a08301516001600160a01b03811660c08401525060c08301516001600160401b03811660e08401525060e08301516001600160a01b038116610100840152506101008301516001600160a01b03811661012084015250610120830151610140830152610140830151610160808401526115b2610180840182613c8e565b5f5f8335601e19843603018112613e31575f5ffd5b8301803591506001600160401b03821115613e4a575f5ffd5b602001915036819003821315613e5e575f5ffd5b9250929050565b5f5f85851115613e73575f5ffd5b83861115613e7f575f5ffd5b5050820193919092039150565b5f60208284031215613e9c575f5ffd5b81356001600160401b03811115613eb1575f5ffd5b6115b284828501613b1b565b8051611ee981613a50565b8051611ee9816138fa565b60ff8116811461118a575f5ffd5b8051611ee981613ed3565b5f82601f830112613efb575f5ffd5b8151602083015f613f0e613b3d84613af5565b9050828152858383011115613f21575f5ffd5b613f2f836020830184613c6c565b95945050505050565b5f5f5f5f5f5f60c08789031215613f4d575f5ffd5b86516001600160401b03811115613f62575f5ffd5b870160a0818a031215613f73575f5ffd5b613f7b6139b2565b8151613f8681613a50565b81526020820151613f96816138fa565b6020820152613fa760408301613ee1565b604082015260608201516001600160401b03811115613fc4575f5ffd5b613fd08b828501613eec565b60608301525060808201516001600160401b03811115613fee575f5ffd5b613ffa8b828501613eec565b6080830152509650614010905060208801613ec8565b945061401e60408801613ec8565b6060880151608089015160a090990151979a96995090979096909590945092505050565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561078d5761078d614042565b8181038181111561078d5761078d614042565b5f6020828403121561408c575f5ffd5b813561212f81613a50565b600181811c908216806140ab57607f821691505b6020821081036140c957634e487b7160e01b5f52602260045260245ffd5b50919050565b6001600160401b03815116825260018060a01b03602082015116602083015260ff60408201511660408301525f606082015160a0606085015261411560a0850182613c8e565b9050608083015184820360808601526122608282613c8e565b602081525f61212f60208301846140cf565b8035611ee981613ed3565b5f5f8335601e19843603018112614160575f5ffd5b83016020810192503590506001600160401b0381111561417e575f5ffd5b803603821315613e5e575f5ffd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b602081525f82356141c481613a50565b6001600160401b03811660208401525060208301356141e2816138fa565b6001600160a01b031660408381019190915283013561420081613ed3565b60ff8116606084015250614217606084018461414b565b60a0608085015261422c60c08501828461418c565b91505061423c608085018561414b565b848303601f190160a086015261377783828461418c565b601f82111561242957805f5260205f20601f840160051c810160208510156142785750805b601f840160051c820191505b81811015614297575f8155600101614284565b5050505050565b6001600160401b038311156142b5576142b561399e565b6142c9836142c38354614097565b83614253565b5f601f8411600181146142fa575f85156142e35750838201355b5f19600387901b1c1916600186901b178355614297565b5f83815260208120601f198716915b828110156143295786850135825560209485019460019092019101614309565b5086821015614345575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b813561436281613a50565b6001600160401b03811690508154816001600160401b03198216178355602084013561438d816138fa565b6001600160e01b031991909116909117604091821b68010000000000000000600160e01b03161782555f908301356143c481613ed3565b825460ff60e01b191660e09190911b60ff60e01b16178255506143ea6060830183613e1c565b6143f881836001860161429e565b50506144076080830183613e1c565b61236f81836002860161429e565b5f60208284031215614425575f5ffd5b813561212f81613ed3565b6001600160a01b0388811682528716602082015260a0604082018190525f9061445c908301878961418c565b828103606084015261446f81868861418c565b91505060ff8316608083015298975050505050505050565b5f60208284031215614497575f5ffd5b8151801515811461212f575f5ffd5b80516bffffffffffffffffffffffff81168114611ee9575f5ffd5b805162ffffff81168114611ee9575f5ffd5b5f6101408284031280156144e5575f5ffd5b506144ee6139da565b825181526144fe60208401613ebd565b602082015261450f604084016144a6565b6040820152614520606084016144a6565b606082015261453160808401613ebd565b608082015261454260a08401613ebd565b60a082015261455360c08401613ebd565b60c082015261456460e084016144c1565b60e08201526145766101008401613ee1565b61010082015261458961012084016144c1565b6101208201529392505050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b8035611ee9816138fa565b5f5f5f5f5f5f60c0878903121561464e575f5ffd5b86356001600160401b03811115614663575f5ffd5b870160a0818a031215614674575f5ffd5b61467c6139b2565b813561468781613a50565b81526020820135614697816138fa565b60208201526146a860408301614140565b604082015260608201356001600160401b038111156146c5575f5ffd5b6146d18b828501613b1b565b60608301525060808201356001600160401b038111156146ef575f5ffd5b6146fb8b828501613b1b565b608083015250965061471190506020880161462e565b945061471f6040880161462e565b959894975094956060810135955060808101359460a0909101359350915050565b63ffffffff8116811461118a575f5ffd5b5f60208284031215614761575f5ffd5b813561212f81614740565b8051611ee981614740565b5f5f60408385031215614788575f5ffd5b825160208401519092506001600160401b038111156147a5575f5ffd5b830161016081860312156147b7575f5ffd5b6147bf6139fd565b6147c882613ebd565b81526147d660208301613ebd565b60208201526147e76040830161476c565b60408201526147f860608301613ec8565b606082015261480960808301613ebd565b608082015261481a60a08301613ec8565b60a082015261482b60c08301613ebd565b60c082015261483c60e08301613ec8565b60e082015261484e6101008301613ec8565b61010082015261012082810151908201526101408201516001600160401b03811115614878575f5ffd5b61488487828501613eec565b6101408301525080925050509250929050565b5f60608284031280156148a8575f5ffd5b50604051606081016001600160401b03811182821017156148cb576148cb61399e565b6040528251815260208301516148e0816138fa565b602082015260408301516148f381613a50565b60408201529392505050565b5f8251614910818460208701613c6c565b9190910192915050565b5f6020828403121561492a575f5ffd5b815161212f816138fa565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f60208284031215614990575f5ffd5b5051919050565b5f602082840312156149a7575f5ffd5b815161212f81613a50565b60c081525f6149c460c08301896140cf565b6001600160a01b0397881660208401529590961660408201526060810193909352608083019190915260a09091015292915050565b602081525f61212f6020830184613c8e565b5f60208284031215614a1b575f5ffd5b815161212f81613ed3565b6001600160a01b038781168252861660208201526001600160401b038516604082015260ff8416606082015260c0608082018190525f90614a6990830185613c8e565b82810360a0840152614a7b8185613c8e565b9998505050505050505050565b6001600160a01b03831681526040602082018190525f906115b290830184613c8e565b81516001600160401b03811115614ac457614ac461399e565b614ad881614ad28454614097565b84614253565b6020601f821160018114614b0a575f8315614af35750848201515b5f19600385901b1c1916600184901b178455614297565b5f84815260208120601f198516915b82811015614b395787850151825560209485019460019092019101614b19565b5084821015614b5657868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b606081525f614b776060830186613c8e565b8281036020840152614b898186613c8e565b91505060ff83166040830152949350505050565b5f60208284031215614bad575f5ffd5b81516001600160401b03811115614bc2575f5ffd5b6115b284828501613eec565b634e487b7160e01b5f52603260045260245ffd5b5f60ff821660ff8103614bf757614bf7614042565b6001019291505056fe60806040526040516104ca3803806104ca833981016040819052610022916102d2565b61002d82825f610034565b50506103ed565b61003d8361005f565b5f825111806100495750805b1561005a57610058838361009e565b505b505050565b610068816100ca565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606100c383836040518060600160405280602781526020016104a36027913961017d565b9392505050565b6001600160a01b0381163b61013c5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f5f856001600160a01b03168560405161019991906103a0565b5f60405180830381855af49150503d805f81146101d1576040519150601f19603f3d011682016040523d82523d5f602084013e6101d6565b606091505b5090925090506101e8868383876101f2565b9695505050505050565b606083156102605782515f03610259576001600160a01b0385163b6102595760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610133565b508161026a565b61026a8383610272565b949350505050565b8151156102825781518083602001fd5b8060405162461bcd60e51b815260040161013391906103bb565b634e487b7160e01b5f52604160045260245ffd5b5f5b838110156102ca5781810151838201526020016102b2565b50505f910152565b5f5f604083850312156102e3575f5ffd5b82516001600160a01b03811681146102f9575f5ffd5b60208401519092506001600160401b03811115610314575f5ffd5b8301601f81018513610324575f5ffd5b80516001600160401b0381111561033d5761033d61029c565b604051601f8201601f19908116603f011681016001600160401b038111828210171561036b5761036b61029c565b604052818152828201602001871015610382575f5ffd5b6103938260208301602086016102b0565b8093505050509250929050565b5f82516103b18184602087016102b0565b9190910192915050565b602081525f82518060208401526103d98160408501602087016102b0565b601f01601f19169190910160400192915050565b60aa806103f95f395ff3fe608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122043d2f051f5e881c8198a3a914d51fd9f4bdb9ce73f603ca51dccc21640965b5864736f6c634300081b0033", + "balance": "0x0" + }, + "0x1670130000000000000000000000000000000002": { + "contractName": "ERC20Vault", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000c9": "0x0000000000000000000000000000000000000000000000000000000000000201", + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0167013000000000000000000000000000000002" + }, + "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033", + "balance": "0x0" + }, + "0x0167013000000000000000000000000000000003": { + "contractName": "ERC721VaultImpl", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190" + }, + "code": "0x60806040526004361061013c575f3560e01c806352d1902d116100b35780638456cb591161006d5780638456cb59146103865780638abf60771461039a5780638da5cb5b146103ae5780639aa8605c146103cb578063e30c3978146103fa578063f2fde38b14610417575f5ffd5b806352d1902d146102d75780635c975abb146102eb57806367090ccf1461030b578063715018a61461034b57806379ba50971461035f5780637f07c94714610373575f5ffd5b806319ab453c1161010457806319ab453c1461023e5780631f59a8301461025d5780633075db561461027d5780633659cfe6146102915780633f4ba83a146102b05780634f1ef286146102c4575f5ffd5b80630178733a1461014057806301ffc9a71461015557806304f3bcec1461018957806306fdde03146101cf578063150b7a02146101fa575b5f5ffd5b61015361014e366004612851565b610436565b005b348015610160575f5ffd5b5061017461016f366004612897565b6105e9565b60405190151581526020015b60405180910390f35b348015610194575f5ffd5b507f00000000000000000000000016701300000000000000000000000000000000065b6040516001600160a01b039091168152602001610180565b3480156101da575f5ffd5b506b195c98cdcc8c57dd985d5b1d60a21b5b604051908152602001610180565b348015610205575f5ffd5b50610225610214366004612921565b630a85bd0160e11b95945050505050565b6040516001600160e01b03199091168152602001610180565b348015610249575f5ffd5b5061015361025836600461298e565b610639565b61027061026b3660046129a9565b61074a565b6040516101809190612a2d565b348015610288575f5ffd5b50610174610bfb565b34801561029c575f5ffd5b506101536102ab36600461298e565b610c13565b3480156102bb575f5ffd5b50610153610cda565b6101536102d2366004612c49565b610d5e565b3480156102e2575f5ffd5b506101ec610e13565b3480156102f6575f5ffd5b5061017460c954610100900460ff1660021490565b348015610316575f5ffd5b506101b7610325366004612c95565b61012e60209081525f92835260408084209091529082529020546001600160a01b031681565b348015610356575f5ffd5b50610153610ec4565b34801561036a575f5ffd5b50610153610ed5565b610153610381366004612cc3565b610f4c565b348015610391575f5ffd5b506101536110b5565b3480156103a5575f5ffd5b506101b7611134565b3480156103b9575f5ffd5b506033546001600160a01b03166101b7565b3480156103d6575f5ffd5b506103ea6103e536600461298e565b611142565b6040516101809493929190612d01565b348015610405575f5ffd5b506065546001600160a01b03166101b7565b348015610422575f5ffd5b5061015361043136600461298e565b61128d565b61044a60c954610100900460ff1660021490565b156104685760405163bae6e2a960e01b815260040160405180910390fd5b600261047660c95460ff1690565b60ff16036104975760405163dfc60d8560e01b815260040160405180910390fd5b6104a160026112fe565b6104a9611314565b505f6104b9610140840184612d50565b6104c7916004908290612d92565b8101906104d49190612db9565b90505f5f828060200190518101906104ec9190612ee7565b9350505091505f610510838760a001602081019061050a919061298e565b8461140a565b905061053a61012087013561052b60c0890160a08a0161298e565b6001600160a01b031690611586565b61054a60c0870160a0880161298e565b6001600160a01b0316857fe48bef18455e47bca14864ab6e82dffa29df148b051c09de95aec44ecf13598c8560200151848687516001600160401b0381111561059557610595612b1d565b6040519080825280602002602001820160405280156105be578160200160208202803683370190505b506040516105cf9493929190613026565b60405180910390a3505050506105e560016112fe565b5050565b5f6001600160e01b0319821662bc399d60e11b148061061857506001600160e01b03198216637f07c94760e01b145b8061063357506001600160e01b031982166301ffc9a760e01b145b92915050565b5f54610100900460ff161580801561065757505f54600160ff909116105b806106705750303b15801561067057505f5460ff166001145b6106d85760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b5f805460ff1916600117905580156106f9575f805461ff0019166101001790555b61070282611591565b80156105e5575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15050565b60408051610160810182525f8082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e0820183905261010082018390526101208201929092526101408101919091526107b860c954610100900460ff1660021490565b156107d65760405163bae6e2a960e01b815260040160405180910390fd5b6107df826130e5565b8060e00151518160c0015151146108095760405163196e8a4160e31b815260040160405180910390fd5b60808101516001600160a01b0316610834576040516303f8a7d360e01b815260040160405180910390fd5b600261084260c95460ff1690565b60ff16036108635760405163dfc60d8560e01b815260040160405180910390fd5b61086d60026112fe565b61087d60808401606085016131b8565b6001600160401b03163410156108a657604051630178ce0b60e31b815260040160405180910390fd5b5f6108b460c08501856131d3565b905090505f5b8181101561090d576108cf60e08601866131d3565b828181106108df576108df613218565b905060200201355f1461090557604051634299323b60e11b815260040160405180910390fd5b6001016108ba565b5061093a90506380ac58cd60e01b61092b60a086016080870161298e565b6001600160a01b0316906115ef565b61095757604051633ee915f560e11b815260040160405180910390fd5b61097c61096a606085016040860161298e565b61097760208601866131b8565b6116bf565b5f5f6109878561171e565b6040805161016081019091525f808252929450909250602081016109b16080890160608a016131b8565b6001600160401b031681526020016109cf60c0890160a08a0161322c565b63ffffffff1681525f60208083018290526040830191909152336060830152608090910190610a00908901896131b8565b6001600160401b031681526020015f6001600160a01b0316886020016020810190610a2b919061298e565b6001600160a01b031603610a3f5733610a4f565b610a4f6040890160208a0161298e565b6001600160a01b03168152602090810190610a8790610a70908a018a6131b8565b6b195c98cdcc8c57dd985d5b1d60a21b5b5f611bf7565b6001600160a01b03168152602001610aa56080890160608a016131b8565b610ab8906001600160401b03163461325b565b815260200184905290505f610ad66562726964676560d01b82611ca1565b6001600160a01b0316631bdb003734846040518363ffffffff1660e01b8152600401610b029190612a2d565b5f6040518083038185885af1158015610b1d573d5f5f3e3d5ffd5b50505050506040513d5f823e601f3d908101601f19168201604052610b459190810190613279565b96509050610b59606088016040890161298e565b6001600160a01b03168660a001516001600160a01b0316827fabbf62a1459339f9ac59136d313a5ccd83d2706cc6d4c04d90642520169144dc8960c0015187602001518c6080016020810190610baf919061298e565b610bbc60c08f018f6131d3565b8f8060e00190610bcc91906131d3565b604051610bdf97969594939291906133c9565b60405180910390a450505050610bf560016112fe565b50919050565b5f6002610c0a60c95460ff1690565b60ff1614905090565b6001600160a01b037f0000000000000000000000000167013000000000000000000000000000000003163003610c5b5760405162461bcd60e51b81526004016106cf90613425565b7f00000000000000000000000001670130000000000000000000000000000000036001600160a01b0316610c8d611d41565b6001600160a01b031614610cb35760405162461bcd60e51b81526004016106cf90613471565b610cbc81611d5c565b604080515f80825260208201909252610cd791839190611d64565b50565b610cee60c954610100900460ff1660021490565b610d0b5760405163bae6e2a960e01b815260040160405180910390fd5b610d1f60c9805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610d5c335f611ed3565b565b6001600160a01b037f0000000000000000000000000167013000000000000000000000000000000003163003610da65760405162461bcd60e51b81526004016106cf90613425565b7f00000000000000000000000001670130000000000000000000000000000000036001600160a01b0316610dd8611d41565b6001600160a01b031614610dfe5760405162461bcd60e51b81526004016106cf90613471565b610e0782611d5c565b6105e582826001611d64565b5f306001600160a01b037f00000000000000000000000001670130000000000000000000000000000000031614610eb25760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c000000000000000060648201526084016106cf565b505f516020613eb45f395f51905f5290565b610ecc611ed7565b610d5c5f611f31565b60655433906001600160a01b03168114610f435760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016106cf565b610cd781611f31565b610f6060c954610100900460ff1660021490565b15610f7e5760405163bae6e2a960e01b815260040160405180910390fd5b6002610f8c60c95460ff1690565b60ff1603610fad5760405163dfc60d8560e01b815260040160405180910390fd5b610fb760026112fe565b5f808080610fc7858701876134bd565b93509350935093505f610fd8611f4a565b9050610fe38361206a565b5f610fef86858561140a565b90506110046001600160a01b03851634611586565b836001600160a01b0316856001600160a01b0316835f01517f895f73e418d1bbbad2a311d085fad00e5d98a960e9f2afa4b942071d39bec43a85604001518a6020015186898a516001600160401b0381111561106257611062612b1d565b60405190808252806020026020018201604052801561108b578160200160208202803683370190505b5060405161109d9594939291906135b6565b60405180910390a45050505050506105e560016112fe565b6110c960c954610100900460ff1660021490565b156110e75760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a1610d5c336001611ed3565b5f61113d611d41565b905090565b61012d6020525f9081526040902080546001820180546001600160401b03831693600160401b9093046001600160a01b03169291906111809061360e565b80601f01602080910402602001604051908101604052809291908181526020018280546111ac9061360e565b80156111f75780601f106111ce576101008083540402835291602001916111f7565b820191905f5260205f20905b8154815290600101906020018083116111da57829003601f168201915b50505050509080600201805461120c9061360e565b80601f01602080910402602001604051908101604052809291908181526020018280546112389061360e565b80156112835780601f1061125a57610100808354040283529160200191611283565b820191905f5260205f20905b81548152906001019060200180831161126657829003601f168201915b5050505050905084565b611295611ed7565b606580546001600160a01b0383166001600160a01b031990911681179091556112c66033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60c9805460ff191660ff92909216919091179055565b604080516060810182525f80825260208201819052918101919091526562726964676560d01b611345816001611ca1565b6001600160a01b0316336001600160a01b031614611376576040516395383ea160e01b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa1580156113b2573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113d69190613640565b60208101519092506001600160a01b0316331461140657604051632583296b60e01b815260040160405180910390fd5b5090565b805183515f9190466001600160401b03909116036114d457846020015191505f5b818110156114ce57826001600160a01b03166342842e0e308787858151811061145657611456613218565b60209081029190910101516040516001600160e01b031960e086901b1681526001600160a01b03938416600482015292909116602483015260448201526064015f604051808303815f87803b1580156114ad575f5ffd5b505af11580156114bf573d5f5f3e3d5ffd5b5050505080600101905061142b565b5061157e565b6114dd856120a6565b91505f5b8181101561157c57826001600160a01b03166340c10f198686848151811061150b5761150b613218565b60200260200101516040518363ffffffff1660e01b81526004016115449291906001600160a01b03929092168252602082015260400190565b5f604051808303815f87803b15801561155b575f5ffd5b505af115801561156d573d5f5f3e3d5ffd5b505050508060010190506114e1565b505b509392505050565b6105e582825a6120ed565b5f54610100900460ff166115b75760405162461bcd60e51b81526004016106cf906136a8565b6115bf612130565b6115dd6001600160a01b038216156115d75781611f31565b33611f31565b5060c9805461ff001916610100179055565b6040516001600160e01b0319821660248201525f90819081906001600160a01b0386169060440160408051601f198184030181529181526020820180516001600160e01b03166301ffc9a760e01b1790525161164b91906136f3565b5f60405180830381855afa9150503d805f8114611683576040519150601f19603f3d011682016040523d82523d5f602084013e611688565b606091505b509150915081801561169b575080516020145b156116b757808060200190518101906116b4919061370e565b92505b505092915050565b6001600160a01b038216158061170057506116eb816b195c98cdcc8c57dd985d5b1d60a21b6001611bf7565b6001600160a01b0316826001600160a01b0316145b156105e557604051635b50f3f960e01b815260040160405180910390fd5b604080516080810182525f808252602082015260609181018290528082018290525f61174d60c08501856131d3565b91505f905061012d8161176660a088016080890161298e565b6001600160a01b03908116825260208201929092526040015f208054909250600160401b90041615611a39576040805160808101825282546001600160401b0381168252600160401b90046001600160a01b031660208201526001830180549192849290840191906117d79061360e565b80601f01602080910402602001604051908101604052809291908181526020018280546118039061360e565b801561184e5780601f106118255761010080835404028352916020019161184e565b820191905f5260205f20905b81548152906001019060200180831161183157829003601f168201915b505050505081526020016002820180546118679061360e565b80601f01602080910402602001604051908101604052809291908181526020018280546118939061360e565b80156118de5780601f106118b5576101008083540402835291602001916118de565b820191905f5260205f20905b8154815290600101906020018083116118c157829003601f168201915b50505050508152505092505f5b82811015611a335761190360a087016080880161298e565b6001600160a01b03166342842e0e333061192060c08b018b6131d3565b8681811061193057611930613218565b6040516001600160e01b031960e088901b1681526001600160a01b039586166004820152949093166024850152506020909102013560448201526064015f604051808303815f87803b158015611984575f5ffd5b505af1158015611996573d5f5f3e3d5ffd5b506119ab9250505060a087016080880161298e565b6001600160a01b03166342966c686119c660c08901896131d3565b848181106119d6576119d6613218565b905060200201356040518263ffffffff1660e01b81526004016119fb91815260200190565b5f604051808303815f87803b158015611a12575f5ffd5b505af1158015611a24573d5f5f3e3d5ffd5b505050508060010190506118eb565b50611b6b565b6040518060800160405280466001600160401b03168152602001866080016020810190611a66919061298e565b6001600160a01b03168152602001611a8c611a8760a0890160808a0161298e565b612156565b8152602001611aa9611aa460a0890160808a0161298e565b612200565b905292505f5b82811015611b6957611ac760a087016080880161298e565b6001600160a01b03166342842e0e3330611ae460c08b018b6131d3565b86818110611af457611af4613218565b6040516001600160e01b031960e088901b1681526001600160a01b039586166004820152949093166024850152506020909102013560448201526064015f604051808303815f87803b158015611b48575f5ffd5b505af1158015611b5a573d5f5f3e3d5ffd5b50505050806001019050611aaf565b505b5030637f07c9478333611b846060890160408a0161298e565b611b9160c08a018a6131d3565b604051602001611ba595949392919061372d565b60408051601f1981840301815290829052611bc2916024016137be565b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050925050915091565b5f7f0000000000000000000000001670130000000000000000000000000000000006604051633632b1fb60e11b81526001600160401b03861660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa158015611c75573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c9991906137d0565b949350505050565b5f7f0000000000000000000000001670130000000000000000000000000000000006604051633632b1fb60e11b81524660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa158015611d16573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d3a91906137d0565b9392505050565b5f516020613eb45f395f51905f52546001600160a01b031690565b610cd7611ed7565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff1615611d9c57611d9783612246565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611df6575060408051601f3d908101601f19168201909252611df3918101906137eb565b60015b611e595760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b60648201526084016106cf565b5f516020613eb45f395f51905f528114611ec75760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b60648201526084016106cf565b50611d978383836122e1565b6105e55b6033546001600160a01b03163314610d5c5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016106cf565b606580546001600160a01b0319169055610cd78161230b565b604080516060810182525f80825260208201819052918101919091526562726964676560d01b611f7b816001611ca1565b6001600160a01b0316336001600160a01b031614611fac576040516395383ea160e01b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa158015611fe8573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061200c9190613640565b91505f61202d8360400151610a816b195c98cdcc8c57dd985d5b1d60a21b90565b9050806001600160a01b031683602001516001600160a01b03161461206557604051632583296b60e01b815260040160405180910390fd5b505090565b6001600160a01b038116158061208857506001600160a01b03811630145b15610cd757604051635b50f3f960e01b815260040160405180910390fd5b80516001600160401b03165f90815261012e60209081526040808320828501516001600160a01b03908116855292529091205416806120e8576106338261235c565b919050565b815f036120f957505050565b61211383838360405180602001604052805f81525061252a565b611d9757604051634c67134d60e11b815260040160405180910390fd5b5f54610100900460ff16610d5c5760405162461bcd60e51b81526004016106cf906136a8565b60408051600481526024810182526020810180516001600160e01b03166395d89b4160e01b17905290516060915f9182916001600160a01b0386169161219c91906136f3565b5f60405180830381855afa9150503d805f81146121d4576040519150601f19603f3d011682016040523d82523d5f602084013e6121d9565b606091505b5091509150816121f75760405180602001604052805f815250611c99565b611c9981612567565b60408051600481526024810182526020810180516001600160e01b03166306fdde0360e01b17905290516060915f9182916001600160a01b0386169161219c91906136f3565b6001600160a01b0381163b6122b35760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016106cf565b5f516020613eb45f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b6122ea836126d4565b5f825111806122f65750805b15611d97576123058383612713565b50505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f5f6123706033546001600160a01b031690565b60208401518451604080870151606088015191516123949594939290602401613802565b60408051601f198184030181529190526020810180516001600160e01b031663689ccd8d60e11b17905290506123db6d627269646765645f65726337323160901b5f611ca1565b816040516123e890612844565b6123f392919061384e565b604051809103905ff08015801561240c573d5f5f3e3d5ffd5b506001600160a01b038082165f90815261012d60209081526040918290208751815492890151909416600160401b026001600160e01b03199092166001600160401b03909416939093171782558501519193508491600182019061247090826138bc565b506060820151600282019061248590826138bc565b505083516001600160401b039081165f90815261012e6020908152604080832082890180516001600160a01b039081168652919093529281902080546001600160a01b03191688851690811790915591518851828a015160608b01519351949750919094169493909316927f44977f2d30fe1e3aee2c1476f2f95aaacaf34e44b9359c403da01fcc93fd751b9261251c9290613976565b60405180910390a450919050565b5f6001600160a01b03851661255257604051634c67134d60e11b815260040160405180910390fd5b5f5f835160208501878988f195945050505050565b606060408251106125865781806020019051810190610633919061399a565b81516020036126c1575f5b60208160ff161080156125c65750828160ff16815181106125b4576125b4613218565b01602001516001600160f81b03191615155b156125dd57806125d5816139cb565b915050612591565b5f8160ff166001600160401b038111156125f9576125f9612b1d565b6040519080825280601f01601f191660200182016040528015612623576020820181803683370190505b5090505f91505b60208260ff1610801561265f5750838260ff168151811061264d5761264d613218565b01602001516001600160f81b03191615155b15611d3a57838260ff168151811061267957612679613218565b602001015160f81c60f81b818360ff168151811061269957612699613218565b60200101906001600160f81b03191690815f1a905350816126b9816139cb565b92505061262a565b505060408051602081019091525f815290565b6126dd81612246565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060611d3a8383604051806060016040528060278152602001613ed46027913960605f5f856001600160a01b03168560405161274f91906136f3565b5f60405180830381855af49150503d805f8114612787576040519150601f19603f3d011682016040523d82523d5f602084013e61278c565b606091505b509150915061279d868383876127a7565b9695505050505050565b606083156128155782515f0361280e576001600160a01b0385163b61280e5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016106cf565b5081611c99565b611c99838381511561282a5781518083602001fd5b8060405162461bcd60e51b81526004016106cf91906137be565b6104ca806139ea83390190565b5f5f60408385031215612862575f5ffd5b82356001600160401b03811115612877575f5ffd5b83016101608186031215612889575f5ffd5b946020939093013593505050565b5f602082840312156128a7575f5ffd5b81356001600160e01b031981168114611d3a575f5ffd5b6001600160a01b0381168114610cd7575f5ffd5b80356120e8816128be565b5f5f83601f8401126128ed575f5ffd5b5081356001600160401b03811115612903575f5ffd5b60208301915083602082850101111561291a575f5ffd5b9250929050565b5f5f5f5f5f60808688031215612935575f5ffd5b8535612940816128be565b94506020860135612950816128be565b93506040860135925060608601356001600160401b03811115612971575f5ffd5b61297d888289016128dd565b969995985093965092949392505050565b5f6020828403121561299e575f5ffd5b8135611d3a816128be565b5f602082840312156129b9575f5ffd5b81356001600160401b038111156129ce575f5ffd5b82016101008185031215611d3a575f5ffd5b5f5b838110156129fa5781810151838201526020016129e2565b50505f910152565b5f8151808452612a198160208601602086016129e0565b601f01601f19169290920160200192915050565b60208152612a476020820183516001600160401b03169052565b5f6020830151612a6260408401826001600160401b03169052565b50604083015163ffffffff811660608401525060608301516001600160a01b03811660808401525060808301516001600160401b03811660a08401525060a08301516001600160a01b03811660c08401525060c08301516001600160401b03811660e08401525060e08301516001600160a01b038116610100840152506101008301516001600160a01b0381166101208401525061012083015161014083015261014083015161016080840152611c99610180840182612a02565b634e487b7160e01b5f52604160045260245ffd5b604051608081016001600160401b0381118282101715612b5357612b53612b1d565b60405290565b60405161010081016001600160401b0381118282101715612b5357612b53612b1d565b60405161016081016001600160401b0381118282101715612b5357612b53612b1d565b604051601f8201601f191681016001600160401b0381118282101715612bc757612bc7612b1d565b604052919050565b5f6001600160401b03821115612be757612be7612b1d565b50601f01601f191660200190565b5f82601f830112612c04575f5ffd5b8135602083015f612c1c612c1784612bcf565b612b9f565b9050828152858383011115612c2f575f5ffd5b828260208301375f92810160200192909252509392505050565b5f5f60408385031215612c5a575f5ffd5b8235612c65816128be565b915060208301356001600160401b03811115612c7f575f5ffd5b612c8b85828601612bf5565b9150509250929050565b5f5f60408385031215612ca6575f5ffd5b823591506020830135612cb8816128be565b809150509250929050565b5f5f60208385031215612cd4575f5ffd5b82356001600160401b03811115612ce9575f5ffd5b612cf5858286016128dd565b90969095509350505050565b6001600160401b03851681526001600160a01b03841660208201526080604082018190525f90612d3390830185612a02565b8281036060840152612d458185612a02565b979650505050505050565b5f5f8335601e19843603018112612d65575f5ffd5b8301803591506001600160401b03821115612d7e575f5ffd5b60200191503681900382131561291a575f5ffd5b5f5f85851115612da0575f5ffd5b83861115612dac575f5ffd5b5050820193919092039150565b5f60208284031215612dc9575f5ffd5b81356001600160401b03811115612dde575f5ffd5b611c9984828501612bf5565b6001600160401b0381168114610cd7575f5ffd5b80516120e881612dea565b80516120e8816128be565b5f82601f830112612e23575f5ffd5b8151602083015f612e36612c1784612bcf565b9050828152858383011115612e49575f5ffd5b612e578360208301846129e0565b95945050505050565b5f6001600160401b03821115612e7857612e78612b1d565b5060051b60200190565b5f82601f830112612e91575f5ffd5b8151612e9f612c1782612e60565b8082825260208201915060208360051b860101925085831115612ec0575f5ffd5b602085015b83811015612edd578051835260209283019201612ec5565b5095945050505050565b5f5f5f5f60808587031215612efa575f5ffd5b84516001600160401b03811115612f0f575f5ffd5b850160808188031215612f20575f5ffd5b612f28612b31565b8151612f3381612dea565b81526020820151612f43816128be565b602082015260408201516001600160401b03811115612f60575f5ffd5b612f6c89828501612e14565b60408301525060608201516001600160401b03811115612f8a575f5ffd5b612f9689828501612e14565b6060830152509450612fac905060208601612e09565b9250612fba60408601612e09565b915060608501516001600160401b03811115612fd4575f5ffd5b612fe087828801612e82565b91505092959194509250565b5f8151808452602084019350602083015f5b8281101561301c578151865260209586019590910190600101612ffe565b5093949350505050565b6001600160a01b038581168252841660208201526080604082018190525f9061305190830185612fec565b8281036060840152612d458185612fec565b80356120e881612dea565b63ffffffff81168114610cd7575f5ffd5b80356120e88161306e565b5f82601f830112613099575f5ffd5b81356130a7612c1782612e60565b8082825260208201915060208360051b8601019250858311156130c8575f5ffd5b602085015b83811015612edd5780358352602092830192016130cd565b5f61010082360312156130f6575f5ffd5b6130fe612b59565b61310783613063565b8152613115602084016128d2565b6020820152613126604084016128d2565b604082015261313760608401613063565b6060820152613148608084016128d2565b608082015261315960a0840161307f565b60a082015260c08301356001600160401b03811115613176575f5ffd5b6131823682860161308a565b60c08301525060e08301356001600160401b038111156131a0575f5ffd5b6131ac3682860161308a565b60e08301525092915050565b5f602082840312156131c8575f5ffd5b8135611d3a81612dea565b5f5f8335601e198436030181126131e8575f5ffd5b8301803591506001600160401b03821115613201575f5ffd5b6020019150600581901b360382131561291a575f5ffd5b634e487b7160e01b5f52603260045260245ffd5b5f6020828403121561323c575f5ffd5b8135611d3a8161306e565b634e487b7160e01b5f52601160045260245ffd5b8181038181111561063357610633613247565b80516120e88161306e565b5f5f6040838503121561328a575f5ffd5b825160208401519092506001600160401b038111156132a7575f5ffd5b830161016081860312156132b9575f5ffd5b6132c1612b7c565b6132ca82612dfe565b81526132d860208301612dfe565b60208201526132e96040830161326e565b60408201526132fa60608301612e09565b606082015261330b60808301612dfe565b608082015261331c60a08301612e09565b60a082015261332d60c08301612dfe565b60c082015261333e60e08301612e09565b60e08201526133506101008301612e09565b61010082015261012082810151908201526101408201516001600160401b0381111561337a575f5ffd5b61338687828501612e14565b6101408301525080925050509250929050565b8183525f6001600160fb1b038311156133b0575f5ffd5b8260051b80836020870137939093016020019392505050565b6001600160401b03881681526001600160a01b0387811660208301528616604082015260a0606082018190525f906134049083018688613399565b8281036080840152613417818587613399565b9a9950505050505050505050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b5f5f5f5f608085870312156134d0575f5ffd5b84356001600160401b038111156134e5575f5ffd5b8501608081880312156134f6575f5ffd5b6134fe612b31565b813561350981612dea565b81526020820135613519816128be565b602082015260408201356001600160401b03811115613536575f5ffd5b61354289828501612bf5565b60408301525060608201356001600160401b03811115613560575f5ffd5b61356c89828501612bf5565b60608301525094506135829050602086016128d2565b9250613590604086016128d2565b915060608501356001600160401b038111156135aa575f5ffd5b612fe08782880161308a565b6001600160401b03861681526001600160a01b0385811660208301528416604082015260a0606082018190525f906135f090830185612fec565b82810360808401526136028185612fec565b98975050505050505050565b600181811c9082168061362257607f821691505b602082108103610bf557634e487b7160e01b5f52602260045260245ffd5b5f6060828403128015613651575f5ffd5b50604051606081016001600160401b038111828210171561367457613674612b1d565b604052825181526020830151613689816128be565b6020820152604083015161369c81612dea565b60408201529392505050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f82516137048184602087016129e0565b9190910192915050565b5f6020828403121561371e575f5ffd5b81518015158114611d3a575f5ffd5b608081526001600160401b03865116608082015260018060a01b0360208701511660a08201525f6040870151608060c084015261376e610100840182612a02565b90506060880151607f198483030160e085015261378b8282612a02565b6001600160a01b0389811660208701528816604086015291506137ab9050565b8281036060840152613602818587613399565b602081525f611d3a6020830184612a02565b5f602082840312156137e0575f5ffd5b8151611d3a816128be565b5f602082840312156137fb575f5ffd5b5051919050565b6001600160a01b038681168252851660208201526001600160401b038416604082015260a0606082018190525f9061383c90830185612a02565b82810360808401526136028185612a02565b6001600160a01b03831681526040602082018190525f90611c9990830184612a02565b601f821115611d9757805f5260205f20601f840160051c810160208510156138965750805b601f840160051c820191505b818110156138b5575f81556001016138a2565b5050505050565b81516001600160401b038111156138d5576138d5612b1d565b6138e9816138e3845461360e565b84613871565b6020601f82116001811461391b575f83156139045750848201515b5f19600385901b1c1916600184901b1784556138b5565b5f84815260208120601f198516915b8281101561394a578785015182556020948501946001909201910161392a565b508482101561396757868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b604081525f6139886040830185612a02565b8281036020840152612e578185612a02565b5f602082840312156139aa575f5ffd5b81516001600160401b038111156139bf575f5ffd5b611c9984828501612e14565b5f60ff821660ff81036139e0576139e0613247565b6001019291505056fe60806040526040516104ca3803806104ca833981016040819052610022916102d2565b61002d82825f610034565b50506103ed565b61003d8361005f565b5f825111806100495750805b1561005a57610058838361009e565b505b505050565b610068816100ca565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606100c383836040518060600160405280602781526020016104a36027913961017d565b9392505050565b6001600160a01b0381163b61013c5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f5f856001600160a01b03168560405161019991906103a0565b5f60405180830381855af49150503d805f81146101d1576040519150601f19603f3d011682016040523d82523d5f602084013e6101d6565b606091505b5090925090506101e8868383876101f2565b9695505050505050565b606083156102605782515f03610259576001600160a01b0385163b6102595760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610133565b508161026a565b61026a8383610272565b949350505050565b8151156102825781518083602001fd5b8060405162461bcd60e51b815260040161013391906103bb565b634e487b7160e01b5f52604160045260245ffd5b5f5b838110156102ca5781810151838201526020016102b2565b50505f910152565b5f5f604083850312156102e3575f5ffd5b82516001600160a01b03811681146102f9575f5ffd5b60208401519092506001600160401b03811115610314575f5ffd5b8301601f81018513610324575f5ffd5b80516001600160401b0381111561033d5761033d61029c565b604051601f8201601f19908116603f011681016001600160401b038111828210171561036b5761036b61029c565b604052818152828201602001871015610382575f5ffd5b6103938260208301602086016102b0565b8093505050509250929050565b5f82516103b18184602087016102b0565b9190910192915050565b602081525f82518060208401526103d98160408501602087016102b0565b601f01601f19169190910160400192915050565b60aa806103f95f395ff3fe608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220671f195ff4eec35e3e3fd6339c6df16c6f9b7026d48461fc5692b5e43520a60664736f6c634300081b0033", + "balance": "0x0" + }, + "0x1670130000000000000000000000000000000003": { + "contractName": "ERC721Vault", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000c9": "0x0000000000000000000000000000000000000000000000000000000000000101", + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0167013000000000000000000000000000000003" + }, + "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033", + "balance": "0x0" + }, + "0x0167013000000000000000000000000000000004": { + "contractName": "ERC1155VaultImpl", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190" + }, + "code": "0x608060405260043610610147575f3560e01c806367090ccf116100b35780638da5cb5b1161006d5780638da5cb5b146103765780639aa8605c14610393578063bc197c81146103c2578063e30c397814610409578063f23a6e6114610426578063f2fde38b14610452575f5ffd5b806367090ccf146102d3578063715018a61461031357806379ba5097146103275780637f07c9471461033b5780638456cb591461034e5780638abf607714610362575f5ffd5b80633075db56116101045780633075db56146102455780633659cfe6146102595780633f4ba83a146102785780634f1ef2861461028c57806352d1902d1461029f5780635c975abb146102b3575f5ffd5b80630178733a1461014b57806301ffc9a71461016057806304f3bcec1461019457806306fdde03146101da57806319ab453c146102065780631f59a83014610225575b5f5ffd5b61015e6101593660046127ca565b610471565b005b34801561016b575f5ffd5b5061017f61017a366004612810565b6105e7565b60405190151581526020015b60405180910390f35b34801561019f575f5ffd5b507f00000000000000000000000016701300000000000000000000000000000000065b6040516001600160a01b03909116815260200161018b565b3480156101e5575f5ffd5b506c195c98cc4c4d4d57dd985d5b1d609a1b5b60405190815260200161018b565b348015610211575f5ffd5b5061015e610220366004612856565b610606565b610238610233366004612871565b61071f565b60405161018b91906128f5565b348015610250575f5ffd5b5061017f610bd1565b348015610264575f5ffd5b5061015e610273366004612856565b610be9565b348015610283575f5ffd5b5061015e610cb0565b61015e61029a366004612b11565b610d34565b3480156102aa575f5ffd5b506101f8610de9565b3480156102be575f5ffd5b5061017f60c954610100900460ff1660021490565b3480156102de575f5ffd5b506101c26102ed366004612b5d565b61012e60209081525f92835260408084209091529082529020546001600160a01b031681565b34801561031e575f5ffd5b5061015e610e9a565b348015610332575f5ffd5b5061015e610eab565b61015e610349366004612bcf565b610f22565b348015610359575f5ffd5b5061015e61104e565b34801561036d575f5ffd5b506101c26110cd565b348015610381575f5ffd5b506033546001600160a01b03166101c2565b34801561039e575f5ffd5b506103b26103ad366004612856565b6110db565b60405161018b9493929190612c0d565b3480156103cd575f5ffd5b506103f06103dc366004612c9c565b63bc197c8160e01b98975050505050505050565b6040516001600160e01b0319909116815260200161018b565b348015610414575f5ffd5b506065546001600160a01b03166101c2565b348015610431575f5ffd5b506103f0610440366004612d5c565b63f23a6e6160e01b9695505050505050565b34801561045d575f5ffd5b5061015e61046c366004612856565b611226565b61048560c954610100900460ff1660021490565b156104a35760405163bae6e2a960e01b815260040160405180910390fd5b60026104b160c95460ff1690565b60ff16036104d25760405163dfc60d8560e01b815260040160405180910390fd5b6104dc6002611297565b6104e46112ad565b505f6104f4610140840184612dd2565b610502916004908290612e14565b81019061050f9190612e3b565b90505f5f5f838060200190518101906105289190612f69565b94509450505092505f61054f848860a00160208101906105489190612856565b85856113a3565b905061057961012088013561056a60c08a0160a08b01612856565b6001600160a01b031690611493565b61058960c0880160a08901612856565b6001600160a01b0316867fe48bef18455e47bca14864ab6e82dffa29df148b051c09de95aec44ecf13598c86602001518487876040516105cc94939291906130d1565b60405180910390a350505050506105e36001611297565b5050565b5f6105f18261149e565b806106005750610600826114ed565b92915050565b5f54610100900460ff161580801561062457505f54600160ff909116105b8061063d5750303b15801561063d57505f5460ff166001145b6106a55760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b5f805460ff1916600117905580156106c6575f805461ff0019166101001790555b6106cf82611521565b6106d761157f565b80156105e3575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15050565b60408051610160810182525f8082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e08201839052610100820183905261012082019290925261014081019190915261078d60c954610100900460ff1660021490565b156107ab5760405163bae6e2a960e01b815260040160405180910390fd5b6107b482613190565b8060e00151518160c0015151146107de5760405163196e8a4160e31b815260040160405180910390fd5b60808101516001600160a01b0316610809576040516303f8a7d360e01b815260040160405180910390fd5b600261081760c95460ff1690565b60ff16036108385760405163dfc60d8560e01b815260040160405180910390fd5b6108426002611297565b6108526080840160608501613263565b6001600160401b031634101561087b57604051630178ce0b60e31b815260040160405180910390fd5b5f61088960e085018561327e565b905090505f5b818110156108e2576108a460e086018661327e565b828181106108b4576108b46132c3565b905060200201355f036108da57604051634299323b60e11b815260040160405180910390fd5b60010161088f565b5061090f9050636cdb3d1360e11b61090060a0860160808701612856565b6001600160a01b0316906115a5565b61092c57604051633ee915f560e11b815260040160405180910390fd5b61095161093f6060850160408601612856565b61094c6020860186613263565b611675565b5f5f61095c856116d5565b6040805161016081019091525f808252929450909250602081016109866080890160608a01613263565b6001600160401b031681526020016109a460c0890160a08a016132d7565b63ffffffff1681525f602080830182905260408301919091523360608301526080909101906109d590890189613263565b6001600160401b031681526020015f6001600160a01b0316886020016020810190610a009190612856565b6001600160a01b031603610a145733610a24565b610a246040890160208a01612856565b6001600160a01b03168152602090810190610a5d90610a45908a018a613263565b6c195c98cc4c4d4d57dd985d5b1d609a1b5b5f611b9c565b6001600160a01b03168152602001610a7b6080890160608a01613263565b610a8e906001600160401b031634613306565b815260200184905290505f610aac6562726964676560d01b82611c3e565b6001600160a01b0316631bdb003734846040518363ffffffff1660e01b8152600401610ad891906128f5565b5f6040518083038185885af1158015610af3573d5f5f3e3d5ffd5b50505050506040513d5f823e601f3d908101601f19168201604052610b1b9190810190613324565b96509050610b2f6060880160408901612856565b6001600160a01b03168660a001516001600160a01b0316827fabbf62a1459339f9ac59136d313a5ccd83d2706cc6d4c04d90642520169144dc8960c0015187602001518c6080016020810190610b859190612856565b610b9260c08f018f61327e565b8f8060e00190610ba2919061327e565b604051610bb59796959493929190613474565b60405180910390a450505050610bcb6001611297565b50919050565b5f6002610be060c95460ff1690565b60ff1614905090565b6001600160a01b037f0000000000000000000000000167013000000000000000000000000000000004163003610c315760405162461bcd60e51b815260040161069c906134d0565b7f00000000000000000000000001670130000000000000000000000000000000046001600160a01b0316610c63611cde565b6001600160a01b031614610c895760405162461bcd60e51b815260040161069c9061351c565b610c9281611cf9565b604080515f80825260208201909252610cad91839190611d01565b50565b610cc460c954610100900460ff1660021490565b610ce15760405163bae6e2a960e01b815260040160405180910390fd5b610cf560c9805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610d32335f611e70565b565b6001600160a01b037f0000000000000000000000000167013000000000000000000000000000000004163003610d7c5760405162461bcd60e51b815260040161069c906134d0565b7f00000000000000000000000001670130000000000000000000000000000000046001600160a01b0316610dae611cde565b6001600160a01b031614610dd45760405162461bcd60e51b815260040161069c9061351c565b610ddd82611cf9565b6105e382826001611d01565b5f306001600160a01b037f00000000000000000000000001670130000000000000000000000000000000041614610e885760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c0000000000000000606482015260840161069c565b505f5160206140915f395f51905f5290565b610ea2611e74565b610d325f611ece565b60655433906001600160a01b03168114610f195760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b606482015260840161069c565b610cad81611ece565b610f3660c954610100900460ff1660021490565b15610f545760405163bae6e2a960e01b815260040160405180910390fd5b6002610f6260c95460ff1690565b60ff1603610f835760405163dfc60d8560e01b815260040160405180910390fd5b610f8d6002611297565b5f80808080610f9e86880188613568565b945094509450945094505f610fb1611ee7565b9050610fbc84612008565b5f610fc9878686866113a3565b9050610fde6001600160a01b03861634611493565b846001600160a01b0316866001600160a01b0316835f01517f895f73e418d1bbbad2a311d085fad00e5d98a960e9f2afa4b942071d39bec43a85604001518b60200151868a8a604051611035959493929190613689565b60405180910390a4505050505050506105e36001611297565b61106260c954610100900460ff1660021490565b156110805760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a1610d32336001611e70565b5f6110d6611cde565b905090565b61012d6020525f9081526040902080546001820180546001600160401b03831693600160401b9093046001600160a01b0316929190611119906136e1565b80601f0160208091040260200160405190810160405280929190818152602001828054611145906136e1565b80156111905780601f1061116757610100808354040283529160200191611190565b820191905f5260205f20905b81548152906001019060200180831161117357829003601f168201915b5050505050908060020180546111a5906136e1565b80601f01602080910402602001604051908101604052809291908181526020018280546111d1906136e1565b801561121c5780601f106111f35761010080835404028352916020019161121c565b820191905f5260205f20905b8154815290600101906020018083116111ff57829003601f168201915b5050505050905084565b61122e611e74565b606580546001600160a01b0383166001600160a01b0319909116811790915561125f6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60c9805460ff191660ff92909216919091179055565b604080516060810182525f80825260208201819052918101919091526562726964676560d01b6112de816001611c3e565b6001600160a01b0316336001600160a01b03161461130f576040516395383ea160e01b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa15801561134b573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061136f9190613713565b60208101519092506001600160a01b0316331461139f57604051632583296b60e01b815260040160405180910390fd5b5090565b5f46855f01516001600160401b03160361142157506020840151604051631759616b60e11b81526001600160a01b03821690632eb2c2d6906113ef90309088908890889060040161377b565b5f604051808303815f87803b158015611406575f5ffd5b505af1158015611418573d5f5f3e3d5ffd5b5050505061148b565b61142a85612044565b60405163d81d0a1560e01b81529091506001600160a01b0382169063d81d0a159061145d908790879087906004016137d4565b5f604051808303815f87803b158015611474575f5ffd5b505af1158015611486573d5f5f3e3d5ffd5b505050505b949350505050565b6105e382825a61208b565b5f6001600160e01b0319821662bc399d60e11b14806114cd57506001600160e01b03198216637f07c94760e01b145b8061060057506001600160e01b031982166301ffc9a760e01b1492915050565b5f6001600160e01b03198216630271189760e51b148061060057506301ffc9a760e01b6001600160e01b0319831614610600565b5f54610100900460ff166115475760405162461bcd60e51b815260040161069c90613809565b61154f61157f565b61156d6001600160a01b038216156115675781611ece565b33611ece565b5060c9805461ff001916610100179055565b5f54610100900460ff16610d325760405162461bcd60e51b815260040161069c90613809565b6040516001600160e01b0319821660248201525f90819081906001600160a01b0386169060440160408051601f198184030181529181526020820180516001600160e01b03166301ffc9a760e01b179052516116019190613854565b5f60405180830381855afa9150503d805f8114611639576040519150601f19603f3d011682016040523d82523d5f602084013e61163e565b606091505b5091509150818015611651575080516020145b1561166d578080602001905181019061166a919061386f565b92505b505092915050565b6001600160a01b03821615806116b757506116a2816c195c98cc4c4d4d57dd985d5b1d609a1b6001611b9c565b6001600160a01b0316826001600160a01b0316145b156105e357604051635b50f3f960e01b815260040160405180910390fd5b604080516080810182525f808252602082015260609181018290528082018290525f61012d8161170b60a0870160808801612856565b6001600160a01b03908116825260208201929092526040015f208054909250600160401b90041615611a05576040805160808101825282546001600160401b0381168252600160401b90046001600160a01b0316602082015260018301805491928492908401919061177c906136e1565b80601f01602080910402602001604051908101604052809291908181526020018280546117a8906136e1565b80156117f35780601f106117ca576101008083540402835291602001916117f3565b820191905f5260205f20905b8154815290600101906020018083116117d657829003601f168201915b5050505050815260200160028201805461180c906136e1565b80601f0160208091040260200160405190810160405280929190818152602001828054611838906136e1565b80156118835780601f1061185a57610100808354040283529160200191611883565b820191905f5260205f20905b81548152906001019060200180831161186657829003601f168201915b50505050508152505091508360800160208101906118a19190612856565b6001600160a01b0316632eb2c2d633306118be60c089018961327e565b6118cb60e08b018b61327e565b6040518763ffffffff1660e01b81526004016118ec9695949392919061388e565b5f604051808303815f87803b158015611903575f5ffd5b505af1158015611915573d5f5f3e3d5ffd5b505f925061192991505060c086018661327e565b905090505f5b818110156119fe5761194760a0870160808801612856565b6001600160a01b031663b390c0ab61196260c089018961327e565b84818110611972576119726132c3565b90506020020135888060e00190611989919061327e565b85818110611999576119996132c3565b905060200201356040518363ffffffff1660e01b81526004016119c6929190918252602082015260400190565b5f604051808303815f87803b1580156119dd575f5ffd5b505af11580156119ef573d5f5f3e3d5ffd5b5050505080600101905061192f565b5050611b02565b6040518060800160405280466001600160401b03168152602001856080016020810190611a329190612856565b6001600160a01b03168152602001611a58611a5360a0880160808901612856565b6120ce565b8152602001611a75611a7060a0880160808901612856565b612178565b90529150611a8960a0850160808601612856565b6001600160a01b0316632eb2c2d63330611aa660c089018961327e565b611ab360e08b018b61327e565b6040518763ffffffff1660e01b8152600401611ad49695949392919061388e565b5f604051808303815f87803b158015611aeb575f5ffd5b505af1158015611afd573d5f5f3e3d5ffd5b505050505b5030637f07c9478233611b1b6060880160408901612856565b611b2860c089018961327e565b611b3560e08b018b61327e565b604051602001611b4b97969594939291906138eb565b60408051601f1981840301815290829052611b689160240161399b565b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050509150915091565b5f7f0000000000000000000000001670130000000000000000000000000000000006604051633632b1fb60e11b81526001600160401b03861660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa158015611c1a573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061148b91906139ad565b5f7f0000000000000000000000001670130000000000000000000000000000000006604051633632b1fb60e11b81524660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa158015611cb3573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611cd791906139ad565b9392505050565b5f5160206140915f395f51905f52546001600160a01b031690565b610cad611e74565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff1615611d3957611d34836121be565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611d93575060408051601f3d908101601f19168201909252611d90918101906139c8565b60015b611df65760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b606482015260840161069c565b5f5160206140915f395f51905f528114611e645760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b606482015260840161069c565b50611d34838383612259565b6105e35b6033546001600160a01b03163314610d325760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161069c565b606580546001600160a01b0319169055610cad81612283565b604080516060810182525f80825260208201819052918101919091526562726964676560d01b611f18816001611c3e565b6001600160a01b0316336001600160a01b031614611f49576040516395383ea160e01b815260040160405180910390fd5b336001600160a01b031663d0496d6a6040518163ffffffff1660e01b8152600401606060405180830381865afa158015611f85573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611fa99190613713565b91505f611fcb8360400151610a576c195c98cc4c4d4d57dd985d5b1d609a1b90565b9050806001600160a01b031683602001516001600160a01b03161461200357604051632583296b60e01b815260040160405180910390fd5b505090565b6001600160a01b038116158061202657506001600160a01b03811630145b15610cad57604051635b50f3f960e01b815260040160405180910390fd5b80516001600160401b03165f90815261012e60209081526040808320828501516001600160a01b039081168552925290912054168061208657610600826122d4565b919050565b815f0361209757505050565b6120b183838360405180602001604052805f8152506124a3565b611d3457604051634c67134d60e11b815260040160405180910390fd5b60408051600481526024810182526020810180516001600160e01b03166395d89b4160e01b17905290516060915f9182916001600160a01b038616916121149190613854565b5f60405180830381855afa9150503d805f811461214c576040519150601f19603f3d011682016040523d82523d5f602084013e612151565b606091505b50915091508161216f5760405180602001604052805f81525061148b565b61148b816124e0565b60408051600481526024810182526020810180516001600160e01b03166306fdde0360e01b17905290516060915f9182916001600160a01b038616916121149190613854565b6001600160a01b0381163b61222b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161069c565b5f5160206140915f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b6122628361264d565b5f8251118061226e5750805b15611d345761227d838361268c565b50505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f5f6122e86033546001600160a01b031690565b602084015184516040808701516060880151915161230c95949392906024016139df565b60408051601f198184030181529190526020810180516001600160e01b031663689ccd8d60e11b17905290506123546e627269646765645f6572633131353560881b5f611c3e565b81604051612361906127bd565b61236c929190613a2b565b604051809103905ff080158015612385573d5f5f3e3d5ffd5b506001600160a01b038082165f90815261012d60209081526040918290208751815492890151909416600160401b026001600160e01b03199092166001600160401b0390941693909317178255850151919350849160018201906123e99082613a99565b50606082015160028201906123fe9082613a99565b505083516001600160401b039081165f90815261012e6020908152604080832082890180516001600160a01b039081168652919093529281902080546001600160a01b03191688851690811790915591518851828a015160608b01519351949750919094169493909316927f44977f2d30fe1e3aee2c1476f2f95aaacaf34e44b9359c403da01fcc93fd751b926124959290613b53565b60405180910390a450919050565b5f6001600160a01b0385166124cb57604051634c67134d60e11b815260040160405180910390fd5b5f5f835160208501878988f195945050505050565b606060408251106124ff57818060200190518101906106009190613b77565b815160200361263a575f5b60208160ff1610801561253f5750828160ff168151811061252d5761252d6132c3565b01602001516001600160f81b03191615155b15612556578061254e81613ba8565b91505061250a565b5f8160ff166001600160401b03811115612572576125726129e5565b6040519080825280601f01601f19166020018201604052801561259c576020820181803683370190505b5090505f91505b60208260ff161080156125d85750838260ff16815181106125c6576125c66132c3565b01602001516001600160f81b03191615155b15611cd757838260ff16815181106125f2576125f26132c3565b602001015160f81c60f81b818360ff1681518110612612576126126132c3565b60200101906001600160f81b03191690815f1a9053508161263281613ba8565b9250506125a3565b505060408051602081019091525f815290565b612656816121be565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060611cd783836040518060600160405280602781526020016140b16027913960605f5f856001600160a01b0316856040516126c89190613854565b5f60405180830381855af49150503d805f8114612700576040519150601f19603f3d011682016040523d82523d5f602084013e612705565b606091505b509150915061271686838387612720565b9695505050505050565b6060831561278e5782515f03612787576001600160a01b0385163b6127875760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161069c565b508161148b565b61148b83838151156127a35781518083602001fd5b8060405162461bcd60e51b815260040161069c919061399b565b6104ca80613bc783390190565b5f5f604083850312156127db575f5ffd5b82356001600160401b038111156127f0575f5ffd5b83016101608186031215612802575f5ffd5b946020939093013593505050565b5f60208284031215612820575f5ffd5b81356001600160e01b031981168114611cd7575f5ffd5b6001600160a01b0381168114610cad575f5ffd5b803561208681612837565b5f60208284031215612866575f5ffd5b8135611cd781612837565b5f60208284031215612881575f5ffd5b81356001600160401b03811115612896575f5ffd5b82016101008185031215611cd7575f5ffd5b5f5b838110156128c25781810151838201526020016128aa565b50505f910152565b5f81518084526128e18160208601602086016128a8565b601f01601f19169290920160200192915050565b6020815261290f6020820183516001600160401b03169052565b5f602083015161292a60408401826001600160401b03169052565b50604083015163ffffffff811660608401525060608301516001600160a01b03811660808401525060808301516001600160401b03811660a08401525060a08301516001600160a01b03811660c08401525060c08301516001600160401b03811660e08401525060e08301516001600160a01b038116610100840152506101008301516001600160a01b038116610120840152506101208301516101408301526101408301516101608084015261148b6101808401826128ca565b634e487b7160e01b5f52604160045260245ffd5b604051608081016001600160401b0381118282101715612a1b57612a1b6129e5565b60405290565b60405161010081016001600160401b0381118282101715612a1b57612a1b6129e5565b60405161016081016001600160401b0381118282101715612a1b57612a1b6129e5565b604051601f8201601f191681016001600160401b0381118282101715612a8f57612a8f6129e5565b604052919050565b5f6001600160401b03821115612aaf57612aaf6129e5565b50601f01601f191660200190565b5f82601f830112612acc575f5ffd5b8135602083015f612ae4612adf84612a97565b612a67565b9050828152858383011115612af7575f5ffd5b828260208301375f92810160200192909252509392505050565b5f5f60408385031215612b22575f5ffd5b8235612b2d81612837565b915060208301356001600160401b03811115612b47575f5ffd5b612b5385828601612abd565b9150509250929050565b5f5f60408385031215612b6e575f5ffd5b823591506020830135612b8081612837565b809150509250929050565b5f5f83601f840112612b9b575f5ffd5b5081356001600160401b03811115612bb1575f5ffd5b602083019150836020828501011115612bc8575f5ffd5b9250929050565b5f5f60208385031215612be0575f5ffd5b82356001600160401b03811115612bf5575f5ffd5b612c0185828601612b8b565b90969095509350505050565b6001600160401b03851681526001600160a01b03841660208201526080604082018190525f90612c3f908301856128ca565b8281036060840152612c5181856128ca565b979650505050505050565b5f5f83601f840112612c6c575f5ffd5b5081356001600160401b03811115612c82575f5ffd5b6020830191508360208260051b8501011115612bc8575f5ffd5b5f5f5f5f5f5f5f5f60a0898b031215612cb3575f5ffd5b8835612cbe81612837565b97506020890135612cce81612837565b965060408901356001600160401b03811115612ce8575f5ffd5b612cf48b828c01612c5c565b90975095505060608901356001600160401b03811115612d12575f5ffd5b612d1e8b828c01612c5c565b90955093505060808901356001600160401b03811115612d3c575f5ffd5b612d488b828c01612b8b565b999c989b5096995094979396929594505050565b5f5f5f5f5f5f60a08789031215612d71575f5ffd5b8635612d7c81612837565b95506020870135612d8c81612837565b9450604087013593506060870135925060808701356001600160401b03811115612db4575f5ffd5b612dc089828a01612b8b565b979a9699509497509295939492505050565b5f5f8335601e19843603018112612de7575f5ffd5b8301803591506001600160401b03821115612e00575f5ffd5b602001915036819003821315612bc8575f5ffd5b5f5f85851115612e22575f5ffd5b83861115612e2e575f5ffd5b5050820193919092039150565b5f60208284031215612e4b575f5ffd5b81356001600160401b03811115612e60575f5ffd5b61148b84828501612abd565b6001600160401b0381168114610cad575f5ffd5b805161208681612e6c565b805161208681612837565b5f82601f830112612ea5575f5ffd5b8151602083015f612eb8612adf84612a97565b9050828152858383011115612ecb575f5ffd5b612ed98360208301846128a8565b95945050505050565b5f6001600160401b03821115612efa57612efa6129e5565b5060051b60200190565b5f82601f830112612f13575f5ffd5b8151612f21612adf82612ee2565b8082825260208201915060208360051b860101925085831115612f42575f5ffd5b602085015b83811015612f5f578051835260209283019201612f47565b5095945050505050565b5f5f5f5f5f60a08688031215612f7d575f5ffd5b85516001600160401b03811115612f92575f5ffd5b860160808189031215612fa3575f5ffd5b612fab6129f9565b8151612fb681612e6c565b81526020820151612fc681612837565b602082015260408201516001600160401b03811115612fe3575f5ffd5b612fef8a828501612e96565b60408301525060608201516001600160401b0381111561300d575f5ffd5b6130198a828501612e96565b606083015250955061302f905060208701612e8b565b935061303d60408701612e8b565b925060608601516001600160401b03811115613057575f5ffd5b61306388828901612f04565b92505060808601516001600160401b0381111561307e575f5ffd5b61308a88828901612f04565b9150509295509295909350565b5f8151808452602084019350602083015f5b828110156130c75781518652602095860195909101906001016130a9565b5093949350505050565b6001600160a01b038581168252841660208201526080604082018190525f906130fc90830185613097565b8281036060840152612c518185613097565b803561208681612e6c565b63ffffffff81168114610cad575f5ffd5b803561208681613119565b5f82601f830112613144575f5ffd5b8135613152612adf82612ee2565b8082825260208201915060208360051b860101925085831115613173575f5ffd5b602085015b83811015612f5f578035835260209283019201613178565b5f61010082360312156131a1575f5ffd5b6131a9612a21565b6131b28361310e565b81526131c06020840161284b565b60208201526131d16040840161284b565b60408201526131e26060840161310e565b60608201526131f36080840161284b565b608082015261320460a0840161312a565b60a082015260c08301356001600160401b03811115613221575f5ffd5b61322d36828601613135565b60c08301525060e08301356001600160401b0381111561324b575f5ffd5b61325736828601613135565b60e08301525092915050565b5f60208284031215613273575f5ffd5b8135611cd781612e6c565b5f5f8335601e19843603018112613293575f5ffd5b8301803591506001600160401b038211156132ac575f5ffd5b6020019150600581901b3603821315612bc8575f5ffd5b634e487b7160e01b5f52603260045260245ffd5b5f602082840312156132e7575f5ffd5b8135611cd781613119565b634e487b7160e01b5f52601160045260245ffd5b81810381811115610600576106006132f2565b805161208681613119565b5f5f60408385031215613335575f5ffd5b825160208401519092506001600160401b03811115613352575f5ffd5b83016101608186031215613364575f5ffd5b61336c612a44565b61337582612e80565b815261338360208301612e80565b602082015261339460408301613319565b60408201526133a560608301612e8b565b60608201526133b660808301612e80565b60808201526133c760a08301612e8b565b60a08201526133d860c08301612e80565b60c08201526133e960e08301612e8b565b60e08201526133fb6101008301612e8b565b61010082015261012082810151908201526101408201516001600160401b03811115613425575f5ffd5b61343187828501612e96565b6101408301525080925050509250929050565b8183525f6001600160fb1b0383111561345b575f5ffd5b8260051b80836020870137939093016020019392505050565b6001600160401b03881681526001600160a01b0387811660208301528616604082015260a0606082018190525f906134af9083018688613444565b82810360808401526134c2818587613444565b9a9950505050505050505050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b5f5f5f5f5f60a0868803121561357c575f5ffd5b85356001600160401b03811115613591575f5ffd5b8601608081890312156135a2575f5ffd5b6135aa6129f9565b81356135b581612e6c565b815260208201356135c581612837565b602082015260408201356001600160401b038111156135e2575f5ffd5b6135ee8a828501612abd565b60408301525060608201356001600160401b0381111561360c575f5ffd5b6136188a828501612abd565b606083015250955061362e90506020870161284b565b935061363c6040870161284b565b925060608601356001600160401b03811115613656575f5ffd5b61366288828901613135565b92505060808601356001600160401b0381111561367d575f5ffd5b61308a88828901613135565b6001600160401b03861681526001600160a01b0385811660208301528416604082015260a0606082018190525f906136c390830185613097565b82810360808401526136d58185613097565b98975050505050505050565b600181811c908216806136f557607f821691505b602082108103610bcb57634e487b7160e01b5f52602260045260245ffd5b5f6060828403128015613724575f5ffd5b50604051606081016001600160401b0381118282101715613747576137476129e5565b60405282518152602083015161375c81612837565b6020820152604083015161376f81612e6c565b60408201529392505050565b6001600160a01b0385811682528416602082015260a0604082018190525f906137a690830185613097565b82810360608401526137b88185613097565b83810360809094019390935250505f8152602001949350505050565b6001600160a01b03841681526060602082018190525f906137f790830185613097565b82810360408401526127168185613097565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f82516138658184602087016128a8565b9190910192915050565b5f6020828403121561387f575f5ffd5b81518015158114611cd7575f5ffd5b6001600160a01b0387811682528616602082015260a0604082018190525f906138ba9083018688613444565b82810360608401526138cd818587613444565b83810360809094019390935250505f81526020019695505050505050565b60a080825288516001600160401b03169082015260208801516001600160a01b031660c08201526040880151608060e08301525f9061392e6101208401826128ca565b905060608a0151609f198483030161010085015261394c82826128ca565b915050613964602084018a6001600160a01b03169052565b6001600160a01b03881660408401528281036060840152613986818789613444565b905082810360808401526134c2818587613444565b602081525f611cd760208301846128ca565b5f602082840312156139bd575f5ffd5b8151611cd781612837565b5f602082840312156139d8575f5ffd5b5051919050565b6001600160a01b038681168252851660208201526001600160401b038416604082015260a0606082018190525f90613a19908301856128ca565b82810360808401526136d581856128ca565b6001600160a01b03831681526040602082018190525f9061148b908301846128ca565b601f821115611d3457805f5260205f20601f840160051c81016020851015613a735750805b601f840160051c820191505b81811015613a92575f8155600101613a7f565b5050505050565b81516001600160401b03811115613ab257613ab26129e5565b613ac681613ac084546136e1565b84613a4e565b6020601f821160018114613af8575f8315613ae15750848201515b5f19600385901b1c1916600184901b178455613a92565b5f84815260208120601f198516915b82811015613b275787850151825560209485019460019092019101613b07565b5084821015613b4457868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b604081525f613b6560408301856128ca565b8281036020840152612ed981856128ca565b5f60208284031215613b87575f5ffd5b81516001600160401b03811115613b9c575f5ffd5b61148b84828501612e96565b5f60ff821660ff8103613bbd57613bbd6132f2565b6001019291505056fe60806040526040516104ca3803806104ca833981016040819052610022916102d2565b61002d82825f610034565b50506103ed565b61003d8361005f565b5f825111806100495750805b1561005a57610058838361009e565b505b505050565b610068816100ca565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606100c383836040518060600160405280602781526020016104a36027913961017d565b9392505050565b6001600160a01b0381163b61013c5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f5f856001600160a01b03168560405161019991906103a0565b5f60405180830381855af49150503d805f81146101d1576040519150601f19603f3d011682016040523d82523d5f602084013e6101d6565b606091505b5090925090506101e8868383876101f2565b9695505050505050565b606083156102605782515f03610259576001600160a01b0385163b6102595760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610133565b508161026a565b61026a8383610272565b949350505050565b8151156102825781518083602001fd5b8060405162461bcd60e51b815260040161013391906103bb565b634e487b7160e01b5f52604160045260245ffd5b5f5b838110156102ca5781810151838201526020016102b2565b50505f910152565b5f5f604083850312156102e3575f5ffd5b82516001600160a01b03811681146102f9575f5ffd5b60208401519092506001600160401b03811115610314575f5ffd5b8301601f81018513610324575f5ffd5b80516001600160401b0381111561033d5761033d61029c565b604051601f8201601f19908116603f011681016001600160401b038111828210171561036b5761036b61029c565b604052818152828201602001871015610382575f5ffd5b6103938260208301602086016102b0565b8093505050509250929050565b5f82516103b18184602087016102b0565b9190910192915050565b602081525f82518060208401526103d98160408501602087016102b0565b601f01601f19169190910160400192915050565b60aa806103f95f395ff3fe608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220194ce3c7ade0e0ed3b707ebafdd37eee7951275a313c7ba2b2698c7d303a3ba464736f6c634300081b0033", + "balance": "0x0" + }, + "0x1670130000000000000000000000000000000004": { + "contractName": "ERC1155Vault", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000c9": "0x0000000000000000000000000000000000000000000000000000000000000101", + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0167013000000000000000000000000000000004" + }, + "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033", + "balance": "0x0" + }, + "0x0167013000000000000000000000000000010096": { + "contractName": "BridgedERC20", + "storage": {}, + "code": "0x60806040526004361061021d575f3560e01c80635c975abb1161011e5780638da5cb5b116100a8578063b8f2e0c51161006d578063b8f2e0c514610653578063dd62ed3e14610672578063e30c397814610691578063f12506c1146106ae578063f2fde38b146106c2575f5ffd5b80638da5cb5b146105c357806395d89b41146105e0578063a457c2d7146105f4578063a77f151614610613578063a9059cbb14610634575f5ffd5b806379ba5097116100ee57806379ba5097146105475780637cf8ed0d1461055b5780637e4746341461057b5780638456cb591461059b5780638abf6077146105af575f5ffd5b80635c975abb146104c05780636c0db62b146104e057806370a08231146104ff578063715018a614610533575f5ffd5b80633075db56116101aa57806340c10f191161016f57806340c10f191461044557806342966c681461046457806349d12605146104835780634f1ef2861461049957806352d1902d146104ac575f5ffd5b80633075db56146103b0578063313ce567146103c45780633659cfe6146103f157806339509351146104125780633f4ba83a14610431575f5ffd5b8063095ea7b3116101f0578063095ea7b3146102ef5780630ae745481461030e57806318160ddd1461032f57806323b872dd1461034d57806326afaadd1461036c575f5ffd5b806301ffc9a71461022157806304f3bcec1461025557806306fdde031461029b578063090f9221146102bc575b5f5ffd5b34801561022c575f5ffd5b5061024061023b366004611f59565b6106e1565b60405190151581526020015b60405180910390f35b348015610260575f5ffd5b507f00000000000000000000000000000000000000000000000000000000000000005b6040516001600160a01b03909116815260200161024c565b3480156102a6575f5ffd5b506102af610783565b60405161024c9190611fa2565b3480156102c7575f5ffd5b506102837f000000000000000000000000167013000000000000000000000000000000000281565b3480156102fa575f5ffd5b50610240610309366004611fef565b610813565b348015610319575f5ffd5b5061012f5461024090600160a01b900460ff1681565b34801561033a575f5ffd5b5060fd545b60405190815260200161024c565b348015610358575f5ffd5b50610240610367366004612017565b61082a565b348015610377575f5ffd5b5061039161012d5461012e546001600160a01b0390911691565b604080516001600160a01b03909316835260208301919091520161024c565b3480156103bb575f5ffd5b5061024061084d565b3480156103cf575f5ffd5b5061012d54600160a01b900460ff165b60405160ff909116815260200161024c565b3480156103fc575f5ffd5b5061041061040b366004612051565b610865565b005b34801561041d575f5ffd5b5061024061042c366004611fef565b610935565b34801561043c575f5ffd5b50610410610956565b348015610450575f5ffd5b5061041061045f366004611fef565b6109da565b34801561046f575f5ffd5b5061041061047e36600461206a565b610af4565b34801561048e575f5ffd5b5061033f61012e5481565b6104106104a7366004612095565b610c31565b3480156104b7575f5ffd5b5061033f610ce6565b3480156104cb575f5ffd5b5061024060c954610100900460ff1660021490565b3480156104eb575f5ffd5b506104106104fa36600461219e565b610d97565b34801561050a575f5ffd5b5061033f610519366004612051565b6001600160a01b03165f90815260fb602052604090205490565b34801561053e575f5ffd5b50610410610f55565b348015610552575f5ffd5b50610410610f66565b348015610566575f5ffd5b5061012d54610283906001600160a01b031681565b348015610586575f5ffd5b5061012f54610283906001600160a01b031681565b3480156105a6575f5ffd5b50610410610fdd565b3480156105ba575f5ffd5b5061028361105c565b3480156105ce575f5ffd5b506033546001600160a01b0316610283565b3480156105eb575f5ffd5b506102af61106a565b3480156105ff575f5ffd5b5061024061060e366004611fef565b611079565b34801561061e575f5ffd5b5061012d546103df90600160a01b900460ff1681565b34801561063f575f5ffd5b5061024061064e366004611fef565b6110f3565b34801561065e575f5ffd5b5061041061066d36600461224e565b611100565b34801561067d575f5ffd5b5061033f61068c366004612287565b61127c565b34801561069c575f5ffd5b506065546001600160a01b0316610283565b3480156106b9575f5ffd5b506102406112a6565b3480156106cd575f5ffd5b506104106106dc366004612051565b6112d1565b5f6001600160e01b0319821663093e326b60e21b148061071157506001600160e01b03198216636c0db62b60e01b145b8061072c57506001600160e01b0319821663b8f2e0c560e01b145b8061074757506001600160e01b031982166336372b0760e01b145b8061076257506001600160e01b0319821663a219a02560e01b145b8061077d57506001600160e01b031982166301ffc9a760e01b145b92915050565b606060fe8054610792906122b8565b80601f01602080910402602001604051908101604052809291908181526020018280546107be906122b8565b80156108095780601f106107e057610100808354040283529160200191610809565b820191905f5260205f20905b8154815290600101906020018083116107ec57829003601f168201915b5050505050905090565b5f33610820818585611342565b5060019392505050565b5f33610837858285611465565b6108428585856114dd565b506001949350505050565b5f600261085c60c95460ff1690565b60ff1614905090565b6001600160a01b037f00000000000000000000000001670130000000000000000000000000000100961630036108b65760405162461bcd60e51b81526004016108ad906122f0565b60405180910390fd5b7f00000000000000000000000001670130000000000000000000000000000100966001600160a01b03166108e8611691565b6001600160a01b03161461090e5760405162461bcd60e51b81526004016108ad9061233c565b610917816116ac565b604080515f80825260208201909252610932918391906116b4565b50565b5f33610820818585610947838361127c565b6109519190612388565b611342565b61096a60c954610100900460ff1660021490565b6109875760405163bae6e2a960e01b815260040160405180910390fd5b61099b60c9805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a16109d8335f61181e565b565b6109ee60c954610100900460ff1660021490565b15610a0c5760405163bae6e2a960e01b815260040160405180910390fd5b6002610a1a60c95460ff1690565b60ff1603610a3b5760405163dfc60d8560e01b815260040160405180910390fd5b610a456002611826565b610a4d6112a6565b15610a6b5760405163270bf77560e01b815260040160405180910390fd5b61012f546001600160a01b031633819003610ad257826001600160a01b0316816001600160a01b03167fe502aa3e015149f4b76a0b2b5394e3100903c4af27c3ddc98385395d3f55252684604051610ac591815260200190565b60405180910390a3610adb565b610adb3361183c565b610ae583836118b3565b50610af06001611826565b5050565b610b0860c954610100900460ff1660021490565b15610b265760405163bae6e2a960e01b815260040160405180910390fd5b6002610b3460c95460ff1690565b60ff1603610b555760405163dfc60d8560e01b815260040160405180910390fd5b610b5f6002611826565b610b676112a6565b15610c145761012f546040518281526001600160a01b0390911690339082907f638edf84937fb2534b47cac985ea84d6ea4f4076315b56ea1c784d26b87e2bcb9060200160405180910390a36040516340c10f1960e01b8152336004820152602481018390526001600160a01b038216906340c10f19906044015f604051808303815f87803b158015610bf8575f5ffd5b505af1158015610c0a573d5f5f3e3d5ffd5b5050505050610c1d565b610c1d3361183c565b610c27338261197d565b6109326001611826565b6001600160a01b037f0000000000000000000000000167013000000000000000000000000000010096163003610c795760405162461bcd60e51b81526004016108ad906122f0565b7f00000000000000000000000001670130000000000000000000000000000100966001600160a01b0316610cab611691565b6001600160a01b031614610cd15760405162461bcd60e51b81526004016108ad9061233c565b610cda826116ac565b610af0828260016116b4565b5f306001600160a01b037f00000000000000000000000001670130000000000000000000000000000100961614610d855760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c000000000000000060648201526084016108ad565b505f51602061252b5f395f51905f5290565b5f54610100900460ff1615808015610db557505f54600160ff909116105b80610dce5750303b158015610dce57505f5460ff166001145b610e315760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016108ad565b5f805460ff191660011790558015610e52575f805461ff0019166101001790555b610e5c8888611aba565b610e6589611af6565b610ed683838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525050604080516020601f8b0181900481028201810190925289815292508991508890819084018382808284375f92019190915250611b5492505050565b61012d805461012e89905560ff8816600160a01b026001600160a81b03199091166001600160a01b038b16171790558015610f4a575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050505050565b610f5d611b84565b6109d85f611bde565b60655433906001600160a01b03168114610fd45760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016108ad565b61093281611bde565b610ff160c954610100900460ff1660021490565b1561100f5760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a16109d833600161181e565b5f611065611691565b905090565b606060ff8054610792906122b8565b5f3381611086828661127c565b9050838110156110e65760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084016108ad565b6108428286868403611342565b5f336108208185856114dd565b61111460c954610100900460ff1660021490565b156111325760405163bae6e2a960e01b815260040160405180910390fd5b7f0000000000000000000000001670130000000000000000000000000000000002336001600160a01b0382161461117c576040516395383ea160e01b815260040160405180910390fd5b600261118a60c95460ff1690565b60ff16036111ab5760405163dfc60d8560e01b815260040160405180910390fd5b6111b56002611826565b61012f546001600160a01b0384811691161480156111e6575061012f60149054906101000a900460ff161515821515145b156112045760405163c118d2f360e01b815260040160405180910390fd5b61012f80546001600160a01b0385166001600160a81b03199091168117600160a01b851515908102919091179092556040805191825260208201929092527fa6b6f959792843a48d9d03d13595f2de7c86ae0ce12ef0fa759dd911b205e565910160405180910390a16112776001611826565b505050565b6001600160a01b039182165f90815260fc6020908152604080832093909416825291909152205490565b61012f545f906001600160a01b03161580159061106557505061012f54600160a01b900460ff161590565b6112d9611b84565b606580546001600160a01b0383166001600160a01b0319909116811790915561130a6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6001600160a01b0383166113a45760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016108ad565b6001600160a01b0382166114055760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016108ad565b6001600160a01b038381165f81815260fc602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b5f611470848461127c565b90505f1981146114d757818110156114ca5760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016108ad565b6114d78484848403611342565b50505050565b6001600160a01b0383166115415760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016108ad565b6001600160a01b0382166115a35760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016108ad565b6115ae838383611bf7565b6001600160a01b0383165f90815260fb6020526040902054818110156116255760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b60648201526084016108ad565b6001600160a01b038085165f81815260fb602052604080822086860390559286168082529083902080548601905591517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906116849086815260200190565b60405180910390a36114d7565b5f51602061252b5f395f51905f52546001600160a01b031690565b610932611b84565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156116e75761127783611c32565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611741575060408051601f3d908101601f1916820190925261173e918101906123a7565b60015b6117a45760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b60648201526084016108ad565b5f51602061252b5f395f51905f5281146118125760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b60648201526084016108ad565b50611277838383611ccd565b610af0611b84565b60c9805460ff191660ff92909216919091179055565b7f000000000000000000000000167013000000000000000000000000000000000261186f6033546001600160a01b031690565b6001600160a01b0316336001600160a01b031614806118965750336001600160a01b038216145b610af0576040516395383ea160e01b815260040160405180910390fd5b6001600160a01b0382166119095760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016108ad565b6119145f8383611bf7565b8060fd5f8282546119259190612388565b90915550506001600160a01b0382165f81815260fb60209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b6001600160a01b0382166119dd5760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b60648201526084016108ad565b6119e8825f83611bf7565b6001600160a01b0382165f90815260fb602052604090205481811015611a5b5760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b60648201526084016108ad565b6001600160a01b0383165f81815260fb60209081526040808320868603905560fd80548790039055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3505050565b6001600160a01b0382161580611ace575080155b80611ad857504681145b15610af05760405163c118d2f360e01b815260040160405180910390fd5b5f54610100900460ff16611b1c5760405162461bcd60e51b81526004016108ad906123be565b611b24611cf1565b611b426001600160a01b03821615611b3c5781611bde565b33611bde565b5060c9805461ff001916610100179055565b5f54610100900460ff16611b7a5760405162461bcd60e51b81526004016108ad906123be565b610af08282611d17565b6033546001600160a01b031633146109d85760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016108ad565b606580546001600160a01b031916905561093281611d56565b611c0b60c954610100900460ff1660021490565b15611c295760405163bae6e2a960e01b815260040160405180910390fd5b61127782611da7565b6001600160a01b0381163b611c9f5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016108ad565b5f51602061252b5f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b611cd683611dd0565b5f82511180611ce25750805b15611277576114d78383611e0f565b5f54610100900460ff166109d85760405162461bcd60e51b81526004016108ad906123be565b5f54610100900460ff16611d3d5760405162461bcd60e51b81526004016108ad906123be565b60fe611d498382612454565b5060ff6112778282612454565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b306001600160a01b0382160361093257604051630c292c9d60e21b815260040160405180910390fd5b611dd981611c32565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060611e34838360405180606001604052806027815260200161254b60279139611e3b565b9392505050565b60605f5f856001600160a01b031685604051611e57919061250f565b5f60405180830381855af49150503d805f8114611e8f576040519150601f19603f3d011682016040523d82523d5f602084013e611e94565b606091505b5091509150611ea586838387611eaf565b9695505050505050565b60608315611f1d5782515f03611f16576001600160a01b0385163b611f165760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016108ad565b5081611f27565b611f278383611f2f565b949350505050565b815115611f3f5781518083602001fd5b8060405162461bcd60e51b81526004016108ad9190611fa2565b5f60208284031215611f69575f5ffd5b81356001600160e01b031981168114611e34575f5ffd5b5f5b83811015611f9a578181015183820152602001611f82565b50505f910152565b602081525f8251806020840152611fc0816040850160208701611f80565b601f01601f19169190910160400192915050565b80356001600160a01b0381168114611fea575f5ffd5b919050565b5f5f60408385031215612000575f5ffd5b61200983611fd4565b946020939093013593505050565b5f5f5f60608486031215612029575f5ffd5b61203284611fd4565b925061204060208501611fd4565b929592945050506040919091013590565b5f60208284031215612061575f5ffd5b611e3482611fd4565b5f6020828403121561207a575f5ffd5b5035919050565b634e487b7160e01b5f52604160045260245ffd5b5f5f604083850312156120a6575f5ffd5b6120af83611fd4565b9150602083013567ffffffffffffffff8111156120ca575f5ffd5b8301601f810185136120da575f5ffd5b803567ffffffffffffffff8111156120f4576120f4612081565b604051601f8201601f19908116603f0116810167ffffffffffffffff8111828210171561212357612123612081565b60405281815282820160200187101561213a575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f5f83601f840112612169575f5ffd5b50813567ffffffffffffffff811115612180575f5ffd5b602083019150836020828501011115612197575f5ffd5b9250929050565b5f5f5f5f5f5f5f5f60c0898b0312156121b5575f5ffd5b6121be89611fd4565b97506121cc60208a01611fd4565b965060408901359550606089013560ff811681146121e8575f5ffd5b9450608089013567ffffffffffffffff811115612203575f5ffd5b61220f8b828c01612159565b90955093505060a089013567ffffffffffffffff81111561222e575f5ffd5b61223a8b828c01612159565b999c989b5096995094979396929594505050565b5f5f6040838503121561225f575f5ffd5b61226883611fd4565b91506020830135801515811461227c575f5ffd5b809150509250929050565b5f5f60408385031215612298575f5ffd5b6122a183611fd4565b91506122af60208401611fd4565b90509250929050565b600181811c908216806122cc57607f821691505b6020821081036122ea57634e487b7160e01b5f52602260045260245ffd5b50919050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b8082018082111561077d57634e487b7160e01b5f52601160045260245ffd5b5f602082840312156123b7575f5ffd5b5051919050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b601f82111561127757805f5260205f20601f840160051c8101602085101561242e5750805b601f840160051c820191505b8181101561244d575f815560010161243a565b5050505050565b815167ffffffffffffffff81111561246e5761246e612081565b6124828161247c84546122b8565b84612409565b6020601f8211600181146124b4575f831561249d5750848201515b5f19600385901b1c1916600184901b17845561244d565b5f84815260208120601f198516915b828110156124e357878501518255602094850194600190920191016124c3565b508482101561250057868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b5f8251612520818460208701611f80565b919091019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220a04306bd53ace894e5872507685df0dfe84ec6aff55628acb0fd6887e55b914f64736f6c634300081b0033", + "balance": "0x0" + }, + "0x0167013000000000000000000000000000010097": { + "contractName": "BridgedERC721", + "storage": {}, + "code": "0x6080604052600436106101f1575f3560e01c80636352211e1161010857806395d89b411161009d578063c87b56dd1161006d578063c87b56dd14610592578063d1399b1a146105b1578063e30c3978146105d0578063e985e9c5146105ed578063f2fde38b1461060c575f5ffd5b806395d89b411461050d578063a22cb46514610521578063b88d4fde14610540578063c0bb46d61461055f575f5ffd5b80637cf8ed0d116100d85780637cf8ed0d146104a85780638456cb59146104c85780638abf6077146104dc5780638da5cb5b146104f0575f5ffd5b80636352211e1461044257806370a0823114610461578063715018a61461048057806379ba509714610494575f5ffd5b80633659cfe61161018957806342966c681161015957806342966c68146103b857806349d12605146103d75780634f1ef286146103fb57806352d1902d1461040e5780635c975abb14610422575f5ffd5b80633659cfe6146103475780633f4ba83a1461036657806340c10f191461037a57806342842e0e14610399575f5ffd5b8063095ea7b3116101c4578063095ea7b3146102af57806323b872dd146102d057806326afaadd146102ef5780633075db5614610333575f5ffd5b806301ffc9a7146101f557806304f3bcec1461022957806306fdde031461026f578063081812fc14610290575b5f5ffd5b348015610200575f5ffd5b5061021461020f3660046122ef565b61062b565b60405190151581526020015b60405180910390f35b348015610234575f5ffd5b507f00000000000000000000000000000000000000000000000000000000000000005b6040516001600160a01b039091168152602001610220565b34801561027a575f5ffd5b50610283610670565b6040516102209190612357565b34801561029b575f5ffd5b506102576102aa366004612369565b610701565b3480156102ba575f5ffd5b506102ce6102c936600461239b565b610727565b005b3480156102db575f5ffd5b506102ce6102ea3660046123c3565b610840565b3480156102fa575f5ffd5b5061031461015f54610160546001600160a01b0390911691565b604080516001600160a01b039093168352602083019190915201610220565b34801561033e575f5ffd5b50610214610871565b348015610352575f5ffd5b506102ce6103613660046123fd565b610889565b348015610371575f5ffd5b506102ce610950565b348015610385575f5ffd5b506102ce61039436600461239b565b6109d4565b3480156103a4575f5ffd5b506102ce6103b33660046123c3565b610a9d565b3480156103c3575f5ffd5b506102ce6103d2366004612369565b610ab7565b3480156103e2575f5ffd5b506103ed6101605481565b604051908152602001610220565b6102ce6104093660046124b5565b610bb4565b348015610419575f5ffd5b506103ed610c69565b34801561042d575f5ffd5b5061021460c954610100900460ff1660021490565b34801561044d575f5ffd5b5061025761045c366004612369565b610d1a565b34801561046c575f5ffd5b506103ed61047b3660046123fd565b610d7a565b34801561048b575f5ffd5b506102ce610dff565b34801561049f575f5ffd5b506102ce610e10565b3480156104b3575f5ffd5b5061015f54610257906001600160a01b031681565b3480156104d3575f5ffd5b506102ce610e87565b3480156104e7575f5ffd5b50610257610f06565b3480156104fb575f5ffd5b506033546001600160a01b0316610257565b348015610518575f5ffd5b50610283610f14565b34801561052c575f5ffd5b506102ce61053b366004612500565b610f24565b34801561054b575f5ffd5b506102ce61055a366004612539565b610f2f565b34801561056a575f5ffd5b506102577f000000000000000000000000167013000000000000000000000000000000000381565b34801561059d575f5ffd5b506102836105ac366004612369565b610f67565b3480156105bc575f5ffd5b506102ce6105cb3660046125e2565b610f90565b3480156105db575f5ffd5b506065546001600160a01b0316610257565b3480156105f8575f5ffd5b5061021461060736600461267b565b611140565b348015610617575f5ffd5b506102ce6106263660046123fd565b61116e565b5f6001600160e01b0319821663093e326b60e21b148061065b57506001600160e01b0319821663689ccd8d60e11b145b8061066a575061066a826111df565b92915050565b606061012d8054610680906126ac565b80601f01602080910402602001604051908101604052809291908181526020018280546106ac906126ac565b80156106f75780601f106106ce576101008083540402835291602001916106f7565b820191905f5260205f20905b8154815290600101906020018083116106da57829003601f168201915b5050505050905090565b5f61070b8261122e565b505f90815261013160205260409020546001600160a01b031690565b5f61073182610d1a565b9050806001600160a01b0316836001600160a01b0316036107a35760405162461bcd60e51b815260206004820152602160248201527f4552433732313a20617070726f76616c20746f2063757272656e74206f776e656044820152603960f91b60648201526084015b60405180910390fd5b336001600160a01b03821614806107bf57506107bf8133611140565b6108315760405162461bcd60e51b815260206004820152603d60248201527f4552433732313a20617070726f76652063616c6c6572206973206e6f7420746f60448201527f6b656e206f776e6572206f7220617070726f76656420666f7220616c6c000000606482015260840161079a565b61083b838361128d565b505050565b61084a33826112fb565b6108665760405162461bcd60e51b815260040161079a906126e4565b61083b838383611359565b5f600261088060c95460ff1690565b60ff1614905090565b6001600160a01b037f00000000000000000000000001670130000000000000000000000000000100971630036108d15760405162461bcd60e51b815260040161079a90612731565b7f00000000000000000000000001670130000000000000000000000000000100976001600160a01b03166109036114cb565b6001600160a01b0316146109295760405162461bcd60e51b815260040161079a9061277d565b610932816114e6565b604080515f8082526020820190925261094d918391906114ee565b50565b61096460c954610100900460ff1660021490565b6109815760405163bae6e2a960e01b815260040160405180910390fd5b61099560c9805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a16109d2335f611658565b565b6109e860c954610100900460ff1660021490565b15610a065760405163bae6e2a960e01b815260040160405180910390fd5b7f0000000000000000000000001670130000000000000000000000000000000003336001600160a01b03821614610a50576040516395383ea160e01b815260040160405180910390fd5b6002610a5e60c95460ff1690565b60ff1603610a7f5760405163dfc60d8560e01b815260040160405180910390fd5b610a896002611660565b610a938383611676565b61083b6001611660565b61083b83838360405180602001604052805f815250610f2f565b610acb60c954610100900460ff1660021490565b15610ae95760405163bae6e2a960e01b815260040160405180910390fd5b7f0000000000000000000000001670130000000000000000000000000000000003336001600160a01b03821614610b33576040516395383ea160e01b815260040160405180910390fd5b6002610b4160c95460ff1690565b60ff1603610b625760405163dfc60d8560e01b815260040160405180910390fd5b610b6c6002611660565b33610b7683610d1a565b6001600160a01b031614610b9d5760405163358bf3d960e01b815260040160405180910390fd5b610ba68261168f565b610bb06001611660565b5050565b6001600160a01b037f0000000000000000000000000167013000000000000000000000000000010097163003610bfc5760405162461bcd60e51b815260040161079a90612731565b7f00000000000000000000000001670130000000000000000000000000000100976001600160a01b0316610c2e6114cb565b6001600160a01b031614610c545760405162461bcd60e51b815260040161079a9061277d565b610c5d826114e6565b610bb0828260016114ee565b5f306001600160a01b037f00000000000000000000000001670130000000000000000000000000000100971614610d085760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c0000000000000000606482015260840161079a565b505f516020612b205f395f51905f5290565b5f81815261012f60205260408120546001600160a01b03168061066a5760405162461bcd60e51b8152602060048201526018602482015277115490cdcc8c4e881a5b9d985b1a59081d1bdad95b88125160421b604482015260640161079a565b5f6001600160a01b038216610de35760405162461bcd60e51b815260206004820152602960248201527f4552433732313a2061646472657373207a65726f206973206e6f7420612076616044820152683634b21037bbb732b960b91b606482015260840161079a565b506001600160a01b03165f908152610130602052604090205490565b610e07611731565b6109d25f61178b565b60655433906001600160a01b03168114610e7e5760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b606482015260840161079a565b61094d8161178b565b610e9b60c954610100900460ff1660021490565b15610eb95760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a16109d2336001611658565b5f610f0f6114cb565b905090565b606061012e8054610680906126ac565b610bb03383836117a4565b610f3933836112fb565b610f555760405162461bcd60e51b815260040161079a906126e4565b610f6184848484611872565b50505050565b61015f546101605460609161066a916001600160a01b0390911690610f8b856118a5565b611935565b5f54610100900460ff1615808015610fae57505f54600160ff909116105b80610fc75750303b158015610fc757505f5460ff166001145b61102a5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161079a565b5f805460ff19166001179055801561104b575f805461ff0019166101001790555b611055878761197f565b61105e886119bb565b6110cf83838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525050604080516020601f8b0181900481028201810190925289815292508991508890819084018382808284375f92019190915250611a1992505050565b61015f80546001600160a01b0319166001600160a01b0389161790556101608690558015611136575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b6001600160a01b039182165f9081526101326020908152604080832093909416825291909152205460ff1690565b611176611731565b606580546001600160a01b0383166001600160a01b031990911681179091556111a76033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b5f6001600160e01b031982166380ac58cd60e01b148061120f57506001600160e01b03198216635b5e139f60e01b145b8061066a57506301ffc9a760e01b6001600160e01b031983161461066a565b5f81815261012f60205260409020546001600160a01b031661094d5760405162461bcd60e51b8152602060048201526018602482015277115490cdcc8c4e881a5b9d985b1a59081d1bdad95b88125160421b604482015260640161079a565b5f8181526101316020526040902080546001600160a01b0319166001600160a01b03841690811790915581906112c282610d1a565b6001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b5f5f61130683610d1a565b9050806001600160a01b0316846001600160a01b0316148061132d575061132d8185611140565b806113515750836001600160a01b031661134684610701565b6001600160a01b0316145b949350505050565b826001600160a01b031661136c82610d1a565b6001600160a01b0316146113925760405162461bcd60e51b815260040161079a906127c9565b6001600160a01b0382166113f45760405162461bcd60e51b8152602060048201526024808201527f4552433732313a207472616e7366657220746f20746865207a65726f206164646044820152637265737360e01b606482015260840161079a565b6114018383836001611a49565b826001600160a01b031661141482610d1a565b6001600160a01b03161461143a5760405162461bcd60e51b815260040161079a906127c9565b5f8181526101316020908152604080832080546001600160a01b03199081169091556001600160a01b03878116808652610130855283862080545f190190559087168086528386208054600101905586865261012f90945282852080549092168417909155905184937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4505050565b5f516020612b205f395f51905f52546001600160a01b031690565b61094d611731565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156115215761083b83611a89565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa92505050801561157b575060408051601f3d908101601f191682019092526115789181019061280e565b60015b6115de5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b606482015260840161079a565b5f516020612b205f395f51905f52811461164c5760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b606482015260840161079a565b5061083b838383611b24565b610bb0611731565b60c9805460ff191660ff92909216919091179055565b610bb0828260405180602001604052805f815250611b48565b5f61169982610d1a565b90506116a8815f846001611a49565b6116b182610d1a565b5f8381526101316020908152604080832080546001600160a01b03199081169091556001600160a01b038516808552610130845282852080545f1901905587855261012f909352818420805490911690555192935084927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908390a45050565b6033546001600160a01b031633146109d25760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161079a565b606580546001600160a01b031916905561094d81611b7a565b816001600160a01b0316836001600160a01b0316036118055760405162461bcd60e51b815260206004820152601960248201527f4552433732313a20617070726f766520746f2063616c6c657200000000000000604482015260640161079a565b6001600160a01b038381165f8181526101326020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b61187d848484611359565b61188984848484611bcb565b610f615760405162461bcd60e51b815260040161079a90612825565b60605f6118b183611cc8565b60010190505f8167ffffffffffffffff8111156118d0576118d0612416565b6040519080825280601f01601f1916602001820160405280156118fa576020820181803683370190505b5090508181016020015b5f19016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a850494508461190457509392505050565b606061194b846001600160a01b03166014611d9f565b611954846118a5565b8360405160200161196793929190612877565b60405160208183030381529060405290509392505050565b6001600160a01b0382161580611993575080155b8061199d57504681145b15610bb05760405163c118d2f360e01b815260040160405180910390fd5b5f54610100900460ff166119e15760405162461bcd60e51b815260040161079a906128ff565b6119e9611f3c565b611a076001600160a01b03821615611a01578161178b565b3361178b565b5060c9805461ff001916610100179055565b5f54610100900460ff16611a3f5760405162461bcd60e51b815260040161079a906128ff565b610bb08282611f62565b611a5d60c954610100900460ff1660021490565b15611a7b5760405163bae6e2a960e01b815260040160405180910390fd5b611a8483611fa3565b610f61565b6001600160a01b0381163b611af65760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161079a565b5f516020612b205f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b611b2d83611fcc565b5f82511180611b395750805b1561083b57610f61838361200b565b611b528383612030565b611b5e5f848484611bcb565b61083b5760405162461bcd60e51b815260040161079a90612825565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f6001600160a01b0384163b15611cbd57604051630a85bd0160e11b81526001600160a01b0385169063150b7a0290611c0e90339089908890889060040161294a565b6020604051808303815f875af1925050508015611c48575060408051601f3d908101601f19168201909252611c459181019061297c565b60015b611ca3573d808015611c75576040519150601f19603f3d011682016040523d82523d5f602084013e611c7a565b606091505b5080515f03611c9b5760405162461bcd60e51b815260040161079a90612825565b805181602001fd5b6001600160e01b031916630a85bd0160e11b149050611351565b506001949350505050565b5f8072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b8310611d065772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef81000000008310611d32576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc100008310611d5057662386f26fc10000830492506010015b6305f5e1008310611d68576305f5e100830492506008015b6127108310611d7c57612710830492506004015b60648310611d8e576064830492506002015b600a831061066a5760010192915050565b60605f611dad8360026129ab565b611db89060026129c2565b67ffffffffffffffff811115611dd057611dd0612416565b6040519080825280601f01601f191660200182016040528015611dfa576020820181803683370190505b509050600360fc1b815f81518110611e1457611e146129d5565b60200101906001600160f81b03191690815f1a905350600f60fb1b81600181518110611e4257611e426129d5565b60200101906001600160f81b03191690815f1a9053505f611e648460026129ab565b611e6f9060016129c2565b90505b6001811115611ee6576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110611ea357611ea36129d5565b1a60f81b828281518110611eb957611eb96129d5565b60200101906001600160f81b03191690815f1a90535060049490941c93611edf816129e9565b9050611e72565b508315611f355760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e74604482015260640161079a565b9392505050565b5f54610100900460ff166109d25760405162461bcd60e51b815260040161079a906128ff565b5f54610100900460ff16611f885760405162461bcd60e51b815260040161079a906128ff565b61012d611f958382612a49565b5061012e61083b8282612a49565b306001600160a01b0382160361094d57604051630c292c9d60e21b815260040160405180910390fd5b611fd581611a89565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060611f358383604051806060016040528060278152602001612b40602791396121c9565b6001600160a01b0382166120865760405162461bcd60e51b815260206004820181905260248201527f4552433732313a206d696e7420746f20746865207a65726f2061646472657373604482015260640161079a565b5f81815261012f60205260409020546001600160a01b0316156120eb5760405162461bcd60e51b815260206004820152601c60248201527f4552433732313a20746f6b656e20616c7265616479206d696e74656400000000604482015260640161079a565b6120f85f83836001611a49565b5f81815261012f60205260409020546001600160a01b03161561215d5760405162461bcd60e51b815260206004820152601c60248201527f4552433732313a20746f6b656e20616c7265616479206d696e74656400000000604482015260640161079a565b6001600160a01b0382165f818152610130602090815260408083208054600101905584835261012f90915280822080546001600160a01b0319168417905551839291907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a45050565b60605f5f856001600160a01b0316856040516121e59190612b04565b5f60405180830381855af49150503d805f811461221d576040519150601f19603f3d011682016040523d82523d5f602084013e612222565b606091505b50915091506122338683838761223d565b9695505050505050565b606083156122ab5782515f036122a4576001600160a01b0385163b6122a45760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161079a565b5081611351565b61135183838151156122c05781518083602001fd5b8060405162461bcd60e51b815260040161079a9190612357565b6001600160e01b03198116811461094d575f5ffd5b5f602082840312156122ff575f5ffd5b8135611f35816122da565b5f5b8381101561232457818101518382015260200161230c565b50505f910152565b5f815180845261234381602086016020860161230a565b601f01601f19169290920160200192915050565b602081525f611f35602083018461232c565b5f60208284031215612379575f5ffd5b5035919050565b80356001600160a01b0381168114612396575f5ffd5b919050565b5f5f604083850312156123ac575f5ffd5b6123b583612380565b946020939093013593505050565b5f5f5f606084860312156123d5575f5ffd5b6123de84612380565b92506123ec60208501612380565b929592945050506040919091013590565b5f6020828403121561240d575f5ffd5b611f3582612380565b634e487b7160e01b5f52604160045260245ffd5b5f82601f830112612439575f5ffd5b813567ffffffffffffffff81111561245357612453612416565b604051601f8201601f19908116603f0116810167ffffffffffffffff8111828210171561248257612482612416565b604052818152838201602001851015612499575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f5f604083850312156124c6575f5ffd5b6124cf83612380565b9150602083013567ffffffffffffffff8111156124ea575f5ffd5b6124f68582860161242a565b9150509250929050565b5f5f60408385031215612511575f5ffd5b61251a83612380565b91506020830135801515811461252e575f5ffd5b809150509250929050565b5f5f5f5f6080858703121561254c575f5ffd5b61255585612380565b935061256360208601612380565b925060408501359150606085013567ffffffffffffffff811115612585575f5ffd5b6125918782880161242a565b91505092959194509250565b5f5f83601f8401126125ad575f5ffd5b50813567ffffffffffffffff8111156125c4575f5ffd5b6020830191508360208285010111156125db575f5ffd5b9250929050565b5f5f5f5f5f5f5f60a0888a0312156125f8575f5ffd5b61260188612380565b965061260f60208901612380565b955060408801359450606088013567ffffffffffffffff811115612631575f5ffd5b61263d8a828b0161259d565b909550935050608088013567ffffffffffffffff81111561265c575f5ffd5b6126688a828b0161259d565b989b979a50959850939692959293505050565b5f5f6040838503121561268c575f5ffd5b61269583612380565b91506126a360208401612380565b90509250929050565b600181811c908216806126c057607f821691505b6020821081036126de57634e487b7160e01b5f52602260045260245ffd5b50919050565b6020808252602d908201527f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560408201526c1c881bdc88185c1c1c9bdd9959609a1b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b60208082526025908201527f4552433732313a207472616e736665722066726f6d20696e636f72726563742060408201526437bbb732b960d91b606082015260800190565b5f6020828403121561281e575f5ffd5b5051919050565b60208082526032908201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560408201527131b2b4bb32b91034b6b83632b6b2b73a32b960711b606082015260800190565b6832ba3432b932bab69d60b91b81525f845161289a81600985016020890161230a565b600160fe1b60099184019182015284516128bb81600a84016020890161230a565b600981830101915050712f746f6b656e5552493f75696e743235363d60701b600182015283516128f281601384016020880161230a565b0160130195945050505050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6001600160a01b03858116825284166020820152604081018390526080606082018190525f906122339083018461232c565b5f6020828403121561298c575f5ffd5b8151611f35816122da565b634e487b7160e01b5f52601160045260245ffd5b808202811582820484141761066a5761066a612997565b8082018082111561066a5761066a612997565b634e487b7160e01b5f52603260045260245ffd5b5f816129f7576129f7612997565b505f190190565b601f82111561083b57805f5260205f20601f840160051c81016020851015612a235750805b601f840160051c820191505b81811015612a42575f8155600101612a2f565b5050505050565b815167ffffffffffffffff811115612a6357612a63612416565b612a7781612a7184546126ac565b846129fe565b6020601f821160018114612aa9575f8315612a925750848201515b5f19600385901b1c1916600184901b178455612a42565b5f84815260208120601f198516915b82811015612ad85787850151825560209485019460019092019101612ab8565b5084821015612af557868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b5f8251612b1581846020870161230a565b919091019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122000b2e3058ad8abcda15ec5bbfc95f023fd7f50fc07ddde9512fcd0523c94567064736f6c634300081b0033", + "balance": "0x0" + }, + "0x0167013000000000000000000000000000010098": { + "contractName": "BridgedERC1155", + "storage": {}, + "code": "0x6080604052600436106101cf575f3560e01c8063715018a6116100fd578063a22cb46511610092578063e30c397811610062578063e30c39781461053f578063e985e9c51461055c578063f242432a146105a4578063f2fde38b146105c3575f5ffd5b8063a22cb465146104c3578063b390c0ab146104e2578063d1399b1a14610501578063d81d0a1514610520575f5ffd5b80638456cb59116100cd5780638456cb591461046a5780638abf60771461047e5780638da5cb5b1461049257806395d89b41146104af575f5ffd5b8063715018a6146103ef57806379275a771461040357806379ba5097146104365780637cf8ed0d1461044a575f5ffd5b80633075db56116101735780634e1273f4116101435780634e1273f41461037c5780634f1ef286146103a857806352d1902d146103bb5780635c975abb146103cf575f5ffd5b80633075db561461031f5780633659cfe6146103335780633f4ba83a1461035257806349d1260514610366575f5ffd5b806306fdde03116101ae57806306fdde031461027a5780630e89341c1461029b57806326afaadd146102ba5780632eb2c2d6146102fe575f5ffd5b8062fdd58e146101d357806301ffc9a71461020557806304f3bcec14610234575b5f5ffd5b3480156101de575f5ffd5b506101f26101ed366004612490565b6105e2565b6040519081526020015b60405180910390f35b348015610210575f5ffd5b5061022461021f3660046124cd565b61067c565b60405190151581526020016101fc565b34801561023f575f5ffd5b507f00000000000000000000000000000000000000000000000000000000000000005b6040516001600160a01b0390911681526020016101fc565b348015610285575f5ffd5b5061028e6106bb565b6040516101fc9190612535565b3480156102a6575f5ffd5b5061028e6102b5366004612547565b610748565b3480156102c5575f5ffd5b506102df61015f54610160546001600160a01b0390911691565b604080516001600160a01b0390931683526020830191909152016101fc565b348015610309575f5ffd5b5061031d6103183660046126a0565b6107db565b005b34801561032a575f5ffd5b50610224610827565b34801561033e575f5ffd5b5061031d61034d36600461274c565b61083f565b34801561035d575f5ffd5b5061031d610906565b348015610371575f5ffd5b506101f26101605481565b348015610387575f5ffd5b5061039b610396366004612765565b61098a565b6040516101fc919061286c565b61031d6103b636600461287e565b610aa9565b3480156103c6575f5ffd5b506101f2610b62565b3480156103da575f5ffd5b5061022460c954610100900460ff1660021490565b3480156103fa575f5ffd5b5061031d610c14565b34801561040e575f5ffd5b506102627f000000000000000000000000167013000000000000000000000000000000000481565b348015610441575f5ffd5b5061031d610c25565b348015610455575f5ffd5b5061015f54610262906001600160a01b031681565b348015610475575f5ffd5b5061031d610c9c565b348015610489575f5ffd5b50610262610d1b565b34801561049d575f5ffd5b506033546001600160a01b0316610262565b3480156104ba575f5ffd5b5061028e610d29565b3480156104ce575f5ffd5b5061031d6104dd3660046128be565b610d37565b3480156104ed575f5ffd5b5061031d6104fc3660046128f7565b610d42565b34801561050c575f5ffd5b5061031d61051b36600461295b565b610e11565b34801561052b575f5ffd5b5061031d61053a366004612a32565b610f8f565b34801561054a575f5ffd5b506065546001600160a01b0316610262565b348015610567575f5ffd5b50610224610576366004612ab0565b6001600160a01b039182165f90815261012e6020908152604080832093909416825291909152205460ff1690565b3480156105af575f5ffd5b5061031d6105be366004612ae1565b6110d0565b3480156105ce575f5ffd5b5061031d6105dd36600461274c565b611115565b5f6001600160a01b0383166106515760405162461bcd60e51b815260206004820152602a60248201527f455243313135353a2061646472657373207a65726f206973206e6f742061207660448201526930b634b21037bbb732b960b11b60648201526084015b60405180910390fd5b505f81815261012d602090815260408083206001600160a01b03861684529091529020545b92915050565b5f6001600160e01b03198216634d22606360e01b14806106ac57506001600160e01b0319821663689ccd8d60e11b145b80610676575061067682611186565b61016280546106c990612b34565b80601f01602080910402602001604051908101604052809291908181526020018280546106f590612b34565b80156107405780601f1061071757610100808354040283529160200191610740565b820191905f5260205f20905b81548152906001019060200180831161072357829003601f168201915b505050505081565b606061012f805461075890612b34565b80601f016020809104026020016040519081016040528092919081815260200182805461078490612b34565b80156107cf5780601f106107a6576101008083540402835291602001916107cf565b820191905f5260205f20905b8154815290600101906020018083116107b257829003601f168201915b50505050509050919050565b6001600160a01b0385163314806107f757506107f78533610576565b6108135760405162461bcd60e51b815260040161064890612b6c565b61082085858585856111d5565b5050505050565b5f600261083660c95460ff1690565b60ff1614905090565b6001600160a01b037f00000000000000000000000001670130000000000000000000000000000100981630036108875760405162461bcd60e51b815260040161064890612bba565b7f00000000000000000000000001670130000000000000000000000000000100986001600160a01b03166108b9611370565b6001600160a01b0316146108df5760405162461bcd60e51b815260040161064890612c06565b6108e88161138b565b604080515f8082526020820190925261090391839190611393565b50565b61091a60c954610100900460ff1660021490565b6109375760405163bae6e2a960e01b815260040160405180910390fd5b61094b60c9805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610988335f6114fd565b565b606081518351146109ef5760405162461bcd60e51b815260206004820152602960248201527f455243313135353a206163636f756e747320616e6420696473206c656e677468604482015268040dad2e6dac2e8c6d60bb1b6064820152608401610648565b5f83516001600160401b03811115610a0957610a0961255e565b604051908082528060200260200182016040528015610a32578160200160208202803683370190505b5090505f5b8451811015610aa157610a7c858281518110610a5557610a55612c52565b6020026020010151858381518110610a6f57610a6f612c52565b60200260200101516105e2565b828281518110610a8e57610a8e612c52565b6020908102919091010152600101610a37565b509392505050565b6001600160a01b037f0000000000000000000000000167013000000000000000000000000000010098163003610af15760405162461bcd60e51b815260040161064890612bba565b7f00000000000000000000000001670130000000000000000000000000000100986001600160a01b0316610b23611370565b6001600160a01b031614610b495760405162461bcd60e51b815260040161064890612c06565b610b528261138b565b610b5e82826001611393565b5050565b5f306001600160a01b037f00000000000000000000000001670130000000000000000000000000000100981614610c015760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610648565b505f5160206132105f395f51905f525b90565b610c1c611501565b6109885f61155b565b60655433906001600160a01b03168114610c935760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b6064820152608401610648565b6109038161155b565b610cb060c954610100900460ff1660021490565b15610cce5760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a16109883360016114fd565b5f610d24611370565b905090565b61016180546106c990612b34565b610b5e338383611574565b610d5660c954610100900460ff1660021490565b15610d745760405163bae6e2a960e01b815260040160405180910390fd5b7f0000000000000000000000001670130000000000000000000000000000000004336001600160a01b03821614610dbe576040516395383ea160e01b815260040160405180910390fd5b6002610dcc60c95460ff1690565b60ff1603610ded5760405163dfc60d8560e01b815260040160405180910390fd5b610df76002611654565b610e0233848461166a565b610e0c6001611654565b505050565b5f54610100900460ff1615808015610e2f57505f54600160ff909116105b80610e485750303b158015610e4857505f5460ff166001145b610eab5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610648565b5f805460ff191660011790558015610ecc575f805461ff0019166101001790555b610ed687876117fe565b610edf8861183a565b610f00610efb888860405180602001604052805f815250611898565b6118e2565b61015f80546001600160a01b0319166001600160a01b038916179055610160869055610161610f30858783612caa565b50610162610f3f838583612caa565b508015610f85575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b610fa360c954610100900460ff1660021490565b15610fc15760405163bae6e2a960e01b815260040160405180910390fd5b7f0000000000000000000000001670130000000000000000000000000000000004336001600160a01b0382161461100b576040516395383ea160e01b815260040160405180910390fd5b600261101960c95460ff1690565b60ff160361103a5760405163dfc60d8560e01b815260040160405180910390fd5b6110446002611654565b6110be868686808060200260200160405190810160405280939291908181526020018383602002808284375f9201919091525050604080516020808a028281018201909352898252909350899250889182918501908490808284375f92018290525060408051602081019091529081529250611911915050565b6110c86001611654565b505050505050565b6001600160a01b0385163314806110ec57506110ec8533610576565b6111085760405162461bcd60e51b815260040161064890612b6c565b6108208585858585611a93565b61111d611501565b606580546001600160a01b0383166001600160a01b0319909116811790915561114e6033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b5f6001600160e01b03198216636cdb3d1360e11b14806111b657506001600160e01b031982166303a24d0760e21b145b8061067657506301ffc9a760e01b6001600160e01b0319831614610676565b81518351146111f65760405162461bcd60e51b815260040161064890612d63565b6001600160a01b03841661121c5760405162461bcd60e51b815260040161064890612dab565b3361122b818787878787611bcd565b5f5b845181101561130a575f85828151811061124957611249612c52565b602002602001015190505f85838151811061126657611266612c52565b6020908102919091018101515f84815261012d835260408082206001600160a01b038e1683529093529190912054909150818110156112b75760405162461bcd60e51b815260040161064890612df0565b5f83815261012d602090815260408083206001600160a01b038e8116855292528083208585039055908b168252812080548492906112f6908490612e4e565b90915550506001909301925061122d915050565b50846001600160a01b0316866001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb878760405161135a929190612e61565b60405180910390a46110c8818787878787611c0d565b5f5160206132105f395f51905f52546001600160a01b031690565b610903611501565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156113c657610e0c83611d67565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611420575060408051601f3d908101601f1916820190925261141d91810190612e8e565b60015b6114835760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610648565b5f5160206132105f395f51905f5281146114f15760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610648565b50610e0c838383611e02565b610b5e5b6033546001600160a01b031633146109885760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610648565b606580546001600160a01b031916905561090381611e2c565b816001600160a01b0316836001600160a01b0316036115e75760405162461bcd60e51b815260206004820152602960248201527f455243313135353a2073657474696e6720617070726f76616c20737461747573604482015268103337b91039b2b63360b91b6064820152608401610648565b6001600160a01b038381165f81815261012e6020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b60c9805460ff191660ff92909216919091179055565b6001600160a01b0383166116cc5760405162461bcd60e51b815260206004820152602360248201527f455243313135353a206275726e2066726f6d20746865207a65726f206164647260448201526265737360e81b6064820152608401610648565b335f6116d784611e7d565b90505f6116e384611e7d565b905061170183875f858560405180602001604052805f815250611bcd565b5f85815261012d602090815260408083206001600160a01b038a168452909152902054848110156117805760405162461bcd60e51b8152602060048201526024808201527f455243313135353a206275726e20616d6f756e7420657863656564732062616c604482015263616e636560e01b6064820152608401610648565b5f86815261012d602090815260408083206001600160a01b038b81168086529184528285208a8703905582518b81529384018a90529092908816917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a460408051602081019091525f90525b50505050505050565b6001600160a01b0382161580611812575080155b8061181c57504681145b15610b5e5760405163c118d2f360e01b815260040160405180910390fd5b5f54610100900460ff166118605760405162461bcd60e51b815260040161064890612ea5565b611868611ec6565b6118866001600160a01b03821615611880578161155b565b3361155b565b5060c9805461ff001916610100179055565b60606118ae846001600160a01b03166014611eec565b6118b784612088565b836040516020016118ca93929190612ef0565b60405160208183030381529060405290509392505050565b5f54610100900460ff166119085760405162461bcd60e51b815260040161064890612ea5565b61090381612117565b6001600160a01b0384166119715760405162461bcd60e51b815260206004820152602160248201527f455243313135353a206d696e7420746f20746865207a65726f206164647265736044820152607360f81b6064820152608401610648565b81518351146119925760405162461bcd60e51b815260040161064890612d63565b336119a1815f87878787611bcd565b5f5b8451811015611a2d578381815181106119be576119be612c52565b602002602001015161012d5f8784815181106119dc576119dc612c52565b602002602001015181526020019081526020015f205f886001600160a01b03166001600160a01b031681526020019081526020015f205f828254611a209190612e4e565b90915550506001016119a3565b50846001600160a01b03165f6001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8787604051611a7d929190612e61565b60405180910390a4610820815f87878787611c0d565b6001600160a01b038416611ab95760405162461bcd60e51b815260040161064890612dab565b335f611ac485611e7d565b90505f611ad085611e7d565b9050611ae0838989858589611bcd565b5f86815261012d602090815260408083206001600160a01b038c16845290915290205485811015611b235760405162461bcd60e51b815260040161064890612df0565b5f87815261012d602090815260408083206001600160a01b038d8116855292528083208985039055908a16825281208054889290611b62908490612e4e565b909155505060408051888152602081018890526001600160a01b03808b16928c821692918816917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a4611bc2848a8a8a8a8a612146565b505050505050505050565b611be160c954610100900460ff1660021490565b15611bff5760405163bae6e2a960e01b815260040160405180910390fd5b611c0884612200565b6110c8565b6001600160a01b0384163b156110c85760405163bc197c8160e01b81526001600160a01b0385169063bc197c8190611c519089908990889088908890600401612f78565b6020604051808303815f875af1925050508015611c8b575060408051601f3d908101601f19168201909252611c8891810190612fd5565b60015b611d3757611c97612ff0565b806308c379a003611cd05750611cab613008565b80611cb65750611cd2565b8060405162461bcd60e51b81526004016106489190612535565b505b60405162461bcd60e51b815260206004820152603460248201527f455243313135353a207472616e7366657220746f206e6f6e2d455243313135356044820152732932b1b2b4bb32b91034b6b83632b6b2b73a32b960611b6064820152608401610648565b6001600160e01b0319811663bc197c8160e01b146117f55760405162461bcd60e51b815260040161064890613082565b6001600160a01b0381163b611dd45760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610648565b5f5160206132105f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b611e0b83612229565b5f82511180611e175750805b15610e0c57611e268383612268565b50505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b6040805160018082528183019092526060915f91906020808301908036833701905050905082815f81518110611eb557611eb5612c52565b602090810291909101015292915050565b5f54610100900460ff166109885760405162461bcd60e51b815260040161064890612ea5565b60605f611efa8360026130ca565b611f05906002612e4e565b6001600160401b03811115611f1c57611f1c61255e565b6040519080825280601f01601f191660200182016040528015611f46576020820181803683370190505b509050600360fc1b815f81518110611f6057611f60612c52565b60200101906001600160f81b03191690815f1a905350600f60fb1b81600181518110611f8e57611f8e612c52565b60200101906001600160f81b03191690815f1a9053505f611fb08460026130ca565b611fbb906001612e4e565b90505b6001811115612032576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110611fef57611fef612c52565b1a60f81b82828151811061200557612005612c52565b60200101906001600160f81b03191690815f1a90535060049490941c9361202b816130e1565b9050611fbe565b5083156120815760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610648565b9392505050565b60605f6120948361228d565b60010190505f816001600160401b038111156120b2576120b261255e565b6040519080825280601f01601f1916602001820160405280156120dc576020820181803683370190505b5090508181016020015b5f19016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a85049450846120e657509392505050565b5f54610100900460ff1661213d5760405162461bcd60e51b815260040161064890612ea5565b61090381612364565b6001600160a01b0384163b156110c85760405163f23a6e6160e01b81526001600160a01b0385169063f23a6e619061218a90899089908890889088906004016130f6565b6020604051808303815f875af19250505080156121c4575060408051601f3d908101601f191682019092526121c191810190612fd5565b60015b6121d057611c97612ff0565b6001600160e01b0319811663f23a6e6160e01b146117f55760405162461bcd60e51b815260040161064890613082565b306001600160a01b0382160361090357604051630c292c9d60e21b815260040160405180910390fd5b61223281611d67565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060612081838360405180606001604052806027815260200161323060279139612371565b5f8072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b83106122cb5772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef810000000083106122f7576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc10000831061231557662386f26fc10000830492506010015b6305f5e100831061232d576305f5e100830492506008015b612710831061234157612710830492506004015b60648310612353576064830492506002015b600a83106106765760010192915050565b61012f610b5e828261313a565b60605f5f856001600160a01b03168560405161238d91906131f4565b5f60405180830381855af49150503d805f81146123c5576040519150601f19603f3d011682016040523d82523d5f602084013e6123ca565b606091505b50915091506123db868383876123e5565b9695505050505050565b606083156124535782515f0361244c576001600160a01b0385163b61244c5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610648565b508161245d565b61245d8383612465565b949350505050565b815115611cb65781518083602001fd5b80356001600160a01b038116811461248b575f5ffd5b919050565b5f5f604083850312156124a1575f5ffd5b6124aa83612475565b946020939093013593505050565b6001600160e01b031981168114610903575f5ffd5b5f602082840312156124dd575f5ffd5b8135612081816124b8565b5f5b838110156125025781810151838201526020016124ea565b50505f910152565b5f81518084526125218160208601602086016124e8565b601f01601f19169290920160200192915050565b602081525f612081602083018461250a565b5f60208284031215612557575f5ffd5b5035919050565b634e487b7160e01b5f52604160045260245ffd5b601f8201601f191681016001600160401b03811182821017156125975761259761255e565b6040525050565b5f6001600160401b038211156125b6576125b661255e565b5060051b60200190565b5f82601f8301126125cf575f5ffd5b81356125da8161259e565b6040516125e78282612572565b80915082815260208101915060208360051b86010192508583111561260a575f5ffd5b602085015b8381101561262757803583526020928301920161260f565b5095945050505050565b5f82601f830112612640575f5ffd5b81356001600160401b038111156126595761265961255e565b604051612670601f8301601f191660200182612572565b818152846020838601011115612684575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f5f5f5f5f60a086880312156126b4575f5ffd5b6126bd86612475565b94506126cb60208701612475565b935060408601356001600160401b038111156126e5575f5ffd5b6126f1888289016125c0565b93505060608601356001600160401b0381111561270c575f5ffd5b612718888289016125c0565b92505060808601356001600160401b03811115612733575f5ffd5b61273f88828901612631565b9150509295509295909350565b5f6020828403121561275c575f5ffd5b61208182612475565b5f5f60408385031215612776575f5ffd5b82356001600160401b0381111561278b575f5ffd5b8301601f8101851361279b575f5ffd5b80356127a68161259e565b6040516127b38282612572565b80915082815260208101915060208360051b8501019250878311156127d6575f5ffd5b6020840193505b828410156127ff576127ee84612475565b8252602093840193909101906127dd565b945050505060208301356001600160401b0381111561281c575f5ffd5b612828858286016125c0565b9150509250929050565b5f8151808452602084019350602083015f5b82811015612862578151865260209586019590910190600101612844565b5093949350505050565b602081525f6120816020830184612832565b5f5f6040838503121561288f575f5ffd5b61289883612475565b915060208301356001600160401b038111156128b2575f5ffd5b61282885828601612631565b5f5f604083850312156128cf575f5ffd5b6128d883612475565b9150602083013580151581146128ec575f5ffd5b809150509250929050565b5f5f60408385031215612908575f5ffd5b50508035926020909101359150565b5f5f83601f840112612927575f5ffd5b5081356001600160401b0381111561293d575f5ffd5b602083019150836020828501011115612954575f5ffd5b9250929050565b5f5f5f5f5f5f5f60a0888a031215612971575f5ffd5b61297a88612475565b965061298860208901612475565b95506040880135945060608801356001600160401b038111156129a9575f5ffd5b6129b58a828b01612917565b90955093505060808801356001600160401b038111156129d3575f5ffd5b6129df8a828b01612917565b989b979a50959850939692959293505050565b5f5f83601f840112612a02575f5ffd5b5081356001600160401b03811115612a18575f5ffd5b6020830191508360208260051b8501011115612954575f5ffd5b5f5f5f5f5f60608688031215612a46575f5ffd5b612a4f86612475565b945060208601356001600160401b03811115612a69575f5ffd5b612a75888289016129f2565b90955093505060408601356001600160401b03811115612a93575f5ffd5b612a9f888289016129f2565b969995985093965092949392505050565b5f5f60408385031215612ac1575f5ffd5b612aca83612475565b9150612ad860208401612475565b90509250929050565b5f5f5f5f5f60a08688031215612af5575f5ffd5b612afe86612475565b9450612b0c60208701612475565b9350604086013592506060860135915060808601356001600160401b03811115612733575f5ffd5b600181811c90821680612b4857607f821691505b602082108103612b6657634e487b7160e01b5f52602260045260245ffd5b50919050565b6020808252602e908201527f455243313135353a2063616c6c6572206973206e6f7420746f6b656e206f776e60408201526d195c881bdc88185c1c1c9bdd995960921b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b634e487b7160e01b5f52603260045260245ffd5b601f821115610e0c57805f5260205f20601f840160051c81016020851015612c8b5750805b601f840160051c820191505b81811015610820575f8155600101612c97565b6001600160401b03831115612cc157612cc161255e565b612cd583612ccf8354612b34565b83612c66565b5f601f841160018114612d06575f8515612cef5750838201355b5f19600387901b1c1916600186901b178355610820565b5f83815260208120601f198716915b82811015612d355786850135825560209485019460019092019101612d15565b5086821015612d51575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b60208082526028908201527f455243313135353a2069647320616e6420616d6f756e7473206c656e677468206040820152670dad2e6dac2e8c6d60c31b606082015260800190565b60208082526025908201527f455243313135353a207472616e7366657220746f20746865207a65726f206164604082015264647265737360d81b606082015260800190565b6020808252602a908201527f455243313135353a20696e73756666696369656e742062616c616e636520666f60408201526939103a3930b739b332b960b11b606082015260800190565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561067657610676612e3a565b604081525f612e736040830185612832565b8281036020840152612e858185612832565b95945050505050565b5f60208284031215612e9e575f5ffd5b5051919050565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6832ba3432b932bab69d60b91b81525f8451612f138160098501602089016124e8565b600160fe1b6009918401918201528451612f3481600a8401602089016124e8565b600981830101915050712f746f6b656e5552493f75696e743235363d60701b60018201528351612f6b8160138401602088016124e8565b0160130195945050505050565b6001600160a01b0386811682528516602082015260a0604082018190525f90612fa390830186612832565b8281036060840152612fb58186612832565b90508281036080840152612fc9818561250a565b98975050505050505050565b5f60208284031215612fe5575f5ffd5b8151612081816124b8565b5f60033d1115610c115760045f5f3e505f5160e01c90565b5f60443d10156130155790565b6040513d600319016004823e80513d60248201116001600160401b038211171561303e57505090565b80820180516001600160401b03811115613059575050505090565b3d8401600319018282016020011115613073575050505090565b610aa160208285010185612572565b60208082526028908201527f455243313135353a204552433131353552656365697665722072656a656374656040820152676420746f6b656e7360c01b606082015260800190565b808202811582820484141761067657610676612e3a565b5f816130ef576130ef612e3a565b505f190190565b6001600160a01b03868116825285166020820152604081018490526060810183905260a0608082018190525f9061312f9083018461250a565b979650505050505050565b81516001600160401b038111156131535761315361255e565b613167816131618454612b34565b84612c66565b6020601f821160018114613199575f83156131825750848201515b5f19600385901b1c1916600184901b178455610820565b5f84815260208120601f198516915b828110156131c857878501518255602094850194600190920191016131a8565b50848210156131e557868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b5f82516132058184602087016124e8565b919091019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212207f2896aba4a86d1fedde1d2701bebcf994a418a7b4bfd3fd894ee616f887215264736f6c634300081b0033", + "balance": "0x0" + }, + "0x0167013000000000000000000000000000000005": { + "contractName": "SignalServiceImpl", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190" + }, + "code": "0x6080604052600436106101ba575f3560e01c806366ca2bc0116100f2578063910af6ed11610092578063dfc8ff1d11610062578063dfc8ff1d146104ef578063e30c39781461052d578063f2fde38b1461054a578063fe9fbb8014610569575f5ffd5b8063910af6ed1461047357806391f3f74b146104925780639b527cfa146104b1578063ce9d0820146104d0575f5ffd5b80638456cb59116100cd5780638456cb591461040f5780638abf6077146104235780638da5cb5b146104375780638e899f8014610454575f5ffd5b806366ca2bc0146103c8578063715018a6146103e757806379ba5097146103fb575f5ffd5b80633b78c8651161015d5780634f1ef286116101385780634f1ef286146103545780634f90a6741461036757806352d1902d146103945780635c975abb146103a8575f5ffd5b80633b78c865146103025780633ced0e08146103215780633f4ba83a14610340575f5ffd5b80633075db56116101985780633075db561461024957806332676bc61461026d578063355bcc3d1461028c5780633659cfe6146102e3575f5ffd5b806304f3bcec146101be57806319ab453c146102095780632d1fb3891461022a575b5f5ffd5b3480156101c9575f5ffd5b507f00000000000000000000000016701300000000000000000000000000000000065b6040516001600160a01b0390911681526020015b60405180910390f35b348015610214575f5ffd5b506102286102233660046135dd565b610597565b005b348015610235575f5ffd5b506102286102443660046135f8565b6106a9565b348015610254575f5ffd5b5061025d61074f565b6040519015158152602001610200565b348015610278575f5ffd5b5061025d610287366004613633565b610767565b348015610297575f5ffd5b506102cb6102a6366004613673565b60fb60209081525f92835260408084209091529082529020546001600160401b031681565b6040516001600160401b039091168152602001610200565b3480156102ee575f5ffd5b506102286102fd3660046135dd565b61077d565b34801561030d575f5ffd5b5061022861031c36600461368d565b610844565b34801561032c575f5ffd5b5061025d61033b3660046136fc565b610918565b34801561034b575f5ffd5b5061022861095f565b610228610362366004613814565b6109e3565b348015610372575f5ffd5b506103866103813660046136fc565b610a98565b604051908152602001610200565b34801561039f575f5ffd5b50610386610ade565b3480156103b3575f5ffd5b5061025d60c954610100900460ff1660021490565b3480156103d3575f5ffd5b506103866103e2366004613860565b610b8f565b3480156103f2575f5ffd5b50610228610b9b565b348015610406575f5ffd5b50610228610bac565b34801561041a575f5ffd5b50610228610c23565b34801561042e575f5ffd5b506101ec610ca2565b348015610442575f5ffd5b506033546001600160a01b03166101ec565b34801561045f575f5ffd5b5061025d61046e366004613860565b610cb0565b34801561047e575f5ffd5b5061038661048d366004613877565b610cc1565b34801561049d575f5ffd5b506103866104ac36600461390c565b610d9c565b3480156104bc575f5ffd5b506103866104cb366004613948565b610e08565b3480156104db575f5ffd5b506102286104ea366004613877565b610e34565b3480156104fa575f5ffd5b5061050e610509366004613948565b610e4a565b604080516001600160401b039093168352602083019190915201610200565b348015610538575f5ffd5b506065546001600160a01b03166101ec565b348015610555575f5ffd5b506102286105643660046135dd565b610ede565b348015610574575f5ffd5b5061025d6105833660046135dd565b60fc6020525f908152604090205460ff1681565b5f54610100900460ff16158080156105b557505f54600160ff909116105b806105ce5750303b1580156105ce57505f5460ff166001145b6106365760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b5f805460ff191660011790558015610657575f805461ff0019166101001790555b61066082610f4f565b80156106a5575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6106b1610fad565b6001600160a01b0382165f90815260fc602052604090205481151560ff9091161515036106f1576040516398f26f4560e01b815260040160405180910390fd5b6001600160a01b0382165f81815260fc6020908152604091829020805460ff191685151590811790915591519182527f4c0079b9bcd37cd5d29a13938effd97c881798cbc6bd52a3026a29d94b27d1bf910160405180910390a25050565b5f600261075e60c95460ff1690565b60ff1614905090565b5f6107728383611007565b151590505b92915050565b6001600160a01b037f00000000000000000000000001670130000000000000000000000000000000051630036107c55760405162461bcd60e51b815260040161062d90613981565b7f00000000000000000000000001670130000000000000000000000000000000056001600160a01b03166107f7611074565b6001600160a01b03161461081d5760405162461bcd60e51b815260040161062d906139cd565b6108268161108f565b604080515f8082526020820190925261084191839190611097565b50565b647461696b6f60d81b610858816001611206565b6001600160a01b0316336001600160a01b031614610889576040516395383ea160e01b815260040160405180910390fd5b5f5b828110156108d957600160fd5f8686858181106108aa576108aa613a19565b602090810292909201358352508101919091526040015f20805460ff191691151591909117905560010161088b565b507f8e7daa0b2b1abdb036d272b0c35976e908cfd7ae752bc13c70dfa049830b8d9b838360405161090b929190613a2d565b60405180910390a1505050565b5f8180820361093a5760405163ec73295960e01b815260040160405180910390fd5b5f610946878787610e08565b9050836109533083611007565b14979650505050505050565b61097360c954610100900460ff1660021490565b6109905760405163bae6e2a960e01b815260040160405180910390fd5b6109a460c9805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a16109e1335f61129f565b565b6001600160a01b037f0000000000000000000000000167013000000000000000000000000000000005163003610a2b5760405162461bcd60e51b815260040161062d90613981565b7f00000000000000000000000001670130000000000000000000000000000000056001600160a01b0316610a5d611074565b6001600160a01b031614610a835760405162461bcd60e51b815260040161062d906139cd565b610a8c8261108f565b6106a582826001611097565b335f90815260fc602052604081205460ff16610ac757604051631f67751f60e01b815260040160405180910390fd5b610ad3858585856112b8565b90505b949350505050565b5f306001600160a01b037f00000000000000000000000001670130000000000000000000000000000000051614610b7d5760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c0000000000000000606482015260840161062d565b505f516020613f985f395f51905f5290565b5f61077733838461139a565b610ba3610fad565b6109e15f611473565b60655433906001600160a01b03168114610c1a5760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b606482015260840161062d565b61084181611473565b610c3760c954610100900460ff1660021490565b15610c555760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a16109e133600161129f565b5f610cab611074565b905090565b5f610cb9825490565b151592915050565b5f610cd660c954610100900460ff1660021490565b15610cf45760405163bae6e2a960e01b815260040160405180910390fd5b6002610d0260c95460ff1690565b60ff1603610d235760405163dfc60d8560e01b815260040160405180910390fd5b60c9805460ff191660021790555f610d408787878787600161148c565b90505f5b8151811015610d8357610d6f828281518110610d6257610d62613a19565b6020026020010151611a00565b610d799084613a78565b9250600101610d44565b505060c9805460ff191660011790555b95945050505050565b6040516514d251d3905360d21b60208201526001600160c01b031960c085901b1660268201526bffffffffffffffffffffffff19606084901b16602e820152604281018290525f906062015b6040516020818303038152906040528051906020012090505b9392505050565b604080516001600160401b03808616602083015291810184905290821660608201525f90608001610de8565b610e4285858585855f61148c565b505050505050565b5f5f826001600160401b03165f03610e86576001600160401b038086165f90815260fb6020908152604080832088845290915290205416610e88565b825b91506001600160401b03821615610ed6575f610ea5868685610e08565b9050610eb13082611007565b91505f829003610ed45760405163738afa0560e01b815260040160405180910390fd5b505b935093915050565b610ee6610fad565b606580546001600160a01b0383166001600160a01b03199091168117909155610f176033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b5f54610100900460ff16610f755760405162461bcd60e51b815260040161062d90613a8b565b610f7d611b44565b610f9b6001600160a01b03821615610f955781611473565b33611473565b5060c9805461ff001916610100179055565b6033546001600160a01b031633146109e15760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161062d565b5f826001600160a01b0381166110305760405163538ba4f960e01b815260040160405180910390fd5b825f8190036110525760405163ec73295960e01b815260040160405180910390fd5b5f61105e468787610d9c565b9050611068815490565b9350505b505092915050565b5f516020613f985f395f51905f52546001600160a01b031690565b610841610fad565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156110cf576110ca83611b6a565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611129575060408051601f3d908101601f1916820190925261112691810190613ad6565b60015b61118c5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b606482015260840161062d565b5f516020613f985f395f51905f5281146111fa5760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b606482015260840161062d565b506110ca838383611c05565b5f7f0000000000000000000000001670130000000000000000000000000000000006604051633632b1fb60e11b81524660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa15801561127b573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610e019190613aed565b604051630c2b8f8f60e11b815260040160405180910390fd5b5f6112c4858585610e08565b90506112d130828461139a565b506001600160401b038581165f90815260fb602090815260408083208884529091529020548185169116101561133b576001600160401b038581165f90815260fb602090815260408083208884529091529020805467ffffffffffffffff19169185169190911790555b83836001600160401b0316866001600160401b03167fde247c825b1fb2d7ff9e0e771cba6f9e757ad04479fcdc135d88ae91fd50b37d858560405161138a929190918252602082015260400190565b60405180910390a4949350505050565b5f836001600160a01b0381166113c35760405163538ba4f960e01b815260040160405180910390fd5b835f8190036113e55760405163ec73295960e01b815260040160405180910390fd5b835f8190036114075760405163ec73295960e01b815260040160405180910390fd5b611412468888610d9c565b858155604080516001600160a01b038a16815260208101899052908101829052606081018790529094507f0ad2d108660a211f47bf7fb43a0443cae181624995d3d42b88ee6879d200e9739060800160405180910390a15050509392505050565b606580546001600160a01b031916905561084181611c2f565b6060856001600160a01b0381166114b65760405163538ba4f960e01b815260040160405180910390fd5b855f8190036114d85760405163ec73295960e01b815260040160405180910390fd5b5f85900361158a5760fd5f6114ee8b8b8b610d9c565b815260208101919091526040015f205460ff1661151e57604051632213945760e11b815260040160405180910390fd5b604080515f8082526020820190925290611582565b61156f6040805160e0810182525f80825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b8152602001906001900390816115335790505b5092506119f4565b5f61159786880188613bc5565b905080515f036115ba57604051630b92daef60e21b815260040160405180910390fd5b5f600182516115c99190613d0b565b6001600160401b038111156115e0576115e061373d565b604051908082528060200260200182016040528015611609578160200160208202803683370190505b50905085611617575f61161a565b81515b6001600160401b038111156116315761163161373d565b60405190808252806020026020018201604052801561169e57816020015b61168b6040805160e0810182525f80825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b81526020019060019003908161164f5790505b5094508a8a8a805f6116c2856d7369676e616c5f7365727669636560901b83611c80565b9050306001600160a01b038216036116ed57604051637556223560e11b815260040160405180910390fd5b6117256040805160c0810182525f80825260208201819052918101829052906060820190815260200160608152602001606081525090565b5f5f5f5f5b8b518110156119b3578b818151811061174557611745613a19565b602002602001015194505f5b818110156117af57855f01516001600160401b03168c828151811061177857611778613a19565b60200260200101516001600160401b0316036117a7576040516348362c2760e11b815260040160405180910390fd5b600101611751565b506117be8a8a8a8a898b611d22565b93508a518114915081156117fe5784516001600160401b031646146117f6576040516338bf822760e21b815260040160405180910390fd5b3095506118a9565b845f01518b828151811061181457611814613a19565b6001600160401b0392831660209182029290920101528551161580611842575084516001600160401b031646145b1561186057604051637556223560e11b815260040160405180910390fd5b845161187e906d7369676e616c5f7365727669636560901b5f611c80565b9550306001600160a01b038716036118a957604051637556223560e11b815260040160405180910390fd5b608085015151151592508f1561193d576040518060e00160405280866040015181526020018581526020018b6001600160401b0316815260200186602001516001600160401b03168152602001841515815260200183151581526020018660600151600381111561191c5761191c613d1e565b8152508f828151811061193157611931613a19565b60200260200101819052505b6119988a8461196c577fc6cdc4f2acf13acb10f410085b821f7b7113b303e9a4799023f928317396aaf561198e565b7f73e6d340850343cc6f001515dc593377337c95a6ffe034fe1e844d4dab5da1695b8760200151610e08565b604086015186519b509699509750949550879460010161172a565b508515806119ca57506119c63088611007565b8614155b156119e85760405163738afa0560e01b815260040160405180910390fd5b50505050505050505050505b50509695505050505050565b5f8060038360c001516003811115611a1a57611a1a613d1e565b1480611a3b575060028360c001516003811115611a3957611a39613d1e565b145b9050808015611a4b575082608001515b8015611a5957508260a00151155b15611a9b5760019150611a9983604001517f73e6d340850343cc6f001515dc593377337c95a6ffe034fe1e844d4dab5da1698560600151865f01516112b8565b505b5f60038460c001516003811115611ab457611ab4613d1e565b1480611ad5575060018460c001516003811115611ad357611ad3613d1e565b145b9050808015611af15750836080015180611af157508360a00151155b15611b3d57611b01600184613a78565b9250611b3b84604001517fc6cdc4f2acf13acb10f410085b821f7b7113b303e9a4799023f928317396aaf5866060015187602001516112b8565b505b5050919050565b5f54610100900460ff166109e15760405162461bcd60e51b815260040161062d90613a8b565b6001600160a01b0381163b611bd75760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161062d565b5f516020613f985f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b611c0e83611dc1565b5f82511180611c1a5750805b156110ca57611c298383611e00565b50505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f7f0000000000000000000000001670130000000000000000000000000000000006604051633632b1fb60e11b81526001600160401b03861660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa158015611cfe573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ad69190613aed565b5f856001600160a01b038116611d4b5760405163538ba4f960e01b815260040160405180910390fd5b855f819003611d6d5760405163ec73295960e01b815260040160405180910390fd5b855f819003611d8f5760405163ec73295960e01b815260040160405180910390fd5b611db3866040015186611da38d8d8d610d9c565b8a8a608001518b60a00151611e25565b9a9950505050505050505050565b611dca81611b6a565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060610e018383604051806060016040528060278152602001613fb860279139611f32565b5f82515f14611ecd576040516bffffffffffffffffffffffff19606088901b1660208201525f90611e6990603401604051602081830303815290604052858a611fa6565b905080515f03611e8c57604051630414cd5b60e31b815260040160405180910390fd5b5f611e9682611fbf565b9050611ebb81600281518110611eae57611eae613a19565b6020026020010151611fd2565b611ec490613d32565b92505050611ed0565b50855b5f611f0786604051602001611ee791815260200190565b60408051601f19818403018152919052611f0087612051565b8585612064565b905080611f2757604051638d9a4db360e01b815260040160405180910390fd5b509695505050505050565b60605f5f856001600160a01b031685604051611f4e9190613d7a565b5f60405180830381855af49150503d805f8114611f86576040519150601f19603f3d011682016040523d82523d5f602084013e611f8b565b606091505b5091509150611f9c8683838761207d565b9695505050505050565b60605f611fb2856120f5565b9050610d93818585612127565b6060610777611fcd83612995565b6129e7565b60605f5f5f611fe085612b65565b919450925090505f816001811115611ffa57611ffa613d1e565b14612018576040516307fe6cb960e21b815260040160405180910390fd5b6120228284613a78565b85511461204257604051630b8aa6f760e31b815260040160405180910390fd5b610d9385602001518484612e48565b606061077761205f83612ed8565b612fec565b5f5f61206f866120f5565b9050611f9c81868686613044565b606083156120eb5782515f036120e4576001600160a01b0385163b6120e45760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161062d565b5081610ad6565b610ad6838361306a565b6060818051906020012060405160200161211191815260200190565b6040516020818303038152906040529050919050565b60605f8451116121715760405162461bcd60e51b81526020600482015260156024820152744d65726b6c65547269653a20656d707479206b657960581b604482015260640161062d565b5f61217b84613094565b90505f61218786613177565b90505f8460405160200161219d91815260200190565b60408051601f1981840301815291905290505f805b845181101561293e575f8582815181106121ce576121ce613a19565b6020026020010151905084518311156122405760405162461bcd60e51b815260206004820152602e60248201527f4d65726b6c65547269653a206b657920696e646578206578636565647320746f60448201526d0e8c2d840d6caf240d8cadccee8d60931b606482015260840161062d565b825f036122de578051805160209182012060405161228d9261226792910190815260200190565b604051602081830303815290604052858051602091820120825192909101919091201490565b6122d95760405162461bcd60e51b815260206004820152601d60248201527f4d65726b6c65547269653a20696e76616c696420726f6f742068617368000000604482015260640161062d565b6123d4565b80515160201161236457805180516020918201206040516123089261226792910190815260200190565b6122d95760405162461bcd60e51b815260206004820152602760248201527f4d65726b6c65547269653a20696e76616c6964206c6172676520696e7465726e6044820152660c2d840d0c2e6d60cb1b606482015260840161062d565b8051845160208087019190912082519190920120146123d45760405162461bcd60e51b815260206004820152602660248201527f4d65726b6c65547269653a20696e76616c696420696e7465726e616c206e6f646044820152650ca40d0c2e6d60d31b606482015260840161062d565b6123e060106001613a78565b8160200151510361257857845183036125125761240d8160200151601081518110611eae57611eae613a19565b96505f8751116124855760405162461bcd60e51b815260206004820152603b60248201527f4d65726b6c65547269653a2076616c7565206c656e677468206d75737420626560448201527f2067726561746572207468616e207a65726f20286272616e6368290000000000606482015260840161062d565b600186516124939190613d0b565b82146125075760405162461bcd60e51b815260206004820152603a60248201527f4d65726b6c65547269653a2076616c7565206e6f6465206d757374206265206c60448201527f617374206e6f646520696e2070726f6f6620286272616e636829000000000000606482015260840161062d565b505050505050610e01565b5f85848151811061252557612525613a19565b602001015160f81c60f81b60f81c90505f82602001518260ff168151811061254f5761254f613a19565b60200260200101519050612562816131d8565b955061256f600186613a78565b94505050612935565b6002816020015151036128dc575f61258f826131fc565b90505f815f815181106125a4576125a4613a19565b016020015160f81c90505f6125ba600283613da9565b6125c5906002613dca565b90505f6125d5848360ff1661321f565b90505f6125e28a8961321f565b90505f6125ef8383613254565b9050808351146126675760405162461bcd60e51b815260206004820152603a60248201527f4d65726b6c65547269653a20706174682072656d61696e646572206d7573742060448201527f736861726520616c6c206e6962626c65732077697468206b6579000000000000606482015260840161062d565b60ff85166002148061267c575060ff85166003145b1561281c57808251146126f75760405162461bcd60e51b815260206004820152603d60248201527f4d65726b6c65547269653a206b65792072656d61696e646572206d757374206260448201527f65206964656e746963616c20746f20706174682072656d61696e646572000000606482015260840161062d565b6127118760200151600181518110611eae57611eae613a19565b9c505f8d51116127895760405162461bcd60e51b815260206004820152603960248201527f4d65726b6c65547269653a2076616c7565206c656e677468206d75737420626560448201527f2067726561746572207468616e207a65726f20286c6561662900000000000000606482015260840161062d565b60018c516127979190613d0b565b881461280b5760405162461bcd60e51b815260206004820152603860248201527f4d65726b6c65547269653a2076616c7565206e6f6465206d757374206265206c60448201527f617374206e6f646520696e2070726f6f6620286c656166290000000000000000606482015260840161062d565b505050505050505050505050610e01565b60ff8516158061282f575060ff85166001145b1561286e5761285b876020015160018151811061284e5761284e613a19565b60200260200101516131d8565b9950612867818a613a78565b98506128d1565b60405162461bcd60e51b815260206004820152603260248201527f4d65726b6c65547269653a2072656365697665642061206e6f64652077697468604482015271040c2dc40eadcd6dcdeeedc40e0e4caccd2f60731b606482015260840161062d565b505050505050612935565b60405162461bcd60e51b815260206004820152602860248201527f4d65726b6c65547269653a20726563656976656420616e20756e706172736561604482015267626c65206e6f646560c01b606482015260840161062d565b506001016121b2565b5060405162461bcd60e51b815260206004820152602560248201527f4d65726b6c65547269653a2072616e206f7574206f662070726f6f6620656c656044820152646d656e747360d81b606482015260840161062d565b604080518082019091525f808252602082015281515f036129c957604051635ab458fb60e01b815260040160405180910390fd5b50604080518082019091528151815260209182019181019190915290565b60605f5f5f6129f585612b65565b919450925090506001816001811115612a1057612a10613d1e565b14612a2e576040516325ce355f60e11b815260040160405180910390fd5b8451612a3a8385613a78565b14612a5857604051630b8aa6f760e31b815260040160405180910390fd5b604080516020808252610420820190925290816020015b604080518082019091525f8082526020820152815260200190600190039081612a6f5790505093505f835b8651811015612b59575f5f612ade6040518060400160405280858c5f0151612ac29190613d0b565b8152602001858c60200151612ad79190613a78565b9052612b65565b509150915060405180604001604052808383612afa9190613a78565b8152602001848b60200151612b0f9190613a78565b815250888581518110612b2457612b24613a19565b6020908102919091010152612b3a600185613a78565b9350612b468183613a78565b612b509084613a78565b92505050612a9a565b50845250919392505050565b5f5f5f835f01515f03612b8b57604051635ab458fb60e01b815260040160405180910390fd5b602084015180515f1a607f8111612bad575f60015f9450945094505050612e41565b60b78111612c42575f612bc1608083613d0b565b905080875f015111612be6576040516366c9448560e01b815260040160405180910390fd5b6001838101516001600160f81b0319169082148015612c125750600160ff1b6001600160f81b03198216105b15612c305760405163babb01dd60e01b815260040160405180910390fd5b506001955093505f9250612e41915050565b60bf8111612d20575f612c5660b783613d0b565b905080875f015111612c7b576040516366c9448560e01b815260040160405180910390fd5b60018301516001600160f81b0319165f819003612cab5760405163babb01dd60e01b815260040160405180910390fd5b600184015160088302610100031c60378111612cda5760405163babb01dd60e01b815260040160405180910390fd5b612ce48184613a78565b895111612d04576040516366c9448560e01b815260040160405180910390fd5b612d0f836001613a78565b975095505f9450612e419350505050565b60f78111612d6a575f612d3460c083613d0b565b905080875f015111612d59576040516366c9448560e01b815260040160405180910390fd5b600195509350849250612e41915050565b5f612d7660f783613d0b565b905080875f015111612d9b576040516366c9448560e01b815260040160405180910390fd5b60018301516001600160f81b0319165f819003612dcb5760405163babb01dd60e01b815260040160405180910390fd5b600184015160088302610100031c60378111612dfa5760405163babb01dd60e01b815260040160405180910390fd5b612e048184613a78565b895111612e24576040516366c9448560e01b815260040160405180910390fd5b612e2f836001613a78565b9750955060019450612e419350505050565b9193909250565b6060816001600160401b03811115612e6257612e6261373d565b6040519080825280601f01601f191660200182016040528015612e8c576020820181803683370190505b5090508115610e01575f612ea08486613a78565b9050602082015f5b84811015612ec0578281015182820152602001612ea8565b84811115612ece575f858301525b5050509392505050565b60605f82604051602001612eee91815260200190565b60408051601f1981840301815291905290505f5b6020811015612f3a57818181518110612f1d57612f1d613a19565b01602001516001600160f81b0319165f03612f3a57600101612f02565b612f45816020613d0b565b6001600160401b03811115612f5c57612f5c61373d565b6040519080825280601f01601f191660200182016040528015612f86576020820181803683370190505b5092505f5b8351811015611b3b578282612f9f81613de3565b935081518110612fb157612fb1613a19565b602001015160f81c60f81b848281518110612fce57612fce613a19565b60200101906001600160f81b03191690815f1a905350600101612f8b565b60608151600114801561301857506080825f8151811061300e5761300e613a19565b016020015160f81c105b15613021575090565b61302d825160806132d7565b82604051602001612111929190613dfb565b919050565b5f610ad384613054878686612127565b8051602091820120825192909101919091201490565b81511561307a5781518083602001fd5b8060405162461bcd60e51b815260040161062d9190613e29565b8051606090806001600160401b038111156130b1576130b161373d565b6040519080825280602002602001820160405280156130f657816020015b60408051808201909152606080825260208201528152602001906001900390816130cf5790505b5091505f5b81811015611b3d57604051806040016040528085838151811061312057613120613a19565b6020026020010151815260200161314f86848151811061314257613142613a19565b6020026020010151611fbf565b81525083828151811061316457613164613a19565b60209081029190910101526001016130fb565b606080604051905082518060011b603f8101601f191683016040528083525060208401602083015f5b838110156131cd578060011b8201818401515f1a8060041c8253600f8116600183015350506001016131a0565b509295945050505050565b60606020825f0151106131f3576131ee82611fd2565b610777565b61077782613474565b606061077761321a83602001515f81518110611eae57611eae613a19565b613177565b60608251821061323d575060408051602081019091525f8152610777565b610e01838384865161324f9190613d0b565b613488565b5f5f8251845110613266578251613269565b83515b90505b80821080156132c0575082828151811061328857613288613a19565b602001015160f81c60f81b6001600160f81b0319168483815181106132af576132af613a19565b01602001516001600160f81b031916145b156132d05781600101915061326c565b5092915050565b6060603883101561333b576040805160018082528183019092529060208201818036833701905050905061330b8284613e5b565b60f81b815f8151811061332057613320613a19565b60200101906001600160f81b03191690815f1a905350610777565b5f60015b6133498186613e74565b1561336f578161335881613de3565b9250613368905061010082613e87565b905061333f565b61337a826001613a78565b6001600160401b038111156133915761339161373d565b6040519080825280601f01601f1916602001820160405280156133bb576020820181803683370190505b5092506133c88483613e5b565b6133d3906037613e5b565b60f81b835f815181106133e8576133e8613a19565b60200101906001600160f81b03191690815f1a905350600190505b81811161106c576101006134178284613d0b565b61342390610100613f79565b61342d9087613e74565b6134379190613f84565b60f81b83828151811061344c5761344c613a19565b60200101906001600160f81b03191690815f1a9053508061346c81613de3565b915050613403565b606061077782602001515f845f0151612e48565b60608182601f0110156134ce5760405162461bcd60e51b815260206004820152600e60248201526d736c6963655f6f766572666c6f7760901b604482015260640161062d565b8282840110156135115760405162461bcd60e51b815260206004820152600e60248201526d736c6963655f6f766572666c6f7760901b604482015260640161062d565b818301845110156135585760405162461bcd60e51b8152602060048201526011602482015270736c6963655f6f75744f66426f756e647360781b604482015260640161062d565b6060821580156135765760405191505f8252602082016040526135c0565b6040519150601f8416801560200281840101858101878315602002848b0101015b818310156135af578051835260209283019201613597565b5050858452601f01601f1916604052505b50949350505050565b6001600160a01b0381168114610841575f5ffd5b5f602082840312156135ed575f5ffd5b8135610e01816135c9565b5f5f60408385031215613609575f5ffd5b8235613614816135c9565b915060208301358015158114613628575f5ffd5b809150509250929050565b5f5f60408385031215613644575f5ffd5b823561364f816135c9565b946020939093013593505050565b80356001600160401b038116811461303f575f5ffd5b5f5f60408385031215613684575f5ffd5b61364f8361365d565b5f5f6020838503121561369e575f5ffd5b82356001600160401b038111156136b3575f5ffd5b8301601f810185136136c3575f5ffd5b80356001600160401b038111156136d8575f5ffd5b8560208260051b84010111156136ec575f5ffd5b6020919091019590945092505050565b5f5f5f5f6080858703121561370f575f5ffd5b6137188561365d565b93506020850135925061372d6040860161365d565b9396929550929360600135925050565b634e487b7160e01b5f52604160045260245ffd5b60405160c081016001600160401b03811182821017156137735761377361373d565b60405290565b604051601f8201601f191681016001600160401b03811182821017156137a1576137a161373d565b604052919050565b5f82601f8301126137b8575f5ffd5b81356001600160401b038111156137d1576137d161373d565b6137e4601f8201601f1916602001613779565b8181528460208386010111156137f8575f5ffd5b816020850160208301375f918101602001919091529392505050565b5f5f60408385031215613825575f5ffd5b8235613830816135c9565b915060208301356001600160401b0381111561384a575f5ffd5b613856858286016137a9565b9150509250929050565b5f60208284031215613870575f5ffd5b5035919050565b5f5f5f5f5f6080868803121561388b575f5ffd5b6138948661365d565b945060208601356138a4816135c9565b93506040860135925060608601356001600160401b038111156138c5575f5ffd5b8601601f810188136138d5575f5ffd5b80356001600160401b038111156138ea575f5ffd5b8860208284010111156138fb575f5ffd5b959894975092955050506020019190565b5f5f5f6060848603121561391e575f5ffd5b6139278461365d565b92506020840135613937816135c9565b929592945050506040919091013590565b5f5f5f6060848603121561395a575f5ffd5b6139638461365d565b9250602084013591506139786040850161365d565b90509250925092565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b634e487b7160e01b5f52603260045260245ffd5b602080825281018290525f6001600160fb1b03831115613a4b575f5ffd5b8260051b80856040850137919091016040019392505050565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561077757610777613a64565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f60208284031215613ae6575f5ffd5b5051919050565b5f60208284031215613afd575f5ffd5b8151610e01816135c9565b5f6001600160401b03821115613b2057613b2061373d565b5060051b60200190565b80356004811061303f575f5ffd5b5f82601f830112613b47575f5ffd5b8135613b5a613b5582613b08565b613779565b8082825260208201915060208360051b860101925085831115613b7b575f5ffd5b602085015b83811015613bbb5780356001600160401b03811115613b9d575f5ffd5b613bac886020838a01016137a9565b84525060209283019201613b80565b5095945050505050565b5f60208284031215613bd5575f5ffd5b81356001600160401b03811115613bea575f5ffd5b8201601f81018413613bfa575f5ffd5b8035613c08613b5582613b08565b8082825260208201915060208360051b850101925086831115613c29575f5ffd5b602084015b83811015611f275780356001600160401b03811115613c4b575f5ffd5b850160c0818a03601f19011215613c60575f5ffd5b613c68613751565b613c746020830161365d565b8152613c826040830161365d565b602082015260608201356040820152613c9d60808301613b2a565b606082015260a08201356001600160401b03811115613cba575f5ffd5b613cc98b602083860101613b38565b60808301525060c08201356001600160401b03811115613ce7575f5ffd5b613cf68b602083860101613b38565b60a08301525084525060209283019201613c2e565b8181038181111561077757610777613a64565b634e487b7160e01b5f52602160045260245ffd5b80516020808301519190811015613d52575f198160200360031b1b821691505b50919050565b5f5b83811015613d72578181015183820152602001613d5a565b50505f910152565b5f8251613d8b818460208701613d58565b9190910192915050565b634e487b7160e01b5f52601260045260245ffd5b5f60ff831680613dbb57613dbb613d95565b8060ff84160691505092915050565b60ff828116828216039081111561077757610777613a64565b5f60018201613df457613df4613a64565b5060010190565b5f8351613e0c818460208801613d58565b835190830190613e20818360208801613d58565b01949350505050565b602081525f8251806020840152613e47816040850160208701613d58565b601f01601f19169190910160400192915050565b60ff818116838216019081111561077757610777613a64565b5f82613e8257613e82613d95565b500490565b808202811582820484141761077757610777613a64565b6001815b6001841115610ed657808504811115613ebd57613ebd613a64565b6001841615613ecb57908102905b60019390931c928002613ea2565b5f82613ee757506001610777565b81613ef357505f610777565b8160018114613f095760028114613f1357613f2f565b6001915050610777565b60ff841115613f2457613f24613a64565b50506001821b610777565b5060208310610133831016604e8410600b8410161715613f52575081810a610777565b613f5e5f198484613e9e565b805f1904821115613f7157613f71613a64565b029392505050565b5f610e018383613ed9565b5f82613f9257613f92613d95565b50069056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212202a702cbc411a740a30cd3fb741b7974fedd78c62eef4f4a0419b623cc7b3afd164736f6c634300081b0033", + "balance": "0x0" + }, + "0x1670130000000000000000000000000000000005": { + "contractName": "SignalService", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000c9": "0x0000000000000000000000000000000000000000000000000000000000000101", + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190", + "0x0d37e26abb66bc712790f295c42206cae5fa437057759b1abb72e6bff9dcb5d6": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0167013000000000000000000000000000000005" + }, + "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033", + "balance": "0x0" + }, + "0x0167013000000000000000000000000000010001": { + "contractName": "TaikoAnchorImpl", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190" + }, + "code": "0x608060405260043610610207575f3560e01c80638abf607711610113578063cbd9999e1161009d578063e902461a1161006d578063e902461a14610617578063ee82ac5e14610631578063f2fde38b14610650578063f940e3851461066f578063fd85eb2d1461068e575f5ffd5b8063cbd9999e146105af578063da69d3db146105c6578063dac5df78146105e5578063e30c3978146105fa575f5ffd5b8063a7137c0f116100e3578063a7137c0f146104dc578063a7e022d114610502578063b310e9e91461053e578063b8c7b30c1461055d578063ba9f41e81461057c575f5ffd5b80638abf6077146104865780638da5cb5b1461049a5780639ee512f2146104b7578063a4b23554146102ac575f5ffd5b80634f1ef2861161019457806362d094531161016457806362d09453146103d3578063715018a61461040657806379ba50971461041a5780638456cb591461042e578063893f546014610442575f5ffd5b80634f1ef2861461035857806352d1902d1461036b578063539b8ade1461038d5780635c975abb146103b3575f5ffd5b80633075db56116101da5780633075db56146102ca57806333d5ac9b146102de5780633659cfe6146103045780633f4ba83a1461032557806348080a4514610339575f5ffd5b806304f3bcec1461020b57806312622e5b14610256578063136dc4a81461028d5780632f980473146102ac575b5f5ffd5b348015610216575f5ffd5b507f00000000000000000000000016701300000000000000000000000000000000065b6040516001600160a01b0390911681526020015b60405180910390f35b348015610261575f5ffd5b5060fe54610275906001600160401b031681565b6040516001600160401b03909116815260200161024d565b348015610298575f5ffd5b506102756102a73660046126d0565b6106ad565b3480156102b7575f5ffd5b505f5b604051901515815260200161024d565b3480156102d5575f5ffd5b506102ba6106c7565b3480156102e9575f5ffd5b5060fd5461027590600160401b90046001600160401b031681565b34801561030f575f5ffd5b5061032361031e366004612724565b6106df565b005b348015610330575f5ffd5b506103236107af565b348015610344575f5ffd5b50610323610353366004612769565b610833565b610323610366366004612829565b610a60565b348015610376575f5ffd5b5061037f610b19565b60405190815260200161024d565b348015610398575f5ffd5b5060fd5461027590600160801b90046001600160401b031681565b3480156103be575f5ffd5b506102ba60c954610100900460ff1660021490565b3480156103de575f5ffd5b506102397f000000000000000000000000167013000000000000000000000000000000000581565b348015610411575f5ffd5b50610323610bca565b348015610425575f5ffd5b50610323610bdb565b348015610439575f5ffd5b50610323610c52565b34801561044d575f5ffd5b5061046161045c3660046128ec565b610cd1565b604080519384526001600160401b03928316602085015291169082015260600161024d565b348015610491575f5ffd5b50610239610e11565b3480156104a5575f5ffd5b506033546001600160a01b0316610239565b3480156104c2575f5ffd5b5061023971777735367b36bc9b61c50022d9d0700db4ec81565b3480156104e7575f5ffd5b5060fd5461027590600160c01b90046001600160401b031681565b34801561050d575f5ffd5b5061052161051c366004612924565b610e1f565b604080519283526001600160401b0390911660208301520161024d565b348015610549575f5ffd5b50610323610558366004612955565b610e3a565b348015610568575f5ffd5b5060fd54610275906001600160401b031681565b348015610587575f5ffd5b506102757f000000000000000000000000000000000000000000000000000000000000000081565b3480156105ba575f5ffd5b5061037f63017d784081565b3480156105d1575f5ffd5b506103236105e0366004612972565b61106c565b3480156105f0575f5ffd5b5061037f60fc5481565b348015610605575f5ffd5b506065546001600160a01b0316610239565b348015610622575f5ffd5b5061052161051c3660046129b5565b34801561063c575f5ffd5b5061037f61064b3660046129fd565b611085565b34801561065b575f5ffd5b5061032361066a366004612724565b6110bd565b34801561067a575f5ffd5b50610323610689366004612a14565b61112e565b348015610699575f5ffd5b506103236106a8366004612a4b565b6112e5565b5f6040516372c0090b60e11b815260040160405180910390fd5b5f60026106d660c95460ff1690565b60ff1614905090565b6001600160a01b037f00000000000000000000000001670130000000000000000000000000000100011630036107305760405162461bcd60e51b815260040161072790612a8c565b60405180910390fd5b7f00000000000000000000000001670130000000000000000000000000000100016001600160a01b0316610762611494565b6001600160a01b0316146107885760405162461bcd60e51b815260040161072790612ad8565b610791816114af565b604080515f808252602082019092526107ac918391906114b7565b50565b6107c360c954610100900460ff1660021490565b6107e05760405163bae6e2a960e01b815260040160405180910390fd5b6107f460c9805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610831335f611626565b565b845f8190036108555760405163ec73295960e01b815260040160405180910390fd5b866001600160401b0316805f0361087f5760405163ec73295960e01b815260040160405180910390fd5b61088f6060860160408701612b24565b63ffffffff16805f036108b55760405163ec73295960e01b815260040160405180910390fd5b6108c26020870187612b3d565b60ff16805f036108e55760405163ec73295960e01b815260040160405180910390fd5b3371777735367b36bc9b61c50022d9d0700db4ec1461091757604051636494e9f760e01b815260040160405180910390fd5b600261092560c95460ff1690565b60ff16036109465760405163dfc60d8560e01b815260040160405180910390fd5b610950600261162e565b7f00000000000000000000000000000000000000000000000000000000000000006001600160401b031643101561099a57604051631799c89b60e01b815260040160405180910390fd5b5f6109a6600143612b71565b90506109b181611644565b6109bb898961167c565b6109c58b8b611763565b6109ce81611881565b604051633b78c86560e01b81526001600160a01b037f00000000000000000000000016701300000000000000000000000000000000051690633b78c86590610a1c908a908a90600401612b84565b5f604051808303815f87803b158015610a33575f5ffd5b505af1158015610a45573d5f5f3e3d5ffd5b5050505050610a54600161162e565b50505050505050505050565b6001600160a01b037f0000000000000000000000000167013000000000000000000000000000010001163003610aa85760405162461bcd60e51b815260040161072790612a8c565b7f00000000000000000000000001670130000000000000000000000000000100016001600160a01b0316610ada611494565b6001600160a01b031614610b005760405162461bcd60e51b815260040161072790612ad8565b610b09826114af565b610b15828260016114b7565b5050565b5f306001600160a01b037f00000000000000000000000001670130000000000000000000000000000100011614610bb85760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610727565b505f516020612db45f395f51905f5290565b610bd2611918565b6108315f611972565b60655433906001600160a01b03168114610c495760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b6064820152608401610727565b6107ac81611972565b610c6660c954610100900460ff1660021490565b15610c845760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a1610831336001611626565b5f808080610ce26020860186612b3d565b60ff16610cf56060870160408801612b24565b63ffffffff16610d059190612bbb565b60fd54909150610d29906001600160401b03600160c01b820481169184911661198b565b90935091505f610d3f6060870160408801612b24565b63ffffffff1660fd60109054906101000a90046001600160401b031688610d669190612be4565b610d709190612bbb565b9050610d8260a0870160808801612b24565b63ffffffff1615801590610db35750610da160a0870160808801612b24565b63ffffffff16816001600160401b0316115b15610dd157610dc860a0870160808801612b24565b63ffffffff1690505b610ded8484838b610de860808c0160608d01612c03565b611aa3565b909550925063017d7840851015610e065763017d784094505b505093509350939050565b5f610e1a611494565b905090565b5f5f6040516372c0090b60e11b815260040160405180910390fd5b5f54610100900460ff1615808015610e5857505f54600160ff909116105b80610e715750303b158015610e7157505f5460ff166001145b610ed45760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610727565b5f805460ff191660011790558015610ef5575f805461ff0019166101001790555b610efe84611b23565b826001600160401b03165f03610f27576040516308279a2560e31b815260040160405180910390fd5b46836001600160401b031603610f50576040516308279a2560e31b815260040160405180910390fd5b60014611610f7157604051638f972ecb60e01b815260040160405180910390fd5b6001600160401b03461115610f9957604051638f972ecb60e01b815260040160405180910390fd5b4315610fe35743600103610fca575f610fb3600143612b71565b5f81815260fb602052604090209040905550610fe3565b604051635a0f9e4160e11b815260040160405180910390fd5b60fe80546001600160401b0380861667ffffffffffffffff199283161790925560fd80549285169290911691909117905561101d43611b81565b5060fc558015611066575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050565b6040516372c0090b60e11b815260040160405180910390fd5b5f43821061109457505f919050565b436110a183610100612c1c565b106110ab57504090565b505f90815260fb602052604090205490565b6110c5611918565b606580546001600160a01b0383166001600160a01b031990911681179091556110f66033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b806001600160a01b0381166111565760405163538ba4f960e01b815260040160405180910390fd5b61116a60c954610100900460ff1660021490565b156111885760405163bae6e2a960e01b815260040160405180910390fd5b693bb4ba34323930bbb2b960b11b6111a86033546001600160a01b031690565b6001600160a01b0316336001600160a01b031614806111e257506111cd816001611c11565b6001600160a01b0316336001600160a01b0316145b6111ff576040516395383ea160e01b815260040160405180910390fd5b600261120d60c95460ff1690565b60ff160361122e5760405163dfc60d8560e01b815260040160405180910390fd5b611238600261162e565b6001600160a01b03841661125e576112596001600160a01b03841647611cb3565b6112db565b6040516370a0823160e01b81523060048201526112db9084906001600160a01b038716906370a0823190602401602060405180830381865afa1580156112a6573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112ca9190612c2f565b6001600160a01b0387169190611cbe565b611066600161162e565b825f8190036113075760405163ec73295960e01b815260040160405180910390fd5b846001600160401b0316805f036113315760405163ec73295960e01b815260040160405180910390fd5b6113416060840160408501612b24565b63ffffffff16805f036113675760405163ec73295960e01b815260040160405180910390fd5b6113746020850185612b3d565b60ff16805f036113975760405163ec73295960e01b815260040160405180910390fd5b3371777735367b36bc9b61c50022d9d0700db4ec146113c957604051636494e9f760e01b815260040160405180910390fd5b60026113d760c95460ff1690565b60ff16036113f85760405163dfc60d8560e01b815260040160405180910390fd5b611402600261162e565b7f00000000000000000000000000000000000000000000000000000000000000006001600160401b0316431061144b57604051631799c89b60e01b815260040160405180910390fd5b5f611457600143612b71565b905061146281611644565b61146c878761167c565b6114768989611763565b61147f81611881565b5061148a600161162e565b5050505050505050565b5f516020612db45f395f51905f52546001600160a01b031690565b6107ac611918565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156114ef576114ea83611d10565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611549575060408051601f3d908101601f1916820190925261154691810190612c2f565b60015b6115ac5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610727565b5f516020612db45f395f51905f52811461161a5760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610727565b506114ea838383611dab565b610b15611918565b60c9805460ff191660ff92909216919091179055565b5f5f61164f83611b81565b915091508160fc54146116755760405163d719258d60e01b815260040160405180910390fd5b60fc555050565b5f5f5f61168a854286610cd1565b9250925092508248148061169b57505f5b6116b8576040516336d54d4f60e11b815260040160405180910390fd5b60fd5460408051600160c01b83046001600160401b039081168252858116602083015292831681830152918316606083015260808201859052517f781ae5c2215806150d5c71a4ed5336e5dc3ad32aef04fc0f626a6ee0c2f8d1c89181900360a00190a160fd805477ffffffffffffffffffffffffffffffff000000000000000016600160c01b6001600160401b039485160267ffffffffffffffff19161791909216179055505050565b60fd546001600160401b03600160401b909104811690831611611784575050565b60fe546040516313e4299d60e21b81526001600160401b0391821660048201527f73e6d340850343cc6f001515dc593377337c95a6ffe034fe1e844d4dab5da16960248201529083166044820152606481018290527f00000000000000000000000016701300000000000000000000000000000000056001600160a01b031690634f90a674906084016020604051808303815f875af1158015611829573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061184d9190612c2f565b505060fd80546001600160401b03909216600160401b026fffffffffffffffff000000000000000019909216919091179055565b5f81815260fb60205260409081902082409081905560fd80546001600160401b03428116600160801b0267ffffffffffffffff60801b1983168117909355935192937f41c3f410f5c8ac36bb46b1dccef0de0f964087c9e688795fa02ecfa2c20b3fe49361190c938693908316921691909117909182526001600160401b0316602082015260400190565b60405180910390a15050565b6033546001600160a01b031633146108315760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610727565b606580546001600160a01b03191690556107ac81611dcf565b5f80670de0b6b3a76400006001600160401b03861682036119b25784849250925050611a9b565b6001600160401b03851615806119d95750846001600160401b0316866001600160401b0316145b806119f757506119ea815f19612c5a565b856001600160401b031610155b15611a085785849250925050611a9b565b5f866001600160401b0316866001600160401b031683611a289190612c6d565b611a329190612c5a565b9050801580611a4757506001600160ff1b0381115b15611a59578585935093505050611a9b565b5f611a6382611e20565b90505f828702828902015f811260018114611a82578582049250611a86565b5f92505b505087611a928261203d565b95509550505050505b935093915050565b5f8080611abf63ffffffff86166001600160401b038916612c1c565b9050856001600160401b03168111611ad8576001611aeb565b611aeb6001600160401b03871682612b71565b9050611b0a6001600160401b03611b048387831661204f565b90612064565b9150611b168883612078565b9250509550959350505050565b5f54610100900460ff16611b495760405162461bcd60e51b815260040161072790612c84565b611b516120ba565b611b6f6001600160a01b03821615611b695781611972565b33611972565b5060c9805461ff001916610100179055565b5f5f611b8b612695565b46611fe08201525f5b60ff81108015611ba75750806001018510155b15611bd8575f198186030180408360ff83066101008110611bca57611bca612ccf565b602002015250600101611b94565b5061200081209250834081611bee60ff87612ce3565b6101008110611bff57611bff612ccf565b60200201526120009020919391925050565b5f7f0000000000000000000000001670130000000000000000000000000000000006604051633632b1fb60e11b81524660048201526024810185905283151560448201526001600160a01b039190911690636c6563f690606401602060405180830381865afa158015611c86573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611caa9190612cf6565b90505b92915050565b610b1582825a6120e0565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b1790526114ea908490612123565b6001600160a01b0381163b611d7d5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610727565b5f516020612db45f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b611db4836121f6565b5f82511180611dc05750805b156114ea576110668383612235565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b6fffffffffffffffffffffffffffffffff811160071b81811c6001600160401b031060061b1781811c63ffffffff1060051b1781811c61ffff1060041b1781811c60ff1060031b175f8213611e7c57631615e6385f526004601cfd5b7ff8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff6f8421084210842108cc6318c6db6d54be83831c1c601f161a1890811b609f90811c6c465772b2bbbb5f824b15207a3081018102606090811d6d0388eaa27412d5aca026815d636e018202811d6d0df99ac502031bf953eff472fdcc018202811d6d13cdffb29d51d99322bdff5f2211018202811d6d0a0f742023def783a307a986912e018202811d6d01920d8043ca89b5239253284e42018202811d6c0b7a86d7375468fac667a0a527016c29508e458543d8aa4df2abee7883018302821d6d0139601a2efabe717e604cbb4894018302821d6d02247f7a7b6594320649aa03aba1018302821d6c8c3f38e95a6b1ff2ab1c3b343619018302821d6d02384773bdf1ac5676facced60901901830290911d6cb9a025d814b29c212b8b1a07cd1901909102780a09507084cc699bb0e71ea869ffffffffffffffffffffffff190105711340daa0d5f769dba1915cef59f0815a5506029190037d0267a36c0c95b3975ab3ee5b203a7614a3f75373f047d803ae7b6687f2b302017d57115e47018c7177eebf7cd370a3356a1b7863008a5ae8028c72b88642840160ae1d90565b5f611cad826001600160401b03612064565b5f81831161205d5781611caa565b5090919050565b5f8183116120725782611caa565b50919050565b5f826001600160401b03165f0361209157506001611cad565b611caa6001846001600160401b03166120aa868661225a565b6120b49190612c5a565b9061204f565b5f54610100900460ff166108315760405162461bcd60e51b815260040161072790612c84565b815f036120ec57505050565b61210683838360405180602001604052805f8152506122e8565b6114ea57604051634c67134d60e11b815260040160405180910390fd5b5f612177826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166123259092919063ffffffff16565b905080515f14806121975750808060200190518101906121979190612d11565b6114ea5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610727565b6121ff81611d10565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b6060611caa8383604051806060016040528060278152602001612dd460279139612333565b5f826001600160401b03165f0361227357612273612d30565b5f836001600160401b0316836001600160401b0316670de0b6b3a764000061229b9190612c6d565b6122a59190612c5a565b9050680755bf798b4a1bf1e48111156122c45750680755bf798b4a1bf1e45b670de0b6b3a76400006122d6826123a7565b6122e09190612c5a565b949350505050565b5f6001600160a01b03851661231057604051634c67134d60e11b815260040160405180910390fd5b5f5f835160208501878988f195945050505050565b60606122e084845f85612521565b60605f5f856001600160a01b03168560405161234f9190612d66565b5f60405180830381855af49150503d805f8114612387576040519150601f19603f3d011682016040523d82523d5f602084013e61238c565b606091505b509150915061239d868383876125f8565b9695505050505050565b5f68023f2fa8f6da5b9d281982136123be57919050565b680755bf798b4a1bf1e582126123db5763a37bfec95f526004601cfd5b6503782dace9d9604e83901b0591505f60606bb17217f7d1cf79abc9e3b39884821b056001605f1b01901d6bb17217f7d1cf79abc9e3b39881029093036c240c330e9fb2d9cbaf0fd5aafb1981018102606090811d6d0277594991cfc85f6e2461837cd9018202811d6d1a521255e34f6a5061b25ef1c9c319018202811d6db1bbb201f443cf962f1a1d3db4a5018202811d6e02c72388d9f74f51a9331fed693f1419018202811d6e05180bb14799ab47a8a8cb2a527d57016d02d16720577bd19bf614176fe9ea6c10fe68e7fd37d0007b713f765084018402831d9081019084016d01d3967ed30fc4f89c02bab5708119010290911d6e0587f503bb6ea29d25fcb740196450019091026d360d7aeea093263ecc6e0ecb291760621b010574029d9dc38563c32e5c2f6dc192ee70ef65f9978af30260c3939093039290921c92915050565b6060824710156125825760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610727565b5f5f866001600160a01b0316858760405161259d9190612d66565b5f6040518083038185875af1925050503d805f81146125d7576040519150601f19603f3d011682016040523d82523d5f602084013e6125dc565b606091505b50915091506125ed878383876125f8565b979650505050505050565b606083156126665782515f0361265f576001600160a01b0385163b61265f5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610727565b50816122e0565b6122e0838381511561267b5781518083602001fd5b8060405162461bcd60e51b81526004016107279190612d81565b604051806120000160405280610100906020820280368337509192915050565b80356001600160401b03811681146126cb575f5ffd5b919050565b5f5f5f606084860312156126e2575f5ffd5b6126eb846126b5565b92506126f9602085016126b5565b9150612707604085016126b5565b90509250925092565b6001600160a01b03811681146107ac575f5ffd5b5f60208284031215612734575f5ffd5b813561273f81612710565b9392505050565b803563ffffffff811681146126cb575f5ffd5b5f60a08284031215612072575f5ffd5b5f5f5f5f5f5f610120878903121561277f575f5ffd5b612788876126b5565b95506020870135945061279d60408801612746565b93506127ac8860608901612759565b92506101008701356001600160401b038111156127c7575f5ffd5b8701601f810189136127d7575f5ffd5b80356001600160401b038111156127ec575f5ffd5b8960208260051b8401011115612800575f5ffd5b60208201935080925050509295509295509295565b634e487b7160e01b5f52604160045260245ffd5b5f5f6040838503121561283a575f5ffd5b823561284581612710565b915060208301356001600160401b0381111561285f575f5ffd5b8301601f8101851361286f575f5ffd5b80356001600160401b0381111561288857612888612815565b604051601f8201601f19908116603f011681016001600160401b03811182821017156128b6576128b6612815565b6040528181528282016020018710156128cd575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f5f5f60e084860312156128fe575f5ffd5b61290784612746565b9250612915602085016126b5565b91506127078560408601612759565b5f5f60408385031215612935575f5ffd5b61293e836126b5565b915061294c60208401612746565b90509250929050565b5f5f5f60608486031215612967575f5ffd5b83356126eb81612710565b5f5f5f5f60808587031215612985575f5ffd5b843593506020850135925061299c604086016126b5565b91506129aa60608601612746565b905092959194509250565b5f5f5f5f61010085870312156129c9575f5ffd5b6129d38686612759565b93506129e160a086016126b5565b92506129ef60c086016126b5565b91506129aa60e08601612746565b5f60208284031215612a0d575f5ffd5b5035919050565b5f5f60408385031215612a25575f5ffd5b8235612a3081612710565b91506020830135612a4081612710565b809150509250929050565b5f5f5f5f6101008587031215612a5f575f5ffd5b612a68856126b5565b935060208501359250612a7d60408601612746565b91506129aa8660608701612759565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b5f60208284031215612b34575f5ffd5b611caa82612746565b5f60208284031215612b4d575f5ffd5b813560ff8116811461273f575f5ffd5b634e487b7160e01b5f52601160045260245ffd5b81810381811115611cad57611cad612b5d565b602080825281018290525f6001600160fb1b03831115612ba2575f5ffd5b8260051b80856040850137919091016040019392505050565b6001600160401b038181168382160290811690818114612bdd57612bdd612b5d565b5092915050565b6001600160401b038281168282160390811115611cad57611cad612b5d565b5f60208284031215612c13575f5ffd5b611caa826126b5565b80820180821115611cad57611cad612b5d565b5f60208284031215612c3f575f5ffd5b5051919050565b634e487b7160e01b5f52601260045260245ffd5b5f82612c6857612c68612c46565b500490565b8082028115828204841417611cad57611cad612b5d565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b634e487b7160e01b5f52603260045260245ffd5b5f82612cf157612cf1612c46565b500690565b5f60208284031215612d06575f5ffd5b815161273f81612710565b5f60208284031215612d21575f5ffd5b8151801515811461273f575f5ffd5b634e487b7160e01b5f52600160045260245ffd5b5f5b83811015612d5e578181015183820152602001612d46565b50505f910152565b5f8251612d77818460208701612d44565b9190910192915050565b602081525f8251806020840152612d9f816040850160208701612d44565b601f01601f1916919091016040019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220669eb9d95fcd3c7905a112b05bcdc814d1518dc8f68911d2974a03490315299c64736f6c634300081b0033", + "balance": "0x0" + }, + "0x1670130000000000000000000000000000010001": { + "contractName": "TaikoAnchor", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000c9": "0x0000000000000000000000000000000000000000000000000000000000000101", + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190", + "0x00000000000000000000000000000000000000000000000000000000000000fe": "0x0000000000000000000000000000000000000000000000000000000000088bb0", + "0x00000000000000000000000000000000000000000000000000000000000000fd": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00000000000000000000000000000000000000000000000000000000000000fc": "0x65ac1596c2e62c560ce8eee277b9bae6f0b86052d5e7b24a83456828ddd144a9", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0167013000000000000000000000000000010001" + }, + "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033", + "balance": "0x0" + }, + "0x0167013000000000000000000000000000010002": { + "contractName": "RollupResolverImpl", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190" + }, + "code": "0x6080604052600436106100fa575f3560e01c80636c6563f6116100925780638abf6077116100625780638abf6077146102515780638da5cb5b14610265578063b490d87f14610282578063e30c3978146102a1578063f2fde38b146102be575f5ffd5b80636c6563f6146101f6578063715018a61461021557806379ba5097146102295780638456cb591461023d575f5ffd5b80633f4ba83a116100cd5780633f4ba83a1461018d5780634f1ef286146101a157806352d1902d146101b45780635c975abb146101d6575f5ffd5b806304f3bcec146100fe57806319ab453c146101295780633075db561461014a5780633659cfe61461016e575b5f5ffd5b348015610109575f5ffd5b50305b6040516001600160a01b0390911681526020015b60405180910390f35b348015610134575f5ffd5b50610148610143366004610e60565b6102dd565b005b348015610155575f5ffd5b5061015e6103ef565b6040519015158152602001610120565b348015610179575f5ffd5b50610148610188366004610e60565b610407565b348015610198575f5ffd5b506101486104ce565b6101486101af366004610e8d565b610552565b3480156101bf575f5ffd5b506101c8610607565b604051908152602001610120565b3480156101e1575f5ffd5b5061015e60c954610100900460ff1660021490565b348015610201575f5ffd5b5061010c610210366004610f51565b6106b8565b348015610220575f5ffd5b50610148610709565b348015610234575f5ffd5b5061014861071a565b348015610248575f5ffd5b50610148610791565b34801561025c575f5ffd5b5061010c610810565b348015610270575f5ffd5b506033546001600160a01b031661010c565b34801561028d575f5ffd5b5061014861029c366004610f8b565b61081e565b3480156102ac575f5ffd5b506065546001600160a01b031661010c565b3480156102c9575f5ffd5b506101486102d8366004610e60565b61089f565b5f54610100900460ff16158080156102fb57505f54600160ff909116105b806103145750303b15801561031457505f5460ff166001145b61037c5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b5f805460ff19166001179055801561039d575f805461ff0019166101001790555b6103a682610910565b80156103eb575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b5f60026103fe60c95460ff1690565b60ff1614905090565b6001600160a01b037f000000000000000000000000016701300000000000000000000000000001000216300361044f5760405162461bcd60e51b815260040161037390610fbd565b7f00000000000000000000000001670130000000000000000000000000000100026001600160a01b031661048161096e565b6001600160a01b0316146104a75760405162461bcd60e51b815260040161037390611009565b6104b081610989565b604080515f808252602082019092526104cb91839190610991565b50565b6104e260c954610100900460ff1660021490565b6104ff5760405163bae6e2a960e01b815260040160405180910390fd5b61051360c9805461ff001916610100179055565b6040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa9060200160405180910390a1610550335f610b00565b565b6001600160a01b037f000000000000000000000000016701300000000000000000000000000001000216300361059a5760405162461bcd60e51b815260040161037390610fbd565b7f00000000000000000000000001670130000000000000000000000000000100026001600160a01b03166105cc61096e565b6001600160a01b0316146105f25760405162461bcd60e51b815260040161037390611009565b6105fb82610989565b6103eb82826001610991565b5f306001600160a01b037f000000000000000000000000016701300000000000000000000000000001000216146106a65760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c00000000000000006064820152608401610373565b505f5160206111275f395f51905f5290565b5f83815260fb602090815260408083208584529091529020546001600160a01b0316801515806106e55750815b61070257604051631692906160e11b815260040160405180910390fd5b9392505050565b610711610b19565b6105505f610b73565b60655433906001600160a01b031681146107885760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b6064820152608401610373565b6104cb81610b73565b6107a560c954610100900460ff1660021490565b156107c35760405163bae6e2a960e01b815260040160405180910390fd5b60c9805461ff0019166102001790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2589060200160405180910390a1610550336001610b00565b5f61081961096e565b905090565b610826610b19565b5f83815260fb6020908152604080832085845282529182902080546001600160a01b038581166001600160a01b0319831681179093558451928352169181018290529091849186917f3fd0559a7b01eb7106f9d9ce79ec76bb44f608a295878cce50856e54dba83d35910160405180910390a350505050565b6108a7610b19565b606580546001600160a01b0383166001600160a01b031990911681179091556108d86033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b5f54610100900460ff166109365760405162461bcd60e51b815260040161037390611055565b61093e610b8c565b61095c6001600160a01b038216156109565781610b73565b33610b73565b5060c9805461ff001916610100179055565b5f5160206111275f395f51905f52546001600160a01b031690565b6104cb610b19565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156109c9576109c483610bb2565b505050565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015610a23575060408051601f3d908101601f19168201909252610a20918101906110a0565b60015b610a865760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201526d6f6e206973206e6f74205555505360901b6064820152608401610373565b5f5160206111275f395f51905f528114610af45760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f786044820152681a58589b195555525160ba1b6064820152608401610373565b506109c4838383610c4d565b604051630c2b8f8f60e11b815260040160405180910390fd5b6033546001600160a01b031633146105505760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610373565b606580546001600160a01b03191690556104cb81610c77565b5f54610100900460ff166105505760405162461bcd60e51b815260040161037390611055565b6001600160a01b0381163b610c1f5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610373565b5f5160206111275f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b610c5683610cc8565b5f82511180610c625750805b156109c457610c718383610d07565b50505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b610cd181610bb2565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b606061070283836040518060600160405280602781526020016111476027913960605f5f856001600160a01b031685604051610d4391906110d9565b5f60405180830381855af49150503d805f8114610d7b576040519150601f19603f3d011682016040523d82523d5f602084013e610d80565b606091505b5091509150610d9186838387610d9b565b9695505050505050565b60608315610e095782515f03610e02576001600160a01b0385163b610e025760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610373565b5081610e13565b610e138383610e1b565b949350505050565b815115610e2b5781518083602001fd5b8060405162461bcd60e51b815260040161037391906110f4565b80356001600160a01b0381168114610e5b575f5ffd5b919050565b5f60208284031215610e70575f5ffd5b61070282610e45565b634e487b7160e01b5f52604160045260245ffd5b5f5f60408385031215610e9e575f5ffd5b610ea783610e45565b9150602083013567ffffffffffffffff811115610ec2575f5ffd5b8301601f81018513610ed2575f5ffd5b803567ffffffffffffffff811115610eec57610eec610e79565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610f1b57610f1b610e79565b604052818152828201602001871015610f32575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f5f5f60608486031215610f63575f5ffd5b833592506020840135915060408401358015158114610f80575f5ffd5b809150509250925092565b5f5f5f60608486031215610f9d575f5ffd5b8335925060208401359150610fb460408501610e45565b90509250925092565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b5f602082840312156110b0575f5ffd5b5051919050565b5f5b838110156110d15781810151838201526020016110b9565b50505f910152565b5f82516110ea8184602087016110b7565b9190910192915050565b602081525f82518060208401526111128160408501602087016110b7565b601f01601f1916919091016040019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212203c364628b47f58f18110ccb14bff9bcef5ebda8cf5ca50013c8ed8384ae4bdd464736f6c634300081b0033", + "balance": "0x0" + }, + "0x1670130000000000000000000000000000010002": { + "contractName": "RollupResolver", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000c9": "0x0000000000000000000000000000000000000000000000000000000000000101", + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000001d2d1bb9d180541e88a6a682acf3f61c1605b190", + "0x217f440aaa3002cdfb2ee9ebce80b5991546524a0b76b2ccefea002962b55a19": "0x0000000000000000000000001670130000000000000000000000000000010001", + "0x1fd4f8aa15619802dcb55d6c851af651ac835d9fe7dc456a2a320fe1e7ce194b": "0x0000000000000000000000001670130000000000000000000000000000000001", + "0x96584a0117748840ea3710035480e8e9cd718737d5d8957a6bffb6161d8a348b": "0x0000000000000000000000001670130000000000000000000000000000000005", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0167013000000000000000000000000000010002" + }, + "code": "0x608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f5f375f5f365f845af43d5f5f3e8080156070573d5ff35b3d5ffdfea264697066735822122063ca278dcc774bf6b03f066b470ab5bd61081120baaf8096d8309758d80de2c864736f6c634300081b0033", + "balance": "0x0" + }, + "0xB74bD4a2dC4Da79ce21Ab84674AB26427fE510B7": { + "contractName": "LibNetwork", + "storage": {}, + "code": "0x730000000000000000000000000000000000000000301460806040525f5ffdfea26469706673582212203c09fc61e7a424e456f9357d2bdff9106f2247c580fb60c88a1d16c70027fa7f64736f6c634300081b0033", + "balance": "0x0" + }, + "0x0167013000000000000000000000000000010099": { + "contractName": "RegularERC20", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x526567756c617245524332300000000000000000000000000000000000000018", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x52474c0000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x00000000000000000000000000000000000000000000000000000000001f4000", + "0xf0fbb1025eb51c8e0aa1ce65781b63aba37155608a1636c0a3fbe6ef8b43f835": "0x00000000000000000000000000000000000000000000000000000000000fa000", + "0xb2e82b18f3e970a88f670af6f13ac31b3a1cedd212d7da2b2b433ac5cc7a1615": "0x00000000000000000000000000000000000000000000000000000000000fa000" + }, + "code": "0x608060405234801561000f575f5ffd5b50600436106100a6575f3560e01c8063395093511161006e578063395093511461011f57806370a082311461013257806395d89b411461015a578063a457c2d714610162578063a9059cbb14610175578063dd62ed3e14610188575f5ffd5b806306fdde03146100aa578063095ea7b3146100c857806318160ddd146100eb57806323b872dd146100fd578063313ce56714610110575b5f5ffd5b6100b261019b565b6040516100bf919061068a565b60405180910390f35b6100db6100d63660046106f0565b61022b565b60405190151581526020016100bf565b6002545b6040519081526020016100bf565b6100db61010b366004610718565b610244565b604051601281526020016100bf565b6100db61012d3660046106f0565b610267565b6100ef610140366004610752565b6001600160a01b03165f9081526020819052604090205490565b6100b2610288565b6100db6101703660046106f0565b610297565b6100db6101833660046106f0565b610316565b6100ef610196366004610772565b610323565b6060600380546101aa906107a3565b80601f01602080910402602001604051908101604052809291908181526020018280546101d6906107a3565b80156102215780601f106101f857610100808354040283529160200191610221565b820191905f5260205f20905b81548152906001019060200180831161020457829003601f168201915b5050505050905090565b5f3361023881858561034d565b60019150505b92915050565b5f33610251858285610470565b61025c8585856104e8565b506001949350505050565b5f336102388185856102798383610323565b61028391906107db565b61034d565b6060600480546101aa906107a3565b5f33816102a48286610323565b9050838110156103095760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b61025c828686840361034d565b5f336102388185856104e8565b6001600160a01b039182165f90815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166103af5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610300565b6001600160a01b0382166104105760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610300565b6001600160a01b038381165f8181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b5f61047b8484610323565b90505f1981146104e257818110156104d55760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610300565b6104e2848484840361034d565b50505050565b6001600160a01b03831661054c5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610300565b6001600160a01b0382166105ae5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610300565b6001600160a01b0383165f90815260208190526040902054818110156106255760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610300565b6001600160a01b038481165f81815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a36104e2565b602081525f82518060208401525f5b818110156106b65760208186018101516040868401015201610699565b505f604082850101526040601f19601f83011684010191505092915050565b80356001600160a01b03811681146106eb575f5ffd5b919050565b5f5f60408385031215610701575f5ffd5b61070a836106d5565b946020939093013593505050565b5f5f5f6060848603121561072a575f5ffd5b610733846106d5565b9250610741602085016106d5565b929592945050506040919091013590565b5f60208284031215610762575f5ffd5b61076b826106d5565b9392505050565b5f5f60408385031215610783575f5ffd5b61078c836106d5565b915061079a602084016106d5565b90509250929050565b600181811c908216806107b757607f821691505b6020821081036107d557634e487b7160e01b5f52602260045260245ffd5b50919050565b8082018082111561023e57634e487b7160e01b5f52601160045260245ffdfea2646970667358221220869af61ba192d71b8c1d28cf90c6c223b1f37ff8f70da696b7a220e9bada3b1264736f6c634300081b0033", + "balance": "0x0" + } + } +} diff --git a/src/Nethermind/Chains/tbn-mainnet.json.zst b/src/Nethermind/Chains/tbn-mainnet.json.zst index c5a68a72f169..a7a8d39b0736 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 129f99607809..208e9288d5e7 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 2095634e15a3..4569abcb8c62 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 301c407b8531..7ff9d5fd16e3 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 c5d391b9f48c..5f0e5ba592c9 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 84ad7ec26290..317fe2140907 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 d96bdac61bcc..0ca4331c1180 100644 --- a/src/Nethermind/Chains/xdc.json +++ b/src/Nethermind/Chains/xdc.json @@ -45,22 +45,98 @@ "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 } - ] + ], + + "MergeSignRange": 15, + + "tip2019Block": 1, + "BlackListHFNumber": 38383838, + "blackListedAddresses": [ + "0x5248bfb72fd4f234e062d3e9bb76f08643004fcd", + "0x5ac26105b35ea8935be382863a70281ec7a985e9", + "0x09c4f991a41e7ca0645d7dfbfee160b55e562ea4", + "0xb3157bbc5b401a45d6f60b106728bb82ebaa585b", + "0x741277a8952128d5c2ffe0550f5001e4c8247674", + "0x10ba49c1caa97d74b22b3e74493032b180cebe01", + "0x07048d51d9e6179578a6e3b9ee28cdc183b865e4", + "0x4b899001d73c7b4ec404a771d37d9be13b8983de", + "0x85cb320a9007f26b7652c19a2a65db1da2d0016f", + "0x06869dbd0e3a2ea37ddef832e20fa005c6f0ca39", + "0x82e48bc7e2c93d89125428578fb405947764ad7c", + "0x1f9a78534d61732367cbb43fc6c89266af67c989", + "0x7c3b1fa91df55ff7af0cad9e0399384dc5c6641b", + "0x5888dc1ceb0ff632713486b9418e59743af0fd20", + "0xa512fa1c735fc3cc635624d591dd9ea1ce339ca5", + "0x0832517654c7b7e36b1ef45d76de70326b09e2c7", + "0xca14e3c4c78bafb60819a78ff6e6f0f709d2aea7", + "0x652ce195a23035114849f7642b0e06647d13e57a", + "0x29a79f00f16900999d61b6e171e44596af4fb5ae", + "0xf9fd1c2b0af0d91b0b6754e55639e3f8478dd04a", + "0xb835710c9901d5fe940ef1b99ed918902e293e35", + "0x04dd29ce5c253377a9a3796103ea0d9a9e514153", + "0x2b4b56846eaf05c1fd762b5e1ac802efd0ab871c", + "0x1d1f909f6600b23ce05004f5500ab98564717996", + "0x0dfdcebf80006dc9ab7aae8c216b51c6b6759e86", + "0x2b373890a28e5e46197fbc04f303bbfdd344056f", + "0xa8a3ef3dc5d8e36aee76f3671ec501ec31e28254", + "0x4f3d18136fe2b5665c29bdaf74591fc6625ef427", + "0x175d728b0e0f1facb5822a2e0c03bde93596e324", + "0xd575c2611984fcd79513b80ab94f59dc5bab4916", + "0x0579337873c97c4ba051310236ea847f5be41bc0", + "0xed12a519cc15b286920fc15fd86106b3e6a16218", + "0x492d26d852a0a0a2982bb40ec86fe394488c419e", + "0xce5c7635d02dc4e1d6b46c256cae6323be294a32", + "0x8b94db158b5e78a6c032c7e7c9423dec62c8b11c", + "0x0e7c48c085b6b0aa7ca6e4cbcc8b9a92dc270eb4", + "0x206e6508462033ef8425edc6c10789d241d49acb", + "0x7710e7b7682f26cb5a1202e1cff094fbf7777758", + "0xcb06f949313b46bbf53b8e6b2868a0c260ff9385", + "0xf884e43533f61dc2997c0e19a6eff33481920c00", + "0x8b635ef2e4c8fe21fc2bda027eb5f371d6aa2fc1", + "0x10f01a27cf9b29d02ce53497312b96037357a361", + "0x693dd49b0ed70f162d733cf20b6c43dc2a2b4d95", + "0xe0bec72d1c2a7a7fb0532cdfac44ebab9f6f41ee", + "0xc8793633a537938cb49cdbbffd45428f10e45b64", + "0x0d07a6cbbe9fa5c4f154e5623bfe47fb4d857d8e", + "0xd4080b289da95f70a586610c38268d8d4cf1e4c4", + "0x8bcfb0caf41f0aa1b548cae76dcdd02e33866a1b", + "0xabfef22b92366d3074676e77ea911ccaabfb64c1", + "0xcc4df7a32faf3efba32c9688def5ccf9fefe443d", + "0x7ec1e48a582475f5f2b7448a86c4ea7a26ea36f8", + "0xe3de67289080f63b0c2612844256a25bb99ac0ad", + "0x3ba623300cf9e48729039b3c9e0dee9b785d636e", + "0x402f2cfc9c8942f5e7a12c70c625d07a5d52fe29", + "0xd62358d42afbde095a4ca868581d85f9adcc3d61", + "0x3969f86acb733526cd61e3c6e3b4660589f32bc6", + "0x67615413d7cdadb2c435a946aec713a9a9794d39", + "0xfe685f43acc62f92ab01a8da80d76455d39d3cb3", + "0x3538a544021c07869c16b764424c5987409cba48", + "0xe187cf86c2274b1f16e8225a7da9a75aba4f1f5f", + "0x0000000000000000000000000000000000000011" + ], + + "masternodeVotingContract": "0x0000000000000000000000000000000000000088", + "blockSignerContract": "0x0000000000000000000000000000000000000089", + "randomizeSMCBinary": "0x0000000000000000000000000000000000000090", + "XDCXAddrBinary": "0x0000000000000000000000000000000000000091", + "tradingStateAddressBinary": "0x0000000000000000000000000000000000000092", + "XDCXLendingAddressBinary": "0x0000000000000000000000000000000000000093", + "XDCXLendingFinalizedTradeAddressBinary": "0x0000000000000000000000000000000000000094" } } }, @@ -71,7 +147,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 2b1015e90d27..cf5e06d4a5f7 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 7d39faf4aa13..b71d1ecb1f9a 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 723ac1039556..06ec4c0b5449 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 7b11b8a99740..b46ea4a88f4e 100644 --- a/src/Nethermind/Directory.Build.props +++ b/src/Nethermind/Directory.Build.props @@ -3,16 +3,16 @@ - $([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()) - $(Commit) - 1.35.0 + $(SOURCE_DATE_EPOCH) + $([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()) + 1.37.0 unstable - <_Parameter1>BuildTimestamp - <_Parameter2>$(BuildTimestamp) + <_Parameter1>SourceDateEpoch + <_Parameter2>$(SourceDateEpoch) diff --git a/src/Nethermind/Directory.Build.targets b/src/Nethermind/Directory.Build.targets new file mode 100644 index 000000000000..4c920adb3cb1 --- /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.Basic.Test/TransactionTests.cs b/src/Nethermind/Ethereum.Basic.Test/TransactionTests.cs index 739fe57310be..cf74e74b3947 100644 --- a/src/Nethermind/Ethereum.Basic.Test/TransactionTests.cs +++ b/src/Nethermind/Ethereum.Basic.Test/TransactionTests.cs @@ -13,7 +13,6 @@ using Nethermind.Core.Extensions; using Nethermind.Crypto; using Nethermind.Int256; -using Nethermind.Logging; using Nethermind.Serialization.Rlp; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Test/Blockhash/blockhash.json b/src/Nethermind/Ethereum.Blockchain.Block.Test/Blockhash/blockhash.json similarity index 100% rename from src/Nethermind/Ethereum.Blockchain.Test/Blockhash/blockhash.json rename to src/Nethermind/Ethereum.Blockchain.Block.Test/Blockhash/blockhash.json diff --git a/src/Nethermind/Ethereum.Blockchain.Test/Eip3860LimitmeterInitCodeTests.cs b/src/Nethermind/Ethereum.Blockchain.Block.Test/BlockhashTests.cs similarity index 73% rename from src/Nethermind/Ethereum.Blockchain.Test/Eip3860LimitmeterInitCodeTests.cs rename to src/Nethermind/Ethereum.Blockchain.Block.Test/BlockhashTests.cs index bacdc7c7ea57..36b0c76707e9 100644 --- a/src/Nethermind/Ethereum.Blockchain.Test/Eip3860LimitmeterInitCodeTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Block.Test/BlockhashTests.cs @@ -1,15 +1,17 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; +using System.IO; using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Test; +namespace Ethereum.Blockchain.Block.Test; [TestFixture] [Parallelizable(ParallelScope.All)] -public class Eip3860LimitmeterInitCodeTests : GeneralStateTestBase +public class BlockhashTests : GeneralStateTestBase { [TestCaseSource(nameof(LoadTests))] public void Test(GeneralStateTest test) @@ -19,7 +21,7 @@ public void Test(GeneralStateTest test) public static IEnumerable LoadTests() { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stEIP3860-limitmeterinitcode"); + var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Blockhash")); return loader.LoadTests(); } } diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Test/Ethereum.Blockchain.Block.Test.csproj b/src/Nethermind/Ethereum.Blockchain.Block.Test/Ethereum.Blockchain.Block.Test.csproj index 1787402212f2..1dff0badfeef 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Test/Ethereum.Blockchain.Block.Test.csproj +++ b/src/Nethermind/Ethereum.Blockchain.Block.Test/Ethereum.Blockchain.Block.Test.csproj @@ -1,5 +1,5 @@ - + @@ -12,7 +12,15 @@ PreserveNewest + + + + PreserveNewest + + + + diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Test/ForgedTests.cs b/src/Nethermind/Ethereum.Blockchain.Block.Test/ForgedTests.cs index a0a2c7957bb6..5950609cb85b 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Test/ForgedTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Block.Test/ForgedTests.cs @@ -16,10 +16,11 @@ public class ForgedTests : BlockchainTestBase [TestCaseSource(nameof(LoadTests))] public async Task Test(BlockchainTest test) { - bool isWindows = System.Runtime.InteropServices.RuntimeInformation - .IsOSPlatform(OSPlatform.Windows); + bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); if (isWindows) + { return; + } await RunTest(test); } diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Test/StateTests.cs b/src/Nethermind/Ethereum.Blockchain.Block.Test/StateTests.cs index 656e042667c7..298b73893beb 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Test/StateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Block.Test/StateTests.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using Ethereum.Test.Base; -using Nethermind.Core.Attributes; using NUnit.Framework; namespace Ethereum.Blockchain.Block.Test @@ -13,7 +12,6 @@ namespace Ethereum.Blockchain.Block.Test [Parallelizable(ParallelScope.All)] public class StateTests : BlockchainTestBase { - [Todo(Improve.TestCoverage, "SuicideStorage tests")] [TestCaseSource(nameof(LoadTests)), Retry(3)] public async Task Test(BlockchainTest test) { diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CallCreateCallCodeTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CallCreateCallCodeTests.cs deleted file mode 100644 index df2bd0ad6f08..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CallCreateCallCodeTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class CallCreateCallCodeTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stCallCreateCallCodeTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CallDelegateCodesCallCodeHomesteadTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CallDelegateCodesCallCodeHomesteadTests.cs deleted file mode 100644 index 035a83621966..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CallDelegateCodesCallCodeHomesteadTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class CallDelegateCodesCallCodeHomesteadTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stCallDelegateCodesCallCodeHomestead"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CallDelegateCodesHomesteadTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CallDelegateCodesHomesteadTests.cs deleted file mode 100644 index 223d7e702ba3..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CallDelegateCodesHomesteadTests.cs +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class CallDelegateCodesHomesteadTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stCallDelegateCodesHomestead"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ChangedEIP150Tests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ChangedEIP150Tests.cs deleted file mode 100644 index e767545f5a44..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ChangedEIP150Tests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ChangedEIP150Tests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stChangedEIP150"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CodeCopyTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CodeCopyTests.cs deleted file mode 100644 index 951067aa7b7f..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CodeCopyTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class CodeCopyTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stCodeCopyTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CodeSizeLimitTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CodeSizeLimitTests.cs deleted file mode 100644 index ec2f3899cca5..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CodeSizeLimitTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class CodeSizeLimitTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stCodeSizeLimit"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Create2Tests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Create2Tests.cs deleted file mode 100644 index 4ea837b1759f..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Create2Tests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class Create2Tests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stCreate2"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CreateTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CreateTests.cs deleted file mode 100644 index c0761e20222b..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CreateTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class CreateTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stCreateTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/DelegateCallTestHomesteadTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/DelegateCallTestHomesteadTests.cs deleted file mode 100644 index 5604942d4417..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/DelegateCallTestHomesteadTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class DelegateCallTestHomesteadTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stDelegatecallTestHomestead"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/EIP150SingleCodeGasPricesTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/EIP150SingleCodeGasPricesTests.cs deleted file mode 100644 index 85e455636700..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/EIP150SingleCodeGasPricesTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class EIP150SingleCodeGasPricesTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP150singleCodeGasPrices"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Eip150SpecificTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Eip150SpecificTests.cs deleted file mode 100644 index 9602f8be4d4d..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Eip150SpecificTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class Eip150SpecificTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP150Specific"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Eip158SpecificTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Eip158SpecificTests.cs deleted file mode 100644 index e8b16addcdb3..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Eip158SpecificTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class Eip158SpecificTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP158Specific"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Ethereum.Blockchain.Legacy.Test.csproj b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Ethereum.Blockchain.Legacy.Test.csproj deleted file mode 100644 index 82c175be3fc2..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Ethereum.Blockchain.Legacy.Test.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ExampleTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ExampleTests.cs deleted file mode 100644 index 5147949057e9..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ExampleTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ExampleTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stExample"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ExtCodeHashTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ExtCodeHashTests.cs deleted file mode 100644 index 119e3979f486..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ExtCodeHashTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ExtCodeHashTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stExtCodeHash"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/HomesteadSpecificTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/HomesteadSpecificTests.cs deleted file mode 100644 index c23c1052ac42..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/HomesteadSpecificTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class HomesteadSpecificTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stHomesteadSpecific"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/InitCodeTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/InitCodeTests.cs deleted file mode 100644 index e4d3423a1af9..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/InitCodeTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class InitCodeTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stInitCodeTest"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/MemExpandingEip150CallsTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/MemExpandingEip150CallsTests.cs deleted file mode 100644 index eb534eec6286..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/MemExpandingEip150CallsTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class MemExpandingEip150CallsTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stMemExpandingEIP150Calls"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/MemoryStressTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/MemoryStressTests.cs deleted file mode 100644 index b8ed97fba088..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/MemoryStressTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class MemoryStressTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stMemoryStressTest"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/MemoryTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/MemoryTests.cs deleted file mode 100644 index 2a215c22b9ea..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/MemoryTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class MemoryTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stMemoryTest"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/NonZeroCallTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/NonZeroCallTests.cs deleted file mode 100644 index eb816ae3aad8..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/NonZeroCallTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class NonZeroCallTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stNonZeroCallsTest"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/PreCompiledContracts2Tests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/PreCompiledContracts2Tests.cs deleted file mode 100644 index 377611884a8b..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/PreCompiledContracts2Tests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class PreCompiledContracts2Tests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stPreCompiledContracts2"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/PreCompiledContractsTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/PreCompiledContractsTests.cs deleted file mode 100644 index 0c55e7fcf251..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/PreCompiledContractsTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class PreCompiledContractsTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stPreCompiledContracts"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/QuadraticComplexityTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/QuadraticComplexityTests.cs deleted file mode 100644 index 0f7ae025fddd..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/QuadraticComplexityTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class QuadraticComplexityTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stQuadraticComplexityTest"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Random2Tests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Random2Tests.cs deleted file mode 100644 index 04a97abdad1a..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Random2Tests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class Random2Tests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stRandom2"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/RandomTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/RandomTests.cs deleted file mode 100644 index e77bf15e90ee..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/RandomTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class RandomTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stRandom"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/RecursiveCreateTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/RecursiveCreateTests.cs deleted file mode 100644 index cca3d38b529e..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/RecursiveCreateTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class RecursiveCreateTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stRecursiveCreate"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/RefundTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/RefundTests.cs deleted file mode 100644 index 18c2c316bfd7..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/RefundTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class RefundTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stRefundTest"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ReturnDataTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ReturnDataTests.cs deleted file mode 100644 index 8e718473a7f0..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ReturnDataTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ReturnDataTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stReturnDataTest"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/RevertTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/RevertTests.cs deleted file mode 100644 index 3b69a316454a..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/RevertTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using System.Linq; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class RevertTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stRevertTest"); - IEnumerable tests = loader.LoadTests(); - HashSet ignoredTests = new() - { - "RevertPrecompiledTouch", - }; - - return tests.Where(t => !ignoredTests.Any(pattern => t.Name.Contains(pattern))); ; - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/SStoreTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/SStoreTests.cs deleted file mode 100644 index f5477c916627..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/SStoreTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class SStoreTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stSStoreTest"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ShiftTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ShiftTests.cs deleted file mode 100644 index 5b614d672fa1..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ShiftTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ShiftTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stShift"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/SolidityTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/SolidityTests.cs deleted file mode 100644 index a292a97dae65..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/SolidityTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class SolidityTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stSolidityTest"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/SpecialTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/SpecialTests.cs deleted file mode 100644 index 6fb70e5ee9c4..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/SpecialTests.cs +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class SpecialTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - [Retry(3)] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stSpecialTest"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/StackTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/StackTests.cs deleted file mode 100644 index 5921030d5903..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/StackTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class StackTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stStackTests"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/StaticCallTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/StaticCallTests.cs deleted file mode 100644 index 29afd2f8536d..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/StaticCallTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class StaticCallTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stStaticCall"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/SystemOperationsTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/SystemOperationsTests.cs deleted file mode 100644 index a1f1fefcb3e0..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/SystemOperationsTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class SystemOperationsTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stSystemOperationsTest"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/TimeConsumingTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/TimeConsumingTests.cs deleted file mode 100644 index cc4c7372d419..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/TimeConsumingTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class TimeConsumingTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stTimeConsuming"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/TransactionTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/TransactionTests.cs deleted file mode 100644 index 310d1e1b5e7d..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/TransactionTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class TransactionTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stTransactionTest"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/TransitionTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/TransitionTests.cs deleted file mode 100644 index be1484e6f613..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/TransitionTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class TransitionTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stTransitionTest"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/WalletTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/WalletTests.cs deleted file mode 100644 index 7aa893f3b264..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/WalletTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class WalletTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stWalletTest"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ZeroCallsRevertTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ZeroCallsRevertTests.cs deleted file mode 100644 index 7e59be7dbde4..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ZeroCallsRevertTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ZeroCallsRevertTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stZeroCallsRevert"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ZeroCallsTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ZeroCallsTests.cs deleted file mode 100644 index e864de956473..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ZeroCallsTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ZeroCallsTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stZeroCallsTest"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ZeroKnowledge2Tests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ZeroKnowledge2Tests.cs deleted file mode 100644 index 2b9051b62304..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ZeroKnowledge2Tests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ZeroKnowledge2Tests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stZeroKnowledge2"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ZeroKnowledgeTests.cs b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ZeroKnowledgeTests.cs deleted file mode 100644 index af324039128f..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ZeroKnowledgeTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Legacy.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ZeroKnowledgeTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stZeroKnowledge"); - return loader.LoadTests(); - } - } -} - diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinBlockChainTests.cs index 264bc859ca77..5e202049eaf8 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinBlockChainTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinBlockChainTests.cs @@ -1,5 +1,7 @@ +// 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 Ethereum.Test.Base; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinStateTests.cs index cdc20c50d11a..cc32ed244b0f 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinStateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/BerlinStateTests.cs @@ -1,5 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using FluentAssertions; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumBlockChainTests.cs index b6be913b4c5d..3583300d49c2 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumBlockChainTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumBlockChainTests.cs @@ -1,5 +1,7 @@ +// 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 Ethereum.Test.Base; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumStateTests.cs index a650f3c79666..7579dc2ed9fc 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumStateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ByzantiumStateTests.cs @@ -1,5 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using FluentAssertions; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunBlockChainTests.cs index 62695ccc71fd..da29277f3419 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunBlockChainTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunBlockChainTests.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Ethereum.Test.Base; using FluentAssertions; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunStateTests.cs index f1f952b6adde..9a7d2402270d 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunStateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/CancunStateTests.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using FluentAssertions; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/Constants.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/Constants.cs index 8c80687245d1..57243feacaee 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/Constants.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/Constants.cs @@ -2,9 +2,10 @@ // SPDX-License-Identifier: LGPL-3.0-only namespace Ethereum.Blockchain.Pyspec.Test; + public class Constants { public const string ARCHIVE_URL_TEMPLATE = "https://github.com/ethereum/execution-spec-tests/releases/download/{0}/{1}"; - public const string DEFAULT_ARCHIVE_VERSION = "v4.4.0"; + public const string DEFAULT_ARCHIVE_VERSION = "v5.0.0"; public const string DEFAULT_ARCHIVE_NAME = "fixtures_develop.tar.gz"; } diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierBlockChainTests.cs index fae15df4b213..0b3c9a4a6417 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierBlockChainTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierBlockChainTests.cs @@ -1,5 +1,7 @@ +// 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 Ethereum.Test.Base; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierStateTests.cs index 6011d779adf7..fbf265a22a8d 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierStateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/FrontierStateTests.cs @@ -1,5 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using FluentAssertions; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadBlockChainTests.cs index eba45011cbb3..fc6bc1c07b95 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadBlockChainTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadBlockChainTests.cs @@ -1,5 +1,7 @@ +// 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 Ethereum.Test.Base; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadStateTests.cs index 44ec8d4041f5..0fec88659d02 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadStateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/HomesteadStateTests.cs @@ -1,5 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using FluentAssertions; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulBlockChainTests.cs index af62090dd9e9..4ad81bb85f3a 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulBlockChainTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulBlockChainTests.cs @@ -1,5 +1,7 @@ +// 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 Ethereum.Test.Base; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulStateTests.cs index a4daf560f351..fe0f84a61d36 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulStateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/IstanbulStateTests.cs @@ -1,5 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using FluentAssertions; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaBlockChainTests.cs index 6e8cafbada58..99231fdd948d 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaBlockChainTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaBlockChainTests.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Ethereum.Test.Base; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaEofTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaEofTests.cs index 28326dea3909..874f6a40f6bb 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaEofTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaEofTests.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaStateTests.cs index 097c9c8f658c..d9c6248b9404 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaStateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaStateTests.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using FluentAssertions; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ParisBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ParisBlockChainTests.cs index 757bbe2ba065..0f424de14f10 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ParisBlockChainTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ParisBlockChainTests.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Ethereum.Test.Base; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueStateTests.cs index 15c20e8ef162..e9eec4dc3a84 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueStateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueStateTests.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using FluentAssertions; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiBlockChainTests.cs index 2d273d715659..2e85ba8918d9 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiBlockChainTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiBlockChainTests.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Ethereum.Test.Base; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiStateTests.cs index 5c75a9b80c63..b8e304a15167 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiStateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/ShanghaiStateTests.cs @@ -1,5 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using FluentAssertions; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Test/ArgsZeroOneBalanceTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/ArgsZeroOneBalanceTests.cs deleted file mode 100644 index 70861549a512..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/ArgsZeroOneBalanceTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ArgsZeroOneBalanceTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stArgsZeroOneBalance"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/AttackTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/AttackTests.cs deleted file mode 100644 index c63c4027fe1b..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/AttackTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class AttackTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stAttackTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/BadOpcodeTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/BadOpcodeTests.cs deleted file mode 100644 index 826aa7d9bf59..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/BadOpcodeTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class BadOpcodeTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - [Retry(3)] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stBadOpcode"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/BlockhashTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/BlockhashTests.cs deleted file mode 100644 index a929c2829dc1..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/BlockhashTests.cs +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.IO; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class BlockhashTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Blockhash")); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/BugsTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/BugsTests.cs deleted file mode 100644 index 544b1043d855..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/BugsTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class BugsTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stBugs"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/CallCodeTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/CallCodeTests.cs deleted file mode 100644 index 5f153b790c6e..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/CallCodeTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class CallCodesTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stCallCodes"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/CallCreateCallCodeTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/CallCreateCallCodeTests.cs deleted file mode 100644 index ac4c6254c378..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/CallCreateCallCodeTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class CallCreateCallCodeTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stCallCreateCallCodeTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/CallDelegateCodesCallCodeHomesteadTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/CallDelegateCodesCallCodeHomesteadTests.cs deleted file mode 100644 index 000b41b60525..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/CallDelegateCodesCallCodeHomesteadTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class CallDelegateCodesCallCodeHomesteadTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stCallDelegateCodesCallCodeHomestead"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/CallDelegateCodesHomesteadTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/CallDelegateCodesHomesteadTests.cs deleted file mode 100644 index cd13e782b2fe..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/CallDelegateCodesHomesteadTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class CallDelegateCodesHomesteadTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stCallDelegateCodesHomestead"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/ChainIdTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/ChainIdTests.cs deleted file mode 100644 index eaba2c3f5216..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/ChainIdTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ChainIdTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stChainId"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/CodeCopyTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/CodeCopyTests.cs deleted file mode 100644 index 14fe7570491c..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/CodeCopyTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class CodeCopyTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stCodeCopyTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/CodeSizeLimitTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/CodeSizeLimitTests.cs deleted file mode 100644 index 211df7ec8172..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/CodeSizeLimitTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class CodeSizeLimitTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stCodeSizeLimit"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/Create2Tests.cs b/src/Nethermind/Ethereum.Blockchain.Test/Create2Tests.cs deleted file mode 100644 index f4fb73889246..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/Create2Tests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class Create2Tests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stCreate2"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/CreateTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/CreateTests.cs deleted file mode 100644 index fc8e60eee763..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/CreateTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class CreateTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - [Retry(3)] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stCreateTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/DelegateCallTestHomesteadTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/DelegateCallTestHomesteadTests.cs deleted file mode 100644 index 052065f73e28..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/DelegateCallTestHomesteadTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class DelegateCallTestHomesteadTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stDelegatecallTestHomestead"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/EIP150SingleCodeGasPricesTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/EIP150SingleCodeGasPricesTests.cs deleted file mode 100644 index 6b2be886136f..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/EIP150SingleCodeGasPricesTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class Eip150SingleCodeGasPricesTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stEIP150singleCodeGasPrices"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/EIP6110Tests.cs b/src/Nethermind/Ethereum.Blockchain.Test/EIP6110Tests.cs deleted file mode 100644 index 9a47a8a81f43..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/EIP6110Tests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class Eip6110Tests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stEIP6110"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/Eip150SpecificTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/Eip150SpecificTests.cs deleted file mode 100644 index 4776c9a67d41..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/Eip150SpecificTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class Eip150SpecificTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stEIP150Specific"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/Eip1559Tests.cs b/src/Nethermind/Ethereum.Blockchain.Test/Eip1559Tests.cs deleted file mode 100644 index 7a8131194b5e..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/Eip1559Tests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class Eip1559Tests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stEIP1559"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/Eip158SpecificTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/Eip158SpecificTests.cs deleted file mode 100644 index 481fe5d12f17..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/Eip158SpecificTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class Eip158SpecificTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stEIP158Specific"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/Eip2537Tests.cs b/src/Nethermind/Ethereum.Blockchain.Test/Eip2537Tests.cs deleted file mode 100644 index c1d1d2422242..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/Eip2537Tests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - // [TestFixture] - // [Parallelizable(ParallelScope.All)] - // public class Eip2537Tests : GeneralStateTestBase - // { - // [TestCaseSource(nameof(LoadTests))] - // public void Test(GeneralStateTest test) - // { - // Assert.That(RunTest(test).Pass, Is.True); - // } - // - // public static IEnumerable LoadTests() - // { - // var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stEIP2537"); - // return loader.LoadTests(); - // } - // } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/Eip2930Tests.cs b/src/Nethermind/Ethereum.Blockchain.Test/Eip2930Tests.cs deleted file mode 100644 index 942d391022b9..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/Eip2930Tests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class Eip2930Tests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stEIP2930"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/Eip3540Tests.cs b/src/Nethermind/Ethereum.Blockchain.Test/Eip3540Tests.cs deleted file mode 100644 index 8ffc83d518d4..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/Eip3540Tests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test; - -[TestFixture] -[Parallelizable(ParallelScope.All)] -public class Eip3540Tests : GeneralStateTestBase -{ - // ToDo: Eip3540 is in development phase on another branch. This will be uncommented after merging that branch. - - // [TestCaseSource(nameof(LoadTests))] - // public void Test(GeneralStateTest test) - // { - // Assert.That(RunTest(test).Pass, Is.True); - // } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stEIP3540"); - return loader.LoadTests(); - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/Eip3607Tests.cs b/src/Nethermind/Ethereum.Blockchain.Test/Eip3607Tests.cs deleted file mode 100644 index df29efdabbc2..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/Eip3607Tests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class Eip3607Tests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stEIP3607"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/Ethereum.Blockchain.Test.csproj b/src/Nethermind/Ethereum.Blockchain.Test/Ethereum.Blockchain.Test.csproj deleted file mode 100644 index 3c6f02d42f4d..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/Ethereum.Blockchain.Test.csproj +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - %(RecursiveDir)%(FileName)%(Extension) - PreserveNewest - - - %(RecursiveDir)%(FileName)%(Extension) - PreserveNewest - - - %(RecursiveDir)%(FileName)%(Extension) - PreserveNewest - - - %(RecursiveDir)%(FileName)%(Extension) - PreserveNewest - - - - - - - - - - - PreserveNewest - - - diff --git a/src/Nethermind/Ethereum.Blockchain.Test/ExampleTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/ExampleTests.cs deleted file mode 100644 index 46105ac95a38..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/ExampleTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ExampleTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stExample"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/ExtCodeHashTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/ExtCodeHashTests.cs deleted file mode 100644 index 29b1ada04bb5..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/ExtCodeHashTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ExtCodeHashTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stExtCodeHash"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/HomesteadSpecificTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/HomesteadSpecificTests.cs deleted file mode 100644 index 6bbe34598eca..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/HomesteadSpecificTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class HomesteadSpecificTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stHomesteadSpecific"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/InitCodeTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/InitCodeTests.cs deleted file mode 100644 index e1ca82e99474..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/InitCodeTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class InitCodeTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stInitCodeTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/LogTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/LogTests.cs deleted file mode 100644 index 725efc21d7b5..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/LogTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class LogTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stLogTests"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/MemExpandingEip150CallsTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/MemExpandingEip150CallsTests.cs deleted file mode 100644 index 482ef80690f6..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/MemExpandingEip150CallsTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class MemExpandingEip150CallsTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stMemExpandingEIP150Calls"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/MemoryStressTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/MemoryStressTests.cs deleted file mode 100644 index 317dd72eb377..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/MemoryStressTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class MemoryStressTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stMemoryStressTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/MemoryTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/MemoryTests.cs deleted file mode 100644 index 1888488cc641..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/MemoryTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class MemoryTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - [Retry(3)] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stMemoryTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/MetaTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/MetaTests.cs deleted file mode 100644 index 7636815f1a73..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/MetaTests.cs +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class MetaTests - { - private List excludesDirectories = new List() - { - "stEWASMTests", - "VMTests", - "Specs", - "runtimes", - "ref", - "TestFiles", - "Blockhash", - "stEIP2537", // ToDo Remove this after updating tests - "Data", - "Log", - "TestResults" - }; - - [Test] - public void All_categories_are_tested() - { - string[] directories = Directory.GetDirectories(AppDomain.CurrentDomain.BaseDirectory) - .Select(Path.GetFileName) - .ToArray(); - Type[] types = GetType().Assembly.GetTypes(); - List missingCategories = new List(); - foreach (string directory in directories) - { - string expectedTypeName = ExpectedTypeName(directory).Replace("-", ""); - Type type = types.SingleOrDefault(t => string.Equals(t.Name, expectedTypeName, StringComparison.InvariantCultureIgnoreCase)); - if (type is null && !excludesDirectories.Contains(directory)) - { - if (new DirectoryInfo(directory).GetFiles().Any(f => f.Name.Contains(".resources."))) - { - continue; - } - - missingCategories.Add(directory + " - " + expectedTypeName); - } - } - - foreach (string missing in missingCategories) - { - Console.WriteLine($"{missing} category is missing"); - } - - Assert.That(missingCategories.Count, Is.EqualTo(0)); - } - - private static string ExpectedTypeName(string directory) - { - string expectedTypeName = directory.Remove(0, 2); - if (!expectedTypeName.EndsWith("Tests")) - { - if (!expectedTypeName.EndsWith("Test")) - { - expectedTypeName += "Tests"; - } - else - { - expectedTypeName += "s"; - } - } - - if (directory.StartsWith("vm")) - { - return "Vm" + expectedTypeName; - } - - return expectedTypeName; - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/NonZeroCallTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/NonZeroCallTests.cs deleted file mode 100644 index d37ea0fae231..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/NonZeroCallTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class NonZeroCallsTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stNonZeroCallsTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/PreCompiledContracts2Tests.cs b/src/Nethermind/Ethereum.Blockchain.Test/PreCompiledContracts2Tests.cs deleted file mode 100644 index 27682f6c2ef4..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/PreCompiledContracts2Tests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class PreCompiledContracts2Tests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stPreCompiledContracts2"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/PreCompiledContractsTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/PreCompiledContractsTests.cs deleted file mode 100644 index ada9c2822623..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/PreCompiledContractsTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class PreCompiledContractsTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stPreCompiledContracts"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/QuadraticComplexityTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/QuadraticComplexityTests.cs deleted file mode 100644 index 56a7f3c50e78..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/QuadraticComplexityTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class QuadraticComplexityTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stQuadraticComplexityTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/Random2Tests.cs b/src/Nethermind/Ethereum.Blockchain.Test/Random2Tests.cs deleted file mode 100644 index b105f5850a02..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/Random2Tests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class Random2Tests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stRandom2"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/RandomTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/RandomTests.cs deleted file mode 100644 index 140194260051..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/RandomTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class RandomTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stRandom"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/RecursiveCreateTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/RecursiveCreateTests.cs deleted file mode 100644 index 69bc615c7566..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/RecursiveCreateTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class RecursiveCreateTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stRecursiveCreate"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/RefundTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/RefundTests.cs deleted file mode 100644 index b5a0bd5a403d..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/RefundTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class RefundTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stRefundTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/ReturnDataTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/ReturnDataTests.cs deleted file mode 100644 index 8be41631af14..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/ReturnDataTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ReturnDataTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stReturnDataTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/RevertTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/RevertTests.cs deleted file mode 100644 index 7f1fadb64585..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/RevertTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class RevertTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stRevertTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/SLoadTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/SLoadTests.cs deleted file mode 100644 index 42134fd5e502..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/SLoadTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class SLoadTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stSLoadTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/SStoreTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/SStoreTests.cs deleted file mode 100644 index 8723c5ae56c6..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/SStoreTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class SStoreTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stSStoreTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/SelfBalanceTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/SelfBalanceTests.cs deleted file mode 100644 index 64b6725d4c79..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/SelfBalanceTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class SelfBalanceTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stSelfBalance"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/ShiftTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/ShiftTests.cs deleted file mode 100644 index df1abbabb21a..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/ShiftTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ShiftTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stShift"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/SolidityTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/SolidityTests.cs deleted file mode 100644 index e8caaf346fd9..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/SolidityTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class SolidityTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - [Retry(3)] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stSolidityTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/SpecialTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/SpecialTests.cs deleted file mode 100644 index cfeeff65bddb..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/SpecialTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class SpecialTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests)), Retry(3)] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stSpecialTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/StackTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/StackTests.cs deleted file mode 100644 index 735c52983f01..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/StackTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class StackTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stStackTests"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/StaticCallTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/StaticCallTests.cs deleted file mode 100644 index 40dc6de1dab0..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/StaticCallTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class StaticCallTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stStaticCall"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/StaticFlagEnabledTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/StaticFlagEnabledTests.cs deleted file mode 100644 index 237b3f2de045..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/StaticFlagEnabledTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class StaticFlagEnabledTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stStaticFlagEnabled"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/SystemOperationsTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/SystemOperationsTests.cs deleted file mode 100644 index 8367c9adb6a4..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/SystemOperationsTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class SystemOperationsTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stSystemOperationsTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/TimeConsumingTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/TimeConsumingTests.cs deleted file mode 100644 index 52941d306db6..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/TimeConsumingTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class TimeConsumingTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stTimeConsuming"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/TransactionJsonTest.cs b/src/Nethermind/Ethereum.Blockchain.Test/TransactionJsonTest.cs deleted file mode 100644 index 1467ff8ed394..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/TransactionJsonTest.cs +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Ethereum.Test.Base; -using FluentAssertions; -using Nethermind.Core; -using Nethermind.Core.Test.Builders; -using Nethermind.Int256; -using Nethermind.Serialization.Json; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class TransactionJsonTest : GeneralStateTestBase - { - [Test] - public void Can_load_access_lists() - { - const string lists = - "{\"accessLists\": [[{\"address\": \"0x0001020304050607080900010203040506070809\", \"storageKeys\": [\"0x00\", \"0x01\"]}]]}"; - - EthereumJsonSerializer serializer = new EthereumJsonSerializer(); - TransactionJson txJson = serializer.Deserialize(lists); - txJson.SecretKey = TestItem.PrivateKeyA.KeyBytes; - txJson.Value = new UInt256[1]; - txJson.GasLimit = new long[1]; - txJson.Data = new byte[1][]; - txJson.AccessLists.Should().NotBeNull(); - txJson.AccessLists[0][0].Address.Should() - .BeEquivalentTo(new Address("0x0001020304050607080900010203040506070809")); - txJson.AccessLists[0][0].StorageKeys[1][0].Should().Be((byte)1); - - Transaction tx = JsonToEthereumTest.Convert(new PostStateJson { Indexes = new IndexesJson() }, txJson); - tx.AccessList.Should().NotBeNull(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/TransactionTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/TransactionTests.cs deleted file mode 100644 index ba200752fc23..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/TransactionTests.cs +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using System.Linq; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class TransactionTests : GeneralStateTestBase - { - // ToDo: This tests are passing on hive tests, but failing here - private readonly string[] ignored = - { - "HighGasPrice_d0g0v0", - "ValueOverflow" - }; - - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - if (ignored.Any(i => test.Name.Contains(i))) - { - return; - } - - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stTransactionTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/TransitionTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/TransitionTests.cs deleted file mode 100644 index 3ef886b011f3..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/TransitionTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class TransitionTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stTransitionTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/WalletTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/WalletTests.cs deleted file mode 100644 index e6e3fd31ca5f..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/WalletTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class WalletTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stWalletTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/ZeroCallsRevertTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/ZeroCallsRevertTests.cs deleted file mode 100644 index 68cea5976954..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/ZeroCallsRevertTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ZeroCallsRevertTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stZeroCallsRevert"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/ZeroCallsTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/ZeroCallsTests.cs deleted file mode 100644 index 096086050a54..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/ZeroCallsTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ZeroCallsTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stZeroCallsTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/ZeroKnowledge2Tests.cs b/src/Nethermind/Ethereum.Blockchain.Test/ZeroKnowledge2Tests.cs deleted file mode 100644 index 1fe846bf06a7..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/ZeroKnowledge2Tests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ZeroKnowledge2Tests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stZeroKnowledge2"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/ZeroKnowledgeTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/ZeroKnowledgeTests.cs deleted file mode 100644 index 5f06f881b7fa..000000000000 --- a/src/Nethermind/Ethereum.Blockchain.Test/ZeroKnowledgeTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Blockchain.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class ZeroKnowledgeTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stZeroKnowledge"); - return loader.LoadTests(); - } - } -} 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 478be5ef5b38..12073260d840 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.Difficulty.Test/DifficultyTestHexJson.cs b/src/Nethermind/Ethereum.Difficulty.Test/DifficultyTestHexJson.cs index fc37faf2caec..217872a15fd6 100644 --- a/src/Nethermind/Ethereum.Difficulty.Test/DifficultyTestHexJson.cs +++ b/src/Nethermind/Ethereum.Difficulty.Test/DifficultyTestHexJson.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Text.Json.Serialization; - namespace Ethereum.Difficulty.Test { public class DifficultyTestHexJson diff --git a/src/Nethermind/Ethereum.Difficulty.Test/DifficultyTestJson.cs b/src/Nethermind/Ethereum.Difficulty.Test/DifficultyTestJson.cs index 9e1108731cb0..fa85071ab548 100644 --- a/src/Nethermind/Ethereum.Difficulty.Test/DifficultyTestJson.cs +++ b/src/Nethermind/Ethereum.Difficulty.Test/DifficultyTestJson.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Text.Json.Serialization; - namespace Ethereum.Difficulty.Test { public class DifficultyTestJson diff --git a/src/Nethermind/Ethereum.HexPrefix.Test/HexPrefixTests.cs b/src/Nethermind/Ethereum.HexPrefix.Test/HexPrefixTests.cs index ceb9e13da4d8..e3c2ff50aa7a 100644 --- a/src/Nethermind/Ethereum.HexPrefix.Test/HexPrefixTests.cs +++ b/src/Nethermind/Ethereum.HexPrefix.Test/HexPrefixTests.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.Linq; -using System.Text.Json.Serialization; - using Ethereum.Test.Base; using Nethermind.Core.Extensions; @@ -16,7 +14,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.KeyAddress.Test/KeyAddressTests.cs b/src/Nethermind/Ethereum.KeyAddress.Test/KeyAddressTests.cs index 944ef338d97e..a74113fcabb7 100644 --- a/src/Nethermind/Ethereum.KeyAddress.Test/KeyAddressTests.cs +++ b/src/Nethermind/Ethereum.KeyAddress.Test/KeyAddressTests.cs @@ -12,7 +12,6 @@ using Nethermind.Core.Crypto; using Nethermind.Crypto; using Nethermind.Int256; -using Nethermind.Logging; using NUnit.Framework; namespace Ethereum.KeyAddress.Test diff --git a/src/Nethermind/Ethereum.KeyStore.Test/KeyStoreJsonTests.cs b/src/Nethermind/Ethereum.KeyStore.Test/KeyStoreJsonTests.cs index dd4baea543d8..bd26f5737899 100644 --- a/src/Nethermind/Ethereum.KeyStore.Test/KeyStoreJsonTests.cs +++ b/src/Nethermind/Ethereum.KeyStore.Test/KeyStoreJsonTests.cs @@ -9,7 +9,6 @@ using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Crypto; -using Nethermind.Db; using Nethermind.KeyStore; using Nethermind.KeyStore.Config; using Nethermind.Logging; diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/BlockGasLimitTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/BlockGasLimitTests.cs similarity index 93% rename from src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/BlockGasLimitTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/BlockGasLimitTests.cs index 6f853f109e67..0f12857feb34 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/BlockGasLimitTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/BlockGasLimitTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Block.Legacy.Test +namespace Ethereum.Legacy.Blockchain.Block.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/Ethereum.Blockchain.Block.Legacy.Test.csproj b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/Ethereum.Legacy.Blockchain.Block.Test.csproj similarity index 100% rename from src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/Ethereum.Blockchain.Block.Legacy.Test.csproj rename to src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/Ethereum.Legacy.Blockchain.Block.Test.csproj diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/ExploitTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/ExploitTests.cs similarity index 93% rename from src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/ExploitTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/ExploitTests.cs index d44297e91a30..d38563a0e824 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/ExploitTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/ExploitTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Block.Legacy.Test +namespace Ethereum.Legacy.Blockchain.Block.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/ForgedTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/ForgedTests.cs similarity index 93% rename from src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/ForgedTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/ForgedTests.cs index 37d1d92d87fd..6dbf8d1ea0db 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/ForgedTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/ForgedTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Block.Legacy.Test +namespace Ethereum.Legacy.Blockchain.Block.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/ForkStressTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/ForkStressTests.cs similarity index 93% rename from src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/ForkStressTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/ForkStressTests.cs index cb4f161c835f..2b0988961c60 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/ForkStressTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/ForkStressTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Block.Legacy.Test +namespace Ethereum.Legacy.Blockchain.Block.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/GasPricerTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/GasPricerTests.cs similarity index 93% rename from src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/GasPricerTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/GasPricerTests.cs index 98a068c5b0b2..d1a0c141532a 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/GasPricerTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/GasPricerTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Block.Legacy.Test +namespace Ethereum.Legacy.Blockchain.Block.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/InvalidHeaderTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/InvalidHeaderTests.cs similarity index 93% rename from src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/InvalidHeaderTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/InvalidHeaderTests.cs index 2975ea50acf8..c2eec5fa97e0 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/InvalidHeaderTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/InvalidHeaderTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Block.Legacy.Test +namespace Ethereum.Legacy.Blockchain.Block.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/MultiChainTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/MultiChainTests.cs similarity index 93% rename from src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/MultiChainTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/MultiChainTests.cs index 2c22bb0caf93..7dd8fcbb24cf 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/MultiChainTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/MultiChainTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Block.Legacy.Test +namespace Ethereum.Legacy.Blockchain.Block.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/RandomBlockchashTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/RandomBlockchashTests.cs similarity index 93% rename from src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/RandomBlockchashTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/RandomBlockchashTests.cs index cbb00404d352..b66985eb9dc9 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/RandomBlockchashTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/RandomBlockchashTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Block.Legacy.Test +namespace Ethereum.Legacy.Blockchain.Block.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/StateTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/StateTests.cs similarity index 93% rename from src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/StateTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/StateTests.cs index eaad23627cd4..6612de9c947d 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/StateTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/StateTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Block.Legacy.Test +namespace Ethereum.Legacy.Blockchain.Block.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/TotalDifficulty.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/TotalDifficulty.cs similarity index 93% rename from src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/TotalDifficulty.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/TotalDifficulty.cs index ca207702306c..9f6cb0b02d32 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/TotalDifficulty.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/TotalDifficulty.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Block.Legacy.Test +namespace Ethereum.Legacy.Blockchain.Block.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/UncleHeaderValidityTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/UncleHeaderValidityTests.cs similarity index 94% rename from src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/UncleHeaderValidityTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/UncleHeaderValidityTests.cs index bc6b9f69b6dd..a823bd88472b 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/UncleHeaderValidityTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/UncleHeaderValidityTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Block.Legacy.Test +namespace Ethereum.Legacy.Blockchain.Block.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/UncleSpecialTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/UncleSpecialTests.cs similarity index 93% rename from src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/UncleSpecialTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/UncleSpecialTests.cs index fb95513cba46..652e78a706ec 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/UncleSpecialTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/UncleSpecialTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Block.Legacy.Test +namespace Ethereum.Legacy.Blockchain.Block.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/UncleTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/UncleTests.cs similarity index 93% rename from src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/UncleTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/UncleTests.cs index 47bb941b4f99..0ac0f05d0698 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/UncleTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/UncleTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Block.Legacy.Test +namespace Ethereum.Legacy.Blockchain.Block.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/ValidBlockTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/ValidBlockTests.cs similarity index 93% rename from src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/ValidBlockTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/ValidBlockTests.cs index a78284dea94e..1768d20b5b5b 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/ValidBlockTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/ValidBlockTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Block.Legacy.Test +namespace Ethereum.Legacy.Blockchain.Block.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/WalletTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/WalletTests.cs similarity index 93% rename from src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/WalletTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/WalletTests.cs index f38e13ff1ae0..16140af1cae3 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/WalletTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Block.Test/WalletTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Block.Legacy.Test +namespace Ethereum.Legacy.Blockchain.Block.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ArgsZeroOneBalanceTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ArgsZeroOneBalanceTests.cs similarity index 85% rename from src/Nethermind/Ethereum.Blockchain.Legacy.Test/ArgsZeroOneBalanceTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Test/ArgsZeroOneBalanceTests.cs index 1dd1c0d509b6..73541bf71673 100644 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/ArgsZeroOneBalanceTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ArgsZeroOneBalanceTests.cs @@ -5,10 +5,10 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Legacy.Test; +namespace Ethereum.Legacy.Blockchain.Test; [Parallelizable(ParallelScope.All)] -public class ArgsZeroOneBalanaceTests : GeneralStateTestBase +public class ArgsZeroOneBalanceTests : GeneralStateTestBase { [TestCaseSource(nameof(LoadTests))] public void Test(GeneralStateTest test) diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/AttackTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/AttackTests.cs similarity index 93% rename from src/Nethermind/Ethereum.Blockchain.Legacy.Test/AttackTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Test/AttackTests.cs index c3c8450efeb5..4ebc59be7490 100644 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/AttackTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/AttackTests.cs @@ -5,7 +5,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Legacy.Test; +namespace Ethereum.Legacy.Blockchain.Test; [Parallelizable(ParallelScope.All)] public class AttackTests : GeneralStateTestBase diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/BadOpCodeTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/BadOpCodeTests.cs similarity index 93% rename from src/Nethermind/Ethereum.Blockchain.Legacy.Test/BadOpCodeTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Test/BadOpCodeTests.cs index f63fe28d09c7..e2f46d5ae2fc 100644 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/BadOpCodeTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/BadOpCodeTests.cs @@ -5,7 +5,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Legacy.Test; +namespace Ethereum.Legacy.Blockchain.Test; [Parallelizable(ParallelScope.All)] public class BadOpCodeTests : GeneralStateTestBase diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/BugsTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/BugsTests.cs similarity index 87% rename from src/Nethermind/Ethereum.Blockchain.Legacy.Test/BugsTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Test/BugsTests.cs index 8504d5b4c5f3..ab7f33f57a0e 100644 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/BugsTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/BugsTests.cs @@ -5,10 +5,10 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Legacy.Test; +namespace Ethereum.Legacy.Blockchain.Test; [Parallelizable(ParallelScope.All)] -public class BugsTets : GeneralStateTestBase +public class BugsTests : GeneralStateTestBase { [TestCaseSource(nameof(LoadTests))] public void Test(GeneralStateTest test) diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CallCodesTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CallCodesTests.cs similarity index 93% rename from src/Nethermind/Ethereum.Blockchain.Legacy.Test/CallCodesTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Test/CallCodesTests.cs index 3ccb9484b531..2092d621f225 100644 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/CallCodesTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CallCodesTests.cs @@ -5,7 +5,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Legacy.Test; +namespace Ethereum.Legacy.Blockchain.Test; [Parallelizable(ParallelScope.All)] public class CallCodesTests : GeneralStateTestBase diff --git a/src/Nethermind/Ethereum.Blockchain.Test/Eip3651WarmCoinbaseTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CallCreateCallCodeTests.cs similarity index 70% rename from src/Nethermind/Ethereum.Blockchain.Test/Eip3651WarmCoinbaseTests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Test/CallCreateCallCodeTests.cs index f23bedd762d3..b6dc53bdc258 100644 --- a/src/Nethermind/Ethereum.Blockchain.Test/Eip3651WarmCoinbaseTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CallCreateCallCodeTests.cs @@ -5,11 +5,11 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Test; +namespace Ethereum.Legacy.Blockchain.Test; [TestFixture] [Parallelizable(ParallelScope.All)] -public class Eip3651WarmCoinbaseTests : GeneralStateTestBase +public class CallCreateCallCodeTests : GeneralStateTestBase { [TestCaseSource(nameof(LoadTests))] public void Test(GeneralStateTest test) @@ -19,7 +19,7 @@ public void Test(GeneralStateTest test) public static IEnumerable LoadTests() { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stEIP3651-warmcoinbase"); + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stCallCreateCallCodeTest"); return loader.LoadTests(); } } diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CallDelegateCodesCallCodeHomesteadTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CallDelegateCodesCallCodeHomesteadTests.cs new file mode 100644 index 000000000000..25662ec7a4cf --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CallDelegateCodesCallCodeHomesteadTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class CallDelegateCodesCallCodeHomesteadTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stCallDelegateCodesCallCodeHomestead"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CallDelegateCodesHomesteadTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CallDelegateCodesHomesteadTests.cs new file mode 100644 index 000000000000..77c197352f62 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CallDelegateCodesHomesteadTests.cs @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class CallDelegateCodesHomesteadTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stCallDelegateCodesHomestead"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ChainIdTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ChainIdTests.cs new file mode 100644 index 000000000000..f7a3e6312819 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ChainIdTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class ChainIdTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stChainId"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ChangedEIP150Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ChangedEIP150Tests.cs new file mode 100644 index 000000000000..3d00fe7839a6 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ChangedEIP150Tests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class ChangedEIP150Tests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stChangedEIP150"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CodeCopyTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CodeCopyTests.cs new file mode 100644 index 000000000000..72f63ac92b7c --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CodeCopyTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class CodeCopyTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stCodeCopyTest"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CodeSizeLimitTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CodeSizeLimitTests.cs new file mode 100644 index 000000000000..9466e78327bf --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CodeSizeLimitTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class CodeSizeLimitTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stCodeSizeLimit"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/Create2Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/Create2Tests.cs new file mode 100644 index 000000000000..10fe821204c7 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/Create2Tests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class Create2Tests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stCreate2"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CreateTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CreateTests.cs new file mode 100644 index 000000000000..ccf9a43d41c9 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/CreateTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class CreateTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stCreateTest"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/DelegateCallTestHomesteadTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/DelegateCallTestHomesteadTests.cs new file mode 100644 index 000000000000..091f762bf2c1 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/DelegateCallTestHomesteadTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class DelegateCallTestHomesteadTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stDelegatecallTestHomestead"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP1153Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP1153Tests.cs new file mode 100644 index 000000000000..317726f3ac9a --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP1153Tests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class EIP1153Tests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP1153"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/Eip1153Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP1153transientStorageTests.cs similarity index 78% rename from src/Nethermind/Ethereum.Blockchain.Test/Eip1153Tests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP1153transientStorageTests.cs index a0d9ec11c551..a90df15dd407 100644 --- a/src/Nethermind/Ethereum.Blockchain.Test/Eip1153Tests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP1153transientStorageTests.cs @@ -5,7 +5,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Test; +namespace Ethereum.Legacy.Blockchain.Test; [TestFixture] [Parallelizable(ParallelScope.All)] @@ -19,7 +19,7 @@ public void Test(GeneralStateTest test) public static IEnumerable LoadTests() { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stEIP1153-transientStorage"); + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP1153-transientStorage"); return loader.LoadTests(); } } diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP150SingleCodeGasPricesTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP150SingleCodeGasPricesTests.cs new file mode 100644 index 000000000000..bb9f26da51a7 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP150SingleCodeGasPricesTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class EIP150SingleCodeGasPricesTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP150singleCodeGasPrices"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP1559Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP1559Tests.cs new file mode 100644 index 000000000000..4ed518148d66 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP1559Tests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class EIP1559Tests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP1559"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP2930Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP2930Tests.cs new file mode 100644 index 000000000000..3b0aeb5da6a9 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP2930Tests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class EIP2930Tests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP2930"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3607Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3607Tests.cs new file mode 100644 index 000000000000..1dff5eccd76b --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3607Tests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class EIP3607Tests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP3607"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3651Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3651Tests.cs new file mode 100644 index 000000000000..b3fb35a685af --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3651Tests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class EIP3651Tests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP3651"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3651warmcoinbaseTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3651warmcoinbaseTests.cs new file mode 100644 index 000000000000..d65b7fc7e713 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3651warmcoinbaseTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Blockchain.Legacy.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class EIP3651WarmCoinbaseTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP3651-warmcoinbase"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3855Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3855Tests.cs new file mode 100644 index 000000000000..21dc3ab36ea8 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3855Tests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class EIP3855Tests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP3855"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3855push0Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3855push0Tests.cs new file mode 100644 index 000000000000..0664789a0407 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3855push0Tests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Blockchain.Legacy.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class EIP3855push0Tests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP3855-push0"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3860Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3860Tests.cs new file mode 100644 index 000000000000..e1b713a72159 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3860Tests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class EIP3860Tests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP3860"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3860limitmeterinitcodeTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3860limitmeterinitcodeTests.cs new file mode 100644 index 000000000000..b0b53519322b --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3860limitmeterinitcodeTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Blockchain.Legacy.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class EIP3860limitmeterinitcodeTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP3860-limitmeterinitcode"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP4844Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP4844Tests.cs new file mode 100644 index 000000000000..83a42a8919a1 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP4844Tests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class EIP4844Tests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP4844"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/Eip4844Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP4844blobtransactionsTests.cs similarity index 78% rename from src/Nethermind/Ethereum.Blockchain.Test/Eip4844Tests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP4844blobtransactionsTests.cs index 81935e1c6c08..407915ec2b21 100644 --- a/src/Nethermind/Ethereum.Blockchain.Test/Eip4844Tests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP4844blobtransactionsTests.cs @@ -5,7 +5,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Test; +namespace Ethereum.Blockchain.Legacy.Test; [TestFixture] [Parallelizable(ParallelScope.All)] @@ -19,7 +19,7 @@ public void Test(GeneralStateTest test) public static IEnumerable LoadTests() { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stEIP4844-blobtransactions"); + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP4844-blobtransactions"); return loader.LoadTests(); } } diff --git a/src/Nethermind/Ethereum.Blockchain.Test/Eip5656Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP5656MCOPYTests.cs similarity index 79% rename from src/Nethermind/Ethereum.Blockchain.Test/Eip5656Tests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP5656MCOPYTests.cs index 5cf5ecd6f7b4..02bb16400ccb 100644 --- a/src/Nethermind/Ethereum.Blockchain.Test/Eip5656Tests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP5656MCOPYTests.cs @@ -5,7 +5,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Test; +namespace Ethereum.Blockchain.Legacy.Test; [TestFixture] [Parallelizable(ParallelScope.All)] @@ -19,7 +19,7 @@ public void Test(GeneralStateTest test) public static IEnumerable LoadTests() { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stEIP5656-MCOPY"); + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP5656-MCOPY"); return loader.LoadTests(); } } diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP5656Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP5656Tests.cs new file mode 100644 index 000000000000..2fb14f29f097 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP5656Tests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class EIP5656Tests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP5656"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/Eip150SpecificTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/Eip150SpecificTests.cs new file mode 100644 index 000000000000..7ec86298dc9a --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/Eip150SpecificTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class Eip150SpecificTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP150Specific"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/Eip158SpecificTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/Eip158SpecificTests.cs new file mode 100644 index 000000000000..d483bd2a50bb --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/Eip158SpecificTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class Eip158SpecificTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stEIP158Specific"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/Ethereum.Legacy.Blockchain.Test.csproj b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/Ethereum.Legacy.Blockchain.Test.csproj new file mode 100644 index 000000000000..11d6c0a0f50d --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/Ethereum.Legacy.Blockchain.Test.csproj @@ -0,0 +1,24 @@ + + + + + + + %(RecursiveDir)%(FileName)%(Extension) + PreserveNewest + + + %(RecursiveDir)%(FileName)%(Extension) + PreserveNewest + + + %(RecursiveDir)%(FileName)%(Extension) + PreserveNewest + + + + + + + + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ExampleTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ExampleTests.cs new file mode 100644 index 000000000000..ec25b97acbb9 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ExampleTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class ExampleTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stExample"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ExtCodeHashTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ExtCodeHashTests.cs new file mode 100644 index 000000000000..3957461b26b7 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ExtCodeHashTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class ExtCodeHashTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stExtCodeHash"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/HomesteadSpecificTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/HomesteadSpecificTests.cs new file mode 100644 index 000000000000..ee0f3361301a --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/HomesteadSpecificTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class HomesteadSpecificTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stHomesteadSpecific"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/InitCodeTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/InitCodeTests.cs new file mode 100644 index 000000000000..62bb11476db6 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/InitCodeTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class InitCodeTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stInitCodeTest"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/LogTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/LogTests.cs new file mode 100644 index 000000000000..ba4f228944b6 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/LogTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class LogTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stLogTests"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/MemExpandingEip150CallsTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/MemExpandingEip150CallsTests.cs new file mode 100644 index 000000000000..e2532ed67de5 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/MemExpandingEip150CallsTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class MemExpandingEip150CallsTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stMemExpandingEIP150Calls"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/MemoryStressTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/MemoryStressTests.cs new file mode 100644 index 000000000000..21d35dd2d638 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/MemoryStressTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class MemoryStressTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stMemoryStressTest"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/MemoryTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/MemoryTests.cs new file mode 100644 index 000000000000..3501593becb3 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/MemoryTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class MemoryTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stMemoryTest"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/MetaTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/MetaTests.cs new file mode 100644 index 000000000000..a88473f572ca --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/MetaTests.cs @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class MetaTests +{ + private readonly List excludesDirectories = + [ + "stEWASMTests", + "VMTests", + "Specs", + "runtimes", + "ref", + "TestFiles", + "Blockhash", + "Data", + "Log", + "TestResults", + "db" + ]; + + [Test] + public void All_categories_are_tested() + { + string[] directories = Directory.GetDirectories(AppDomain.CurrentDomain.BaseDirectory) + .Select(Path.GetFileName) + .ToArray(); + Type[] types = GetType().Assembly.GetTypes(); + List missingCategories = new List(); + foreach (string directory in directories) + { + string expectedTypeName = ExpectedTypeName(directory).Replace("-", ""); + Type type = types.SingleOrDefault(t => string.Equals(t.Name, expectedTypeName, StringComparison.InvariantCultureIgnoreCase)); + if (type is null && !excludesDirectories.Contains(directory)) + { + if (new DirectoryInfo(directory).GetFiles().Any(f => f.Name.Contains(".resources."))) + continue; + + missingCategories.Add(directory + " - " + expectedTypeName); + } + } + + foreach (string missing in missingCategories) + { + Console.WriteLine($"{missing} category is missing"); + } + + Assert.That(missingCategories.Count, Is.EqualTo(0)); + } + + private static string ExpectedTypeName(string directory) + { + string expectedTypeName = directory.Remove(0, 2); + if (!expectedTypeName.EndsWith("Tests")) + { + if (!expectedTypeName.EndsWith("Test")) + { + expectedTypeName += "Tests"; + } + else + { + expectedTypeName += "s"; + } + } + + if (directory.StartsWith("vm")) + return "Vm" + expectedTypeName; + + return expectedTypeName; + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/NonZeroCallsTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/NonZeroCallsTests.cs new file mode 100644 index 000000000000..6c2f75d3bf1d --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/NonZeroCallsTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class NonZeroCallsTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stNonZeroCallsTest"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/PreCompiledContracts2Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/PreCompiledContracts2Tests.cs new file mode 100644 index 000000000000..6f8199287cc2 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/PreCompiledContracts2Tests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class PreCompiledContracts2Tests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stPreCompiledContracts2"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/PreCompiledContractsTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/PreCompiledContractsTests.cs new file mode 100644 index 000000000000..9496c69c86e7 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/PreCompiledContractsTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class PreCompiledContractsTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stPreCompiledContracts"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/QuadraticComplexityTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/QuadraticComplexityTests.cs new file mode 100644 index 000000000000..18d0a424457b --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/QuadraticComplexityTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class QuadraticComplexityTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stQuadraticComplexityTest"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/Random2Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/Random2Tests.cs new file mode 100644 index 000000000000..0b9438f55ef2 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/Random2Tests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class Random2Tests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stRandom2"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/RandomTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/RandomTests.cs new file mode 100644 index 000000000000..87a08b7798c8 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/RandomTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class RandomTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stRandom"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/RecursiveCreateTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/RecursiveCreateTests.cs new file mode 100644 index 000000000000..31081732de2b --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/RecursiveCreateTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class RecursiveCreateTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stRecursiveCreate"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/RefundTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/RefundTests.cs new file mode 100644 index 000000000000..0deafc7ed83e --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/RefundTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class RefundTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stRefundTest"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ReturnDataTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ReturnDataTests.cs new file mode 100644 index 000000000000..835782b50708 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ReturnDataTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class ReturnDataTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stReturnDataTest"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/RevertTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/RevertTests.cs new file mode 100644 index 000000000000..82d36edeeb98 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/RevertTests.cs @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Linq; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class RevertTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stRevertTest"); + IEnumerable tests = loader.LoadTests(); + HashSet ignoredTests = new() + { + "RevertPrecompiledTouch", + }; + + return tests.Where(t => !ignoredTests.Any(pattern => t.Name.Contains(pattern))); + } +} + diff --git a/src/Nethermind/Ethereum.Blockchain.Test/Eip3855Push0Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SLoadTestTests.cs similarity index 70% rename from src/Nethermind/Ethereum.Blockchain.Test/Eip3855Push0Tests.cs rename to src/Nethermind/Ethereum.Legacy.Blockchain.Test/SLoadTestTests.cs index 281c0c6d4fb7..32dc5d064a14 100644 --- a/src/Nethermind/Ethereum.Blockchain.Test/Eip3855Push0Tests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SLoadTestTests.cs @@ -5,11 +5,10 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Test; +namespace Ethereum.Legacy.Blockchain.Test; -[TestFixture] [Parallelizable(ParallelScope.All)] -public class Eip3855Push0Tests : GeneralStateTestBase +public class SLoadTestTests : GeneralStateTestBase { [TestCaseSource(nameof(LoadTests))] public void Test(GeneralStateTest test) @@ -19,7 +18,7 @@ public void Test(GeneralStateTest test) public static IEnumerable LoadTests() { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "stEIP3855-push0"); + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stSLoadTest"); return loader.LoadTests(); } } diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SLoadTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SLoadTests.cs new file mode 100644 index 000000000000..f2a986d5a9df --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SLoadTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Blockchain.Legacy.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class SLoadTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stSLoadTest"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SStoreTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SStoreTests.cs new file mode 100644 index 000000000000..efca5b0a5427 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SStoreTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class SStoreTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stSStoreTest"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SelfBalanceTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SelfBalanceTests.cs new file mode 100644 index 000000000000..08cc732dcc3e --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SelfBalanceTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class SelfBalanceTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stSelfBalance"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ShiftTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ShiftTests.cs new file mode 100644 index 000000000000..556a452dd302 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ShiftTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class ShiftTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stShift"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SolidityTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SolidityTests.cs new file mode 100644 index 000000000000..7352761d911e --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SolidityTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class SolidityTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stSolidityTest"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SpecialTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SpecialTests.cs new file mode 100644 index 000000000000..ea9354c0d61a --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SpecialTests.cs @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class SpecialTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + [Retry(3)] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stSpecialTest"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/StackTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/StackTests.cs new file mode 100644 index 000000000000..2c19e824dec7 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/StackTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class StackTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stStackTests"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/StaticCallTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/StaticCallTests.cs new file mode 100644 index 000000000000..87681c3d153c --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/StaticCallTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class StaticCallTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stStaticCall"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/StaticFlagEnabledTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/StaticFlagEnabledTests.cs new file mode 100644 index 000000000000..c494fcce2fe5 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/StaticFlagEnabledTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class StaticFlagEnabledTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stStaticFlagEnabled"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SystemOperationsTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SystemOperationsTests.cs new file mode 100644 index 000000000000..ccaa3d0d4d67 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/SystemOperationsTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class SystemOperationsTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stSystemOperationsTest"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/TimeConsumingTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/TimeConsumingTests.cs new file mode 100644 index 000000000000..d5533c02d03c --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/TimeConsumingTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class TimeConsumingTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stTimeConsuming"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/TransactionTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/TransactionTests.cs new file mode 100644 index 000000000000..06351b4d008b --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/TransactionTests.cs @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Linq; +using Ethereum.Test.Base; +using Ethereum.Test.Base.Interfaces; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class TransactionTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + try + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stTransactionTest"); + // We don't handle invalid transaction's RLP cases, that are loaded as FailedToLoadTest + return loader.LoadTests().OfType(); + } + catch + { + throw; + } + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/TransitionTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/TransitionTests.cs new file mode 100644 index 000000000000..cc24e621c1a3 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/TransitionTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class TransitionTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stTransitionTest"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/WalletTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/WalletTests.cs new file mode 100644 index 000000000000..9e216c9d2bf9 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/WalletTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class WalletTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stWalletTest"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ZeroCallsRevertTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ZeroCallsRevertTests.cs new file mode 100644 index 000000000000..b74345680f47 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ZeroCallsRevertTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class ZeroCallsRevertTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stZeroCallsRevert"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ZeroCallsTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ZeroCallsTests.cs new file mode 100644 index 000000000000..46ffd6c4db2a --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ZeroCallsTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class ZeroCallsTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stZeroCallsTest"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ZeroKnowledge2Tests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ZeroKnowledge2Tests.cs new file mode 100644 index 000000000000..e07f237c5a47 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ZeroKnowledge2Tests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class ZeroKnowledge2Tests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stZeroKnowledge2"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ZeroKnowledgeTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ZeroKnowledgeTests.cs new file mode 100644 index 000000000000..f15cb484eb99 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/ZeroKnowledgeTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class ZeroKnowledgeTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + Assert.That(RunTest(test).Pass, Is.True); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stZeroKnowledge"); + return loader.LoadTests(); + } +} + diff --git a/src/Nethermind/Ethereum.Legacy.Transition.Test/BerlinToLondonTests.cs b/src/Nethermind/Ethereum.Legacy.Transition.Test/BerlinToLondonTests.cs new file mode 100644 index 000000000000..835f19a5c038 --- /dev/null +++ b/src/Nethermind/Ethereum.Legacy.Transition.Test/BerlinToLondonTests.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Threading.Tasks; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Legacy.Transition.Test; + +[TestFixture] +[Parallelizable(ParallelScope.None)] +public class BerlinToLondonTests : BlockchainTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public async Task Test(BlockchainTest test) + { + await RunTest(test); + } + + public static IEnumerable LoadTests() + { + var loader = new TestsSourceLoader(new LoadBlockchainTestsStrategy(), "bcBerlinToLondon"); + return loader.LoadTests(); + } +} diff --git a/src/Nethermind/Ethereum.Transition.Test/ByzantiumToConstantinopleFixTests.cs b/src/Nethermind/Ethereum.Legacy.Transition.Test/ByzantiumToConstantinopleFixTests.cs similarity index 94% rename from src/Nethermind/Ethereum.Transition.Test/ByzantiumToConstantinopleFixTests.cs rename to src/Nethermind/Ethereum.Legacy.Transition.Test/ByzantiumToConstantinopleFixTests.cs index ba743f0ee527..ddad01efea8c 100644 --- a/src/Nethermind/Ethereum.Transition.Test/ByzantiumToConstantinopleFixTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Transition.Test/ByzantiumToConstantinopleFixTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Transition.Test +namespace Ethereum.Legacy.Transition.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Transition.Test/Eip158ToByzantiumTests.cs b/src/Nethermind/Ethereum.Legacy.Transition.Test/Eip158ToByzantiumTests.cs similarity index 94% rename from src/Nethermind/Ethereum.Transition.Test/Eip158ToByzantiumTests.cs rename to src/Nethermind/Ethereum.Legacy.Transition.Test/Eip158ToByzantiumTests.cs index eb0c0dd46b8f..5566a3bffc7c 100644 --- a/src/Nethermind/Ethereum.Transition.Test/Eip158ToByzantiumTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Transition.Test/Eip158ToByzantiumTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Transition.Test +namespace Ethereum.Legacy.Transition.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Transition.Test/Ethereum.Transition.Test.csproj b/src/Nethermind/Ethereum.Legacy.Transition.Test/Ethereum.Legacy.Transition.Test.csproj similarity index 100% rename from src/Nethermind/Ethereum.Transition.Test/Ethereum.Transition.Test.csproj rename to src/Nethermind/Ethereum.Legacy.Transition.Test/Ethereum.Legacy.Transition.Test.csproj diff --git a/src/Nethermind/Ethereum.Transition.Test/FrontierToHomesteadTests.cs b/src/Nethermind/Ethereum.Legacy.Transition.Test/FrontierToHomesteadTests.cs similarity index 94% rename from src/Nethermind/Ethereum.Transition.Test/FrontierToHomesteadTests.cs rename to src/Nethermind/Ethereum.Legacy.Transition.Test/FrontierToHomesteadTests.cs index 443fe9e3c27d..62167d3db08e 100644 --- a/src/Nethermind/Ethereum.Transition.Test/FrontierToHomesteadTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Transition.Test/FrontierToHomesteadTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Transition.Test +namespace Ethereum.Legacy.Transition.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Transition.Test/HomesteadToDaoTests.cs b/src/Nethermind/Ethereum.Legacy.Transition.Test/HomesteadToDaoTests.cs similarity index 94% rename from src/Nethermind/Ethereum.Transition.Test/HomesteadToDaoTests.cs rename to src/Nethermind/Ethereum.Legacy.Transition.Test/HomesteadToDaoTests.cs index 9451f5ff36fc..0a213c05a7e7 100644 --- a/src/Nethermind/Ethereum.Transition.Test/HomesteadToDaoTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Transition.Test/HomesteadToDaoTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Transition.Test +namespace Ethereum.Legacy.Transition.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Transition.Test/HomesteadToEip150Tests.cs b/src/Nethermind/Ethereum.Legacy.Transition.Test/HomesteadToEip150Tests.cs similarity index 94% rename from src/Nethermind/Ethereum.Transition.Test/HomesteadToEip150Tests.cs rename to src/Nethermind/Ethereum.Legacy.Transition.Test/HomesteadToEip150Tests.cs index f244d82121ca..54295c08cfd5 100644 --- a/src/Nethermind/Ethereum.Transition.Test/HomesteadToEip150Tests.cs +++ b/src/Nethermind/Ethereum.Legacy.Transition.Test/HomesteadToEip150Tests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Transition.Test +namespace Ethereum.Legacy.Transition.Test { [TestFixture] [Parallelizable(ParallelScope.All)] diff --git a/src/Nethermind/Ethereum.Transition.Test/MergeToShanghaiTests.cs b/src/Nethermind/Ethereum.Legacy.Transition.Test/MergeToShanghaiTests.cs similarity index 79% rename from src/Nethermind/Ethereum.Transition.Test/MergeToShanghaiTests.cs rename to src/Nethermind/Ethereum.Legacy.Transition.Test/MergeToShanghaiTests.cs index f0fb44ba22eb..2aead08dbb68 100644 --- a/src/Nethermind/Ethereum.Transition.Test/MergeToShanghaiTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Transition.Test/MergeToShanghaiTests.cs @@ -6,7 +6,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Transition.Test; +namespace Ethereum.Legacy.Transition.Test; [TestFixture] [Parallelizable(ParallelScope.All)] @@ -20,7 +20,7 @@ public async Task Test(BlockchainTest test) public static IEnumerable LoadTests() { - var loader = new TestsSourceLoader(new LoadBlockchainTestsStrategy(), "bcMergeToShanghai"); + var loader = new TestsSourceLoader(new LoadLegacyBlockchainTestsStrategy(), "bcMergeToShanghai"); return loader.LoadTests(); } } diff --git a/src/Nethermind/Ethereum.Transition.Test/MetaTests.cs b/src/Nethermind/Ethereum.Legacy.Transition.Test/MetaTests.cs similarity index 96% rename from src/Nethermind/Ethereum.Transition.Test/MetaTests.cs rename to src/Nethermind/Ethereum.Legacy.Transition.Test/MetaTests.cs index 73318524654a..8341e536b6fa 100644 --- a/src/Nethermind/Ethereum.Transition.Test/MetaTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Transition.Test/MetaTests.cs @@ -7,7 +7,7 @@ using System.Linq; using NUnit.Framework; -namespace Ethereum.Transition.Test +namespace Ethereum.Legacy.Transition.Test { [TestFixture] [Parallelizable(ParallelScope.All)] @@ -26,9 +26,7 @@ public void All_categories_are_tested() { string expectedTypeName = ExpectedTypeName(directory); if (types.All(t => !string.Equals(t.Name, expectedTypeName, StringComparison.InvariantCultureIgnoreCase))) - { missingCategories.Add(directory); - } } foreach (string missing in missingCategories) diff --git a/src/Nethermind/Ethereum.VM.Test/AbiTests.cs b/src/Nethermind/Ethereum.Legacy.VM.Test/AbiTests.cs similarity index 97% rename from src/Nethermind/Ethereum.VM.Test/AbiTests.cs rename to src/Nethermind/Ethereum.Legacy.VM.Test/AbiTests.cs index 284e159fe91a..32a1caabc2df 100644 --- a/src/Nethermind/Ethereum.VM.Test/AbiTests.cs +++ b/src/Nethermind/Ethereum.Legacy.VM.Test/AbiTests.cs @@ -8,7 +8,7 @@ using Nethermind.Core.Extensions; using NUnit.Framework; -namespace Ethereum.VM.Test; +namespace Ethereum.Legacy.VM.Test; internal class AbiTests { @@ -28,7 +28,7 @@ private static AbiType ToAbiType(string typeName) private static AbiTest Convert(string name, AbiTestJson testJson) { - AbiTest test = new AbiTest(); + AbiTest test = new(); test.Name = name; test.Result = Bytes.FromHexString(testJson.Result); test.Types = testJson.Types.Select(ToAbiType).ToArray(); diff --git a/src/Nethermind/Ethereum.VM.Test/ArithmeticTests.cs b/src/Nethermind/Ethereum.Legacy.VM.Test/ArithmeticTests.cs similarity index 80% rename from src/Nethermind/Ethereum.VM.Test/ArithmeticTests.cs rename to src/Nethermind/Ethereum.Legacy.VM.Test/ArithmeticTests.cs index 692690dba929..6afb75cabdae 100644 --- a/src/Nethermind/Ethereum.VM.Test/ArithmeticTests.cs +++ b/src/Nethermind/Ethereum.Legacy.VM.Test/ArithmeticTests.cs @@ -5,7 +5,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.VM.Test +namespace Ethereum.Legacy.VM.Test { [TestFixture] [Parallelizable(ParallelScope.All)] @@ -19,7 +19,7 @@ public void Test(GeneralStateTest test) public static IEnumerable LoadTests() { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "vmArithmeticTest"); + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "VMTests", "vmArithmeticTest"); return loader.LoadTests(); } } diff --git a/src/Nethermind/Ethereum.VM.Test/BitwiseLogicOperationTests.cs b/src/Nethermind/Ethereum.Legacy.VM.Test/BitwiseLogicOperationTests.cs similarity index 80% rename from src/Nethermind/Ethereum.VM.Test/BitwiseLogicOperationTests.cs rename to src/Nethermind/Ethereum.Legacy.VM.Test/BitwiseLogicOperationTests.cs index b128a507861c..ce7605275998 100644 --- a/src/Nethermind/Ethereum.VM.Test/BitwiseLogicOperationTests.cs +++ b/src/Nethermind/Ethereum.Legacy.VM.Test/BitwiseLogicOperationTests.cs @@ -5,7 +5,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.VM.Test +namespace Ethereum.Legacy.VM.Test { [TestFixture] [Parallelizable(ParallelScope.All)] @@ -19,7 +19,7 @@ public void Test(GeneralStateTest test) public static IEnumerable LoadTests() { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "vmBitwiseLogicOperation"); + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "vmBitwiseLogicOperation"); return loader.LoadTests(); } } diff --git a/src/Nethermind/Ethereum.VM.Test/Ethereum.VM.Test.csproj b/src/Nethermind/Ethereum.Legacy.VM.Test/Ethereum.Legacy.VM.Test.csproj similarity index 87% rename from src/Nethermind/Ethereum.VM.Test/Ethereum.VM.Test.csproj rename to src/Nethermind/Ethereum.Legacy.VM.Test/Ethereum.Legacy.VM.Test.csproj index 6f57a0eb9757..262db6d8fa6d 100644 --- a/src/Nethermind/Ethereum.VM.Test/Ethereum.VM.Test.csproj +++ b/src/Nethermind/Ethereum.Legacy.VM.Test/Ethereum.Legacy.VM.Test.csproj @@ -10,7 +10,7 @@ %(RecursiveDir)%(FileName)%(Extension) PreserveNewest - + %(RecursiveDir)%(FileName)%(Extension) PreserveNewest diff --git a/src/Nethermind/Ethereum.VM.Test/IOAndFlowOperationsTests.cs b/src/Nethermind/Ethereum.Legacy.VM.Test/IOAndFlowOperationsTests.cs similarity index 81% rename from src/Nethermind/Ethereum.VM.Test/IOAndFlowOperationsTests.cs rename to src/Nethermind/Ethereum.Legacy.VM.Test/IOAndFlowOperationsTests.cs index 1f24fb7077cc..260a281c5ed8 100644 --- a/src/Nethermind/Ethereum.VM.Test/IOAndFlowOperationsTests.cs +++ b/src/Nethermind/Ethereum.Legacy.VM.Test/IOAndFlowOperationsTests.cs @@ -5,7 +5,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.VM.Test +namespace Ethereum.Legacy.VM.Test { [TestFixture] [Parallelizable(ParallelScope.All)] @@ -20,7 +20,7 @@ public void Test(GeneralStateTest test) public static IEnumerable LoadTests() { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "vmIOAndFlowOperations"); + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "vmIOAndFlowOperations"); return loader.LoadTests(); } } diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/LogTests.cs b/src/Nethermind/Ethereum.Legacy.VM.Test/LogTests.cs similarity index 89% rename from src/Nethermind/Ethereum.Blockchain.Legacy.Test/LogTests.cs rename to src/Nethermind/Ethereum.Legacy.VM.Test/LogTests.cs index 650a03675f21..b7bd487a2c40 100644 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/LogTests.cs +++ b/src/Nethermind/Ethereum.Legacy.VM.Test/LogTests.cs @@ -5,7 +5,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.Blockchain.Legacy.Test +namespace Ethereum.Legacy.VM.Test { [TestFixture] [Parallelizable(ParallelScope.All)] @@ -19,9 +19,8 @@ public void Test(GeneralStateTest test) public static IEnumerable LoadTests() { - var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "stLogTests"); + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "vmLogTest"); return loader.LoadTests(); } } } - diff --git a/src/Nethermind/Ethereum.VM.Test/MetaTests.cs b/src/Nethermind/Ethereum.Legacy.VM.Test/MetaTests.cs similarity index 96% rename from src/Nethermind/Ethereum.VM.Test/MetaTests.cs rename to src/Nethermind/Ethereum.Legacy.VM.Test/MetaTests.cs index 4181e7f42f0d..de21eef49b49 100644 --- a/src/Nethermind/Ethereum.VM.Test/MetaTests.cs +++ b/src/Nethermind/Ethereum.Legacy.VM.Test/MetaTests.cs @@ -7,7 +7,7 @@ using System.Linq; using NUnit.Framework; -namespace Ethereum.VM.Test +namespace Ethereum.Legacy.VM.Test { [TestFixture] [Parallelizable(ParallelScope.All)] @@ -30,9 +30,7 @@ public void All_categories_are_tested() if (types.All(t => !string.Equals(t.Name, expectedTypeName, StringComparison.InvariantCultureIgnoreCase))) { if (new DirectoryInfo(directory).GetFiles().Any(f => f.Name.Contains(".resources."))) - { continue; - } missingCategories.Add(directory + " expected " + expectedTypeName); } diff --git a/src/Nethermind/Ethereum.VM.Test/PerformanceTests.cs b/src/Nethermind/Ethereum.Legacy.VM.Test/PerformanceTests.cs similarity index 82% rename from src/Nethermind/Ethereum.VM.Test/PerformanceTests.cs rename to src/Nethermind/Ethereum.Legacy.VM.Test/PerformanceTests.cs index c9b9dc637a50..84ee86b5a860 100644 --- a/src/Nethermind/Ethereum.VM.Test/PerformanceTests.cs +++ b/src/Nethermind/Ethereum.Legacy.VM.Test/PerformanceTests.cs @@ -5,7 +5,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.VM.Test +namespace Ethereum.Legacy.VM.Test { [TestFixture] [Parallelizable(ParallelScope.All)] @@ -21,7 +21,7 @@ public void Test(GeneralStateTest test) public static IEnumerable LoadTests() { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "vmPerformance"); + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "vmPerformance"); return loader.LoadTests(); } } diff --git a/src/Nethermind/Ethereum.VM.Test/Tests.cs b/src/Nethermind/Ethereum.Legacy.VM.Test/Tests.cs similarity index 82% rename from src/Nethermind/Ethereum.VM.Test/Tests.cs rename to src/Nethermind/Ethereum.Legacy.VM.Test/Tests.cs index 19a1d8f45def..51a22ccbc3aa 100644 --- a/src/Nethermind/Ethereum.VM.Test/Tests.cs +++ b/src/Nethermind/Ethereum.Legacy.VM.Test/Tests.cs @@ -5,7 +5,7 @@ using Ethereum.Test.Base; using NUnit.Framework; -namespace Ethereum.VM.Test +namespace Ethereum.Legacy.VM.Test { [TestFixture] [Parallelizable(ParallelScope.All)] @@ -20,7 +20,7 @@ public void Test(GeneralStateTest test) public static IEnumerable LoadTests() { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "vmTests"); + var loader = new TestsSourceLoader(new LoadLegacyGeneralStateTestsStrategy(), "vmTests"); return loader.LoadTests(); } } diff --git a/src/Nethermind/Ethereum.Rlp.Test/RlpTests.cs b/src/Nethermind/Ethereum.Rlp.Test/RlpTests.cs index 6185329ed454..29adbae0102a 100644 --- a/src/Nethermind/Ethereum.Rlp.Test/RlpTests.cs +++ b/src/Nethermind/Ethereum.Rlp.Test/RlpTests.cs @@ -18,6 +18,13 @@ namespace Ethereum.Rlp.Test [TestFixture] public class RlpTests { + private sealed class TestRlpStream : Nethermind.Serialization.Rlp.RlpStream + { + public TestRlpStream(int length) : base(length) { } + public TestRlpStream(byte[] data) : base(data) { } + public void CallWriteZero(int length) => base.WriteZero(length); + } + private class RlpTestJson { public object In { get; set; } @@ -100,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] @@ -120,6 +127,45 @@ public void TestCast() Assert.That(Nethermind.Serialization.Rlp.Rlp.Encode((UInt256)1UL, HeaderDecoder.NonceLength).Bytes, Is.EqualTo(expectedNonce), "nonce bytes"); } + [Test] + public void WriteZero_ClearsAndAdvances_OnPreFilledBuffer() + { + byte[] buffer = Enumerable.Repeat((byte)0xAB, 32).ToArray(); + var stream = new TestRlpStream(buffer); + stream.Position = 5; + stream.CallWriteZero(7); + + Assert.That(stream.Position, Is.EqualTo(12)); + Assert.That(buffer.AsSpan(5, 7).ToArray(), Is.EqualTo(new byte[7])); + Assert.That(buffer.AsSpan(0, 5).ToArray(), Is.EqualTo(Enumerable.Repeat((byte)0xAB, 5).ToArray())); + Assert.That(buffer.AsSpan(12).ToArray(), Is.EqualTo(Enumerable.Repeat((byte)0xAB, 20).ToArray())); + } + + [Test] + public void WriteZero_AdvancesByRequestedLength_OnNewBuffer() + { + var stream = new TestRlpStream(16); + stream.Position = 3; + stream.CallWriteZero(4); + Assert.That(stream.Position, Is.EqualTo(7)); + Assert.That(stream.Data.AsSpan(3, 4).ToArray(), Is.EqualTo(new byte[4])); + } + + [Test] + public void EncodeBloom_Empty_Writes256Zeros() + { + int total = Nethermind.Serialization.Rlp.Rlp.LengthOf(Bloom.Empty); + var stream = new TestRlpStream(total); + stream.Encode(Bloom.Empty); + var bytes = stream.Data.AsSpan().ToArray(); + + Assert.That(bytes.Length, Is.EqualTo(total)); + Assert.That(bytes[0], Is.EqualTo(185)); + Assert.That(bytes[1], Is.EqualTo(1)); + Assert.That(bytes[2], Is.EqualTo(0)); + Assert.That(bytes.AsSpan(3, 256).ToArray(), Is.EqualTo(new byte[256])); + } + [Test] public void TestNonce() { diff --git a/src/Nethermind/Ethereum.Test.Base/AuthorizationListJson.cs b/src/Nethermind/Ethereum.Test.Base/AuthorizationListJson.cs index 728fe1bd52bb..592255588d7c 100644 --- a/src/Nethermind/Ethereum.Test.Base/AuthorizationListJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/AuthorizationListJson.cs @@ -5,6 +5,7 @@ using Nethermind.Int256; namespace Ethereum.Test.Base; + public class AuthorizationListJson { public UInt256 ChainId { get; set; } diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTest.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTest.cs index 19f62afb8007..6916dd59eb53 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTest.cs @@ -20,11 +20,11 @@ public class BlockchainTest : EthereumTest public TestBlockJson[]? Blocks { get; set; } public TestBlockHeaderJson? GenesisBlockHeader { get; set; } + public TestEngineNewPayloadsJson[]? EngineNewPayloads { get; set; } public Dictionary? Pre { get; set; } public Dictionary? PostState { get; set; } public Hash256? PostStateRoot { get; set; } - public bool SealEngineUsed { get; set; } public override string? ToString() { diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index 55e59e9e10c0..8e509fd0d818 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -9,7 +9,6 @@ using System.Threading; using System.Threading.Tasks; using Autofac; -using Nethermind.Api; using Nethermind.Blockchain; using Nethermind.Blockchain.Find; using Nethermind.Config; @@ -30,26 +29,26 @@ using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.Specs.Test; -using Nethermind.State; using Nethermind.Evm.State; using Nethermind.Init.Modules; -using Nethermind.TxPool; using NUnit.Framework; +using Nethermind.Merge.Plugin.Data; +using Nethermind.Merge.Plugin; +using Nethermind.JsonRpc; +using System.Reflection; namespace Ethereum.Test.Base; public abstract class BlockchainTestBase { - private static ILogger _logger; - private static ILogManager _logManager = new TestLogManager(LogLevel.Warn); - private static ISealValidator Sealer { get; } + private static readonly ILogger _logger; + private static readonly ILogManager _logManager = new TestLogManager(LogLevel.Warn); private static DifficultyCalculatorWrapper DifficultyCalculator { get; } + private const int _genesisProcessingTimeoutMs = 30000; static BlockchainTestBase() { DifficultyCalculator = new DifficultyCalculatorWrapper(); - Sealer = new EthashSealValidator(_logManager, DifficultyCalculator, new CryptoRandom(), new Ethash(_logManager), Timestamper.Default); // temporarily keep reusing the same one as otherwise it would recreate cache for each test - _logManager ??= LimboLogs.Instance; _logger = _logManager.GetClassLogger(); } @@ -75,9 +74,9 @@ public UInt256 Calculate(BlockHeader header, BlockHeader parent) } } - protected async Task RunTest(BlockchainTest test, Stopwatch? stopwatch = null, bool failOnInvalidRlp = true) + protected async Task RunTest(BlockchainTest test, Stopwatch? stopwatch = null, bool failOnInvalidRlp = true, ITestBlockTracer? tracer = null) { - _logger.Info($"Running {test.Name}, Network: [{test.Network.Name}] at {DateTime.UtcNow:HH:mm:ss.ffffff}"); + _logger.Info($"Running {test.Name}, Network: [{test.Network!.Name}] at {DateTime.UtcNow:HH:mm:ss.ffffff}"); if (test.NetworkAfterTransition is not null) _logger.Info($"Network after transition: [{test.NetworkAfterTransition.Name}] at {test.TransitionForkActivation}"); Assert.That(test.LoadFailure, Is.Null, "test data loading failure"); @@ -85,8 +84,13 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? test.Network = ChainUtils.ResolveSpec(test.Network, test.ChainId); test.NetworkAfterTransition = ChainUtils.ResolveSpec(test.NetworkAfterTransition, test.ChainId); + bool isEngineTest = test.Blocks is null && test.EngineNewPayloads is not null; + List<(ForkActivation Activation, IReleaseSpec Spec)> transitions = + isEngineTest ? + [((ForkActivation)0, test.Network)] : [((ForkActivation)0, test.GenesisSpec), ((ForkActivation)1, test.Network)]; // TODO: this thing took a lot of time to find after it was removed!, genesis block is always initialized with Frontier + if (test.NetworkAfterTransition is not null) { transitions.Add((test.TransitionForkActivation!.Value, test.NetworkAfterTransition)); @@ -94,10 +98,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? ISpecProvider specProvider = new CustomSpecProvider(test.ChainId, test.ChainId, transitions.ToArray()); - if (test.ChainId != GnosisSpecProvider.Instance.ChainId && specProvider.GenesisSpec != Frontier.Instance) - { - Assert.Fail("Expected genesis spec to be Frontier for blockchain tests"); - } + Assert.That(isEngineTest || test.ChainId == GnosisSpecProvider.Instance.ChainId || specProvider.GenesisSpec == Frontier.Instance, "Expected genesis spec to be Frontier for blockchain tests"); if (test.Network is Cancun || test.NetworkAfterTransition is Cancun) { @@ -127,137 +128,230 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? IConfigProvider configProvider = new ConfigProvider(); // configProvider.GetConfig().PreWarmStateOnBlockProcessing = false; - await using IContainer container = new ContainerBuilder() + ContainerBuilder containerBuilder = new ContainerBuilder() .AddModule(new TestNethermindModule(configProvider)) .AddSingleton(specProvider) .AddSingleton(_logManager) .AddSingleton(rewardCalculator) - .AddSingleton(DifficultyCalculator) - .AddSingleton(NullTxPool.Instance) - .Build(); + .AddSingleton(DifficultyCalculator); + + if (isEngineTest) + { + containerBuilder.AddModule(new TestMergeModule(configProvider)); + } + + await using IContainer container = containerBuilder.Build(); IMainProcessingContext mainBlockProcessingContext = container.Resolve(); IWorldState stateProvider = mainBlockProcessingContext.WorldState; - IBlockchainProcessor blockchainProcessor = mainBlockProcessingContext.BlockchainProcessor; + BlockchainProcessor blockchainProcessor = (BlockchainProcessor)mainBlockProcessingContext.BlockchainProcessor; IBlockTree blockTree = container.Resolve(); IBlockValidator blockValidator = container.Resolve(); blockchainProcessor.Start(); - BlockHeader parentHeader; - // Genesis processing - using (stateProvider.BeginScope(null)) + // Register tracer if provided for blocktest tracing + if (tracer is not null) { - InitializeTestState(test, stateProvider, specProvider); + blockchainProcessor.Tracers.Add(tracer); + } - stopwatch?.Start(); + try + { + BlockHeader parentHeader; + // Genesis processing + using (stateProvider.BeginScope(null)) + { + InitializeTestState(test, stateProvider, specProvider); - test.GenesisRlp ??= Rlp.Encode(new Block(JsonToEthereumTest.Convert(test.GenesisBlockHeader))); + stopwatch?.Start(); - Block genesisBlock = Rlp.Decode(test.GenesisRlp.Bytes); - Assert.That(genesisBlock.Header.Hash, Is.EqualTo(new Hash256(test.GenesisBlockHeader.Hash))); + test.GenesisRlp ??= Rlp.Encode(new Block(JsonToEthereumTest.Convert(test.GenesisBlockHeader))); - ManualResetEvent genesisProcessed = new(false); + Block genesisBlock = Rlp.Decode(test.GenesisRlp.Bytes); + Assert.That(genesisBlock.Header.Hash, Is.EqualTo(new Hash256(test.GenesisBlockHeader.Hash))); - blockTree.NewHeadBlock += (_, args) => - { - if (args.Block.Number == 0) + ManualResetEvent genesisProcessed = new(false); + + blockTree.NewHeadBlock += (_, args) => { - Assert.That(stateProvider.HasStateForBlock(genesisBlock.Header), Is.EqualTo(true)); - genesisProcessed.Set(); - } - }; + if (args.Block.Number == 0) + { + Assert.That(stateProvider.HasStateForBlock(genesisBlock.Header), Is.True); + genesisProcessed.Set(); + } + }; + + blockchainProcessor.BlockRemoved += (_, args) => + { + if (args.ProcessingResult != ProcessingResult.Success && args.BlockHash == genesisBlock.Header.Hash) + { + Assert.Fail($"Failed to process genesis block: {args.Exception}"); + genesisProcessed.Set(); + } + }; + + blockTree.SuggestBlock(genesisBlock); + genesisProcessed.WaitOne(_genesisProcessingTimeoutMs); + parentHeader = genesisBlock.Header; + + // Dispose genesis block's AccountChanges + genesisBlock.DisposeAccountChanges(); + } + + if (test.Blocks is not null) + { + // blockchain test + parentHeader = SuggestBlocks(test, failOnInvalidRlp, blockValidator, blockTree, parentHeader); + } + else if (test.EngineNewPayloads is not null) + { + // engine test + IEngineRpcModule engineRpcModule = container.Resolve(); + await RunNewPayloads(test.EngineNewPayloads, engineRpcModule); + } + else + { + Assert.Fail("Invalid blockchain test, did not contain blocks or new payloads."); + } + + // NOTE: Tracer removal must happen AFTER StopAsync to ensure all blocks are traced + // Blocks are queued asynchronously, so we need to wait for processing to complete + await blockchainProcessor.StopAsync(true); + stopwatch?.Stop(); + + IBlockCachePreWarmer? preWarmer = container.Resolve().LifetimeScope.ResolveOptional(); + + // Caches are cleared async, which is a problem as read for the MainWorldState with prewarmer is not correct if its not cleared. + preWarmer?.ClearCaches(); + + Block? headBlock = blockTree.RetrieveHeadBlock(); - blockTree.SuggestBlock(genesisBlock); - genesisProcessed.WaitOne(); - parentHeader = genesisBlock.Header; + Assert.That(headBlock, Is.Not.Null); + if (headBlock is null) + { + return new EthereumTestResult(test.Name, null, false); + } + + List differences; + using (stateProvider.BeginScope(headBlock.Header)) + { + differences = RunAssertions(test, headBlock, stateProvider); + } - // Dispose genesis block's AccountChanges - genesisBlock.DisposeAccountChanges(); + bool testPassed = differences.Count == 0; + + // Write test end marker if using streaming tracer (JSONL format) + // This must be done BEFORE removing tracer and BEFORE Assert to ensure marker is written even on failure + if (tracer is not null) + { + tracer.TestFinished(test.Name, testPassed, test.Network, stopwatch?.Elapsed, headBlock?.StateRoot); + blockchainProcessor.Tracers.Remove(tracer); + } + + Assert.That(differences, Is.Empty, "differences"); + return new EthereumTestResult(test.Name, null, testPassed); } + catch (Exception) + { + await blockchainProcessor.StopAsync(true); + throw; + } + } + private static BlockHeader SuggestBlocks(BlockchainTest test, bool failOnInvalidRlp, IBlockValidator blockValidator, IBlockTree blockTree, BlockHeader parentHeader) + { List<(Block Block, string ExpectedException)> correctRlp = DecodeRlps(test, failOnInvalidRlp); for (int i = 0; i < correctRlp.Count; i++) { - if (correctRlp[i].Block.Hash is null) - { - Assert.Fail($"null hash in {test.Name} block {i}"); - } + // Mimic the actual behaviour where block goes through validating sync manager + correctRlp[i].Block.Header.IsPostMerge = correctRlp[i].Block.Difficulty == 0; - try + // For tests with reorgs, find the actual parent header from block tree + parentHeader = blockTree.FindHeader(correctRlp[i].Block.ParentHash) ?? parentHeader; + + Assert.That(correctRlp[i].Block.Hash, Is.Not.Null, $"null hash in {test.Name} block {i}"); + + bool expectsException = correctRlp[i].ExpectedException is not null; + // Validate block structure first (mimics SyncServer validation) + if (blockValidator.ValidateSuggestedBlock(correctRlp[i].Block, parentHeader, out string? validationError)) { - // TODO: mimic the actual behaviour where block goes through validating sync manager? - correctRlp[i].Block.Header.IsPostMerge = correctRlp[i].Block.Difficulty == 0; - if (!test.SealEngineUsed || blockValidator.ValidateSuggestedBlock(correctRlp[i].Block, parentHeader, out var _error)) + Assert.That(!expectsException, $"Expected block {correctRlp[i].Block.Hash} to fail with '{correctRlp[i].ExpectedException}', but it passed validation"); + try { + // All validations passed, suggest the block blockTree.SuggestBlock(correctRlp[i].Block); + } - else + catch (InvalidBlockException e) { - if (correctRlp[i].ExpectedException is not null) - { - Assert.Fail($"Unexpected invalid block {correctRlp[i].Block.Hash}"); - } + // Exception thrown during block processing + Assert.That(expectsException, $"Unexpected invalid block {correctRlp[i].Block.Hash}: {validationError}, Exception: {e}"); + // else: Expected to fail and did fail via exception → this is correct behavior } - } - catch (InvalidBlockException e) - { - if (correctRlp[i].ExpectedException is not null) + catch (Exception e) { - Assert.Fail($"Unexpected invalid block {correctRlp[i].Block.Hash}: {e}"); + Assert.Fail($"Unexpected exception during processing: {e}"); + } + finally + { + // Dispose AccountChanges to prevent memory leaks in tests + correctRlp[i].Block.DisposeAccountChanges(); } } - catch (Exception e) - { - Assert.Fail($"Unexpected exception during processing: {e}"); - } - finally + else { - // Dispose AccountChanges to prevent memory leaks in tests - correctRlp[i].Block.DisposeAccountChanges(); + // Validation FAILED + Assert.That(expectsException, $"Unexpected invalid block {correctRlp[i].Block.Hash}: {validationError}"); + // else: Expected to fail and did fail → this is correct behavior } parentHeader = correctRlp[i].Block.Header; } + return parentHeader; + } - await blockchainProcessor.StopAsync(true); - stopwatch?.Stop(); + private async static Task RunNewPayloads(TestEngineNewPayloadsJson[]? newPayloads, IEngineRpcModule engineRpcModule) + { + (ExecutionPayloadV3, string[]?, string[]?, int, int)[] payloads = [.. JsonToEthereumTest.Convert(newPayloads)]; - IBlockCachePreWarmer? preWarmer = container.Resolve().LifetimeScope.ResolveOptional(); - if (preWarmer is not null) + // blockchain test engine + foreach ((ExecutionPayload executionPayload, string[]? blobVersionedHashes, string[]? validationError, int newPayloadVersion, int fcuVersion) in payloads) { - // Caches are cleared async, which is a problem as read for the MainWorldState with prewarmer is not correct if its not cleared. - preWarmer.ClearCaches(); - } + ResultWrapper res; + byte[]?[] hashes = blobVersionedHashes is null ? [] : [.. blobVersionedHashes.Select(x => Bytes.FromHexString(x))]; - Block? headBlock = blockTree.RetrieveHeadBlock(); - List differences; - using (stateProvider.BeginScope(headBlock.Header)) - { - differences = RunAssertions(test, blockTree.RetrieveHeadBlock(), stateProvider); - } + MethodInfo newPayloadMethod = engineRpcModule.GetType().GetMethod($"engine_newPayloadV{newPayloadVersion}"); + List newPayloadParams = [executionPayload]; + if (newPayloadVersion >= 3) + { + newPayloadParams.AddRange([hashes, executionPayload.ParentBeaconBlockRoot]); + } + if (newPayloadVersion >= 4) + { + newPayloadParams.Add(executionPayload.ExecutionRequests); + } - Assert.That(differences.Count, Is.Zero, "differences"); + res = await (Task>)newPayloadMethod.Invoke(engineRpcModule, [.. newPayloadParams]); - return new EthereumTestResult - ( - test.Name, - null, - differences.Count == 0 - ); + if (res.Result.ResultType == ResultType.Success) + { + ForkchoiceStateV1 fcuState = new(executionPayload.BlockHash, executionPayload.BlockHash, executionPayload.BlockHash); + MethodInfo fcuMethod = engineRpcModule.GetType().GetMethod($"engine_forkchoiceUpdatedV{fcuVersion}"); + await (Task>)fcuMethod.Invoke(engineRpcModule, [fcuState, null]); + } + } } - private List<(Block Block, string ExpectedException)> DecodeRlps(BlockchainTest test, bool failOnInvalidRlp) + private static List<(Block Block, string ExpectedException)> DecodeRlps(BlockchainTest test, bool failOnInvalidRlp) { - List<(Block Block, string ExpectedException)> correctRlp = new(); - for (int i = 0; i < test.Blocks.Length; i++) + List<(Block Block, string ExpectedException)> correctRlp = []; + for (int i = 0; i < test.Blocks!.Length; i++) { TestBlockJson testBlockJson = test.Blocks[i]; try { - var rlpContext = Bytes.FromHexString(testBlockJson.Rlp).AsRlpStream(); + RlpStream rlpContext = Bytes.FromHexString(testBlockJson.Rlp!).AsRlpStream(); Block suggestedBlock = Rlp.Decode(rlpContext); - suggestedBlock.Header.SealEngineType = - test.SealEngineUsed ? SealEngineType.Ethash : SealEngineType.None; if (testBlockJson.BlockHeader is not null) { @@ -265,7 +359,7 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? for (int uncleIndex = 0; uncleIndex < suggestedBlock.Uncles.Length; uncleIndex++) { - Assert.That(suggestedBlock.Uncles[uncleIndex].Hash, Is.EqualTo(new Hash256(testBlockJson.UncleHeaders[uncleIndex].Hash))); + Assert.That(suggestedBlock.Uncles[uncleIndex].Hash, Is.EqualTo(new Hash256(testBlockJson.UncleHeaders![uncleIndex].Hash))); } correctRlp.Add((suggestedBlock, testBlockJson.ExpectedException)); @@ -276,16 +370,10 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? if (testBlockJson.ExpectedException is null) { string invalidRlpMessage = $"Invalid RLP ({i}) {e}"; - if (failOnInvalidRlp) - { - Assert.Fail(invalidRlpMessage); - } - else - { - // ForgedTests don't have ExpectedException and at the same time have invalid rlps - // Don't fail here. If test executed incorrectly will fail at last check - _logger.Warn(invalidRlpMessage); - } + Assert.That(!failOnInvalidRlp, invalidRlpMessage); + // ForgedTests don't have ExpectedException and at the same time have invalid rlps + // Don't fail here. If test executed incorrectly will fail at last check + _logger.Warn(invalidRlpMessage); } else { @@ -296,17 +384,20 @@ protected async Task RunTest(BlockchainTest test, Stopwatch? if (correctRlp.Count == 0) { - Assert.That(test.GenesisBlockHeader, Is.Not.Null); - Assert.That(test.LastBlockHash, Is.EqualTo(new Hash256(test.GenesisBlockHeader.Hash))); + using (Assert.EnterMultipleScope()) + { + Assert.That(test.GenesisBlockHeader, Is.Not.Null); + Assert.That(test.LastBlockHash, Is.EqualTo(new Hash256(test.GenesisBlockHeader.Hash))); + } } return correctRlp; } - private void InitializeTestState(BlockchainTest test, IWorldState stateProvider, ISpecProvider specProvider) + private static void InitializeTestState(BlockchainTest test, IWorldState stateProvider, ISpecProvider specProvider) { foreach (KeyValuePair accountState in - ((IEnumerable>)test.Pre ?? Array.Empty>())) + (IEnumerable>)test.Pre ?? Array.Empty>()) { foreach (KeyValuePair storageItem in accountState.Value.Storage) { @@ -318,24 +409,18 @@ private void InitializeTestState(BlockchainTest test, IWorldState stateProvider, } stateProvider.Commit(specProvider.GenesisSpec); - stateProvider.CommitTree(0); - stateProvider.Reset(); } - private List RunAssertions(BlockchainTest test, Block headBlock, IWorldState stateProvider) + private static List RunAssertions(BlockchainTest test, Block headBlock, IWorldState stateProvider) { if (test.PostStateRoot is not null) { - return test.PostStateRoot != stateProvider.StateRoot ? new List { "state root mismatch" } : Enumerable.Empty().ToList(); + return test.PostStateRoot != stateProvider.StateRoot ? ["state root mismatch"] : Enumerable.Empty().ToList(); } - TestBlockHeaderJson testHeaderJson = (test.Blocks? - .Where(b => b.BlockHeader is not null) - .SingleOrDefault(b => new Hash256(b.BlockHeader.Hash) == headBlock.Hash)?.BlockHeader) ?? test.GenesisBlockHeader; - BlockHeader testHeader = JsonToEthereumTest.Convert(testHeaderJson); - List differences = new(); + List differences = []; IEnumerable> deletedAccounts = test.Pre? .Where(pre => !(test.PostState?.ContainsKey(pre.Key) ?? false)) ?? Array.Empty>(); @@ -348,7 +433,7 @@ private List RunAssertions(BlockchainTest test, Block headBlock, IWorldS } } - foreach ((Address acountAddress, AccountState accountState) in test.PostState) + foreach ((Address accountAddress, AccountState accountState) in test.PostState!) { int differencesBefore = differences.Count; @@ -358,87 +443,96 @@ private List RunAssertions(BlockchainTest test, Block headBlock, IWorldS break; } - bool accountExists = stateProvider.AccountExists(acountAddress); - UInt256? balance = accountExists ? stateProvider.GetBalance(acountAddress) : (UInt256?)null; - UInt256? nonce = accountExists ? stateProvider.GetNonce(acountAddress) : (UInt256?)null; + bool accountExists = stateProvider.AccountExists(accountAddress); + UInt256? balance = accountExists ? stateProvider.GetBalance(accountAddress) : null; + UInt256? nonce = accountExists ? stateProvider.GetNonce(accountAddress) : null; if (accountState.Balance != balance) { - differences.Add($"{acountAddress} balance exp: {accountState.Balance}, actual: {balance}, diff: {(balance > accountState.Balance ? balance - accountState.Balance : accountState.Balance - balance)}"); + differences.Add($"{accountAddress} balance exp: {accountState.Balance}, actual: {balance}, diff: {(balance > accountState.Balance ? balance - accountState.Balance : accountState.Balance - balance)}"); } if (accountState.Nonce != nonce) { - differences.Add($"{acountAddress} nonce exp: {accountState.Nonce}, actual: {nonce}"); + differences.Add($"{accountAddress} nonce exp: {accountState.Nonce}, actual: {nonce}"); } - byte[] code = accountExists ? stateProvider.GetCode(acountAddress) : new byte[0]; + byte[] code = accountExists ? stateProvider.GetCode(accountAddress) : []; if (!Bytes.AreEqual(accountState.Code, code)) { - differences.Add($"{acountAddress} code exp: {accountState.Code?.Length}, actual: {code?.Length}"); + differences.Add($"{accountAddress} code exp: {accountState.Code?.Length}, actual: {code?.Length}"); } if (differences.Count != differencesBefore) { - _logger.Info($"ACCOUNT STATE ({acountAddress}) HAS DIFFERENCES"); + _logger.Info($"ACCOUNT STATE ({accountAddress}) HAS DIFFERENCES"); } differencesBefore = differences.Count; - KeyValuePair[] clearedStorages = new KeyValuePair[0]; - if (test.Pre.ContainsKey(acountAddress)) + KeyValuePair[] clearedStorages = []; + if (test.Pre.ContainsKey(accountAddress)) { - clearedStorages = test.Pre[acountAddress].Storage.Where(s => !accountState.Storage.ContainsKey(s.Key)).ToArray(); + clearedStorages = [.. test.Pre[accountAddress].Storage.Where(s => !accountState.Storage.ContainsKey(s.Key))]; } foreach (KeyValuePair clearedStorage in clearedStorages) { - ReadOnlySpan value = !stateProvider.AccountExists(acountAddress) ? Bytes.Empty : stateProvider.Get(new StorageCell(acountAddress, clearedStorage.Key)); + ReadOnlySpan value = !stateProvider.AccountExists(accountAddress) ? Bytes.Empty : stateProvider.Get(new StorageCell(accountAddress, clearedStorage.Key)); if (!value.IsZero()) { - differences.Add($"{acountAddress} storage[{clearedStorage.Key}] exp: 0x00, actual: {value.ToHexString(true)}"); + differences.Add($"{accountAddress} storage[{clearedStorage.Key}] exp: 0x00, actual: {value.ToHexString(true)}"); } } foreach (KeyValuePair storageItem in accountState.Storage) { - ReadOnlySpan value = !stateProvider.AccountExists(acountAddress) ? Bytes.Empty : stateProvider.Get(new StorageCell(acountAddress, storageItem.Key)); + ReadOnlySpan value = !stateProvider.AccountExists(accountAddress) ? Bytes.Empty : stateProvider.Get(new StorageCell(accountAddress, storageItem.Key)); if (!Bytes.AreEqual(storageItem.Value, value)) { - differences.Add($"{acountAddress} storage[{storageItem.Key}] exp: {storageItem.Value.ToHexString(true)}, actual: {value.ToHexString(true)}"); + differences.Add($"{accountAddress} storage[{storageItem.Key}] exp: {storageItem.Value.ToHexString(true)}, actual: {value.ToHexString(true)}"); } } if (differences.Count != differencesBefore) { - _logger.Info($"ACCOUNT STORAGE ({acountAddress}) HAS DIFFERENCES"); + _logger.Info($"ACCOUNT STORAGE ({accountAddress}) HAS DIFFERENCES"); } } - BigInteger gasUsed = headBlock.Header.GasUsed; - if ((testHeader?.GasUsed ?? 0) != gasUsed) - { - differences.Add($"GAS USED exp: {testHeader?.GasUsed ?? 0}, actual: {gasUsed}"); - } + TestBlockHeaderJson? testHeaderJson = test.Blocks? + .Where(b => b.BlockHeader is not null) + .SingleOrDefault(b => new Hash256(b.BlockHeader.Hash) == headBlock.Hash)?.BlockHeader; - if (headBlock.Transactions.Any() && testHeader.Bloom.ToString() != headBlock.Header.Bloom.ToString()) + if (testHeaderJson is not null) { - differences.Add($"BLOOM exp: {testHeader.Bloom}, actual: {headBlock.Header.Bloom}"); - } + BlockHeader testHeader = JsonToEthereumTest.Convert(testHeaderJson); - if (testHeader.StateRoot != stateProvider.StateRoot) - { - differences.Add($"STATE ROOT exp: {testHeader.StateRoot}, actual: {stateProvider.StateRoot}"); - } + BigInteger gasUsed = headBlock.Header.GasUsed; + if ((testHeader?.GasUsed ?? 0) != gasUsed) + { + differences.Add($"GAS USED exp: {testHeader?.GasUsed ?? 0}, actual: {gasUsed}"); + } - if (testHeader.TxRoot != headBlock.Header.TxRoot) - { - differences.Add($"TRANSACTIONS ROOT exp: {testHeader.TxRoot}, actual: {headBlock.Header.TxRoot}"); - } + if (headBlock.Transactions.Length != 0 && testHeader.Bloom.ToString() != headBlock.Header.Bloom.ToString()) + { + differences.Add($"BLOOM exp: {testHeader.Bloom}, actual: {headBlock.Header.Bloom}"); + } - if (testHeader.ReceiptsRoot != headBlock.Header.ReceiptsRoot) - { - differences.Add($"RECEIPT ROOT exp: {testHeader.ReceiptsRoot}, actual: {headBlock.Header.ReceiptsRoot}"); + if (testHeader.StateRoot != stateProvider.StateRoot) + { + differences.Add($"STATE ROOT exp: {testHeader.StateRoot}, actual: {stateProvider.StateRoot}"); + } + + if (testHeader.TxRoot != headBlock.Header.TxRoot) + { + differences.Add($"TRANSACTIONS ROOT exp: {testHeader.TxRoot}, actual: {headBlock.Header.TxRoot}"); + } + + if (testHeader.ReceiptsRoot != headBlock.Header.ReceiptsRoot) + { + differences.Add($"RECEIPT ROOT exp: {testHeader.ReceiptsRoot}, actual: {headBlock.Header.ReceiptsRoot}"); + } } if (test.LastBlockHash != headBlock.Hash) diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestJson.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestJson.cs index fdd03858fd8c..c344cd773da7 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestJson.cs @@ -24,6 +24,7 @@ public class BlockchainTestJson public TestBlockJson[]? Blocks { get; set; } public TestBlockHeaderJson? GenesisBlockHeader { get; set; } + public TestEngineNewPayloadsJson[]? EngineNewPayloads { get; set; } public Dictionary? Pre { get; set; } public Dictionary? PostState { get; set; } diff --git a/src/Nethermind/Ethereum.Test.Base/EofTest.cs b/src/Nethermind/Ethereum.Test.Base/EofTest.cs index 10455305019a..fad44f07780f 100644 --- a/src/Nethermind/Ethereum.Test.Base/EofTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/EofTest.cs @@ -5,6 +5,7 @@ using Nethermind.Evm.EvmObjectFormat; namespace Ethereum.Test.Base; + public class Result { public string Fork { get; set; } diff --git a/src/Nethermind/Ethereum.Test.Base/FileTestsSource.cs b/src/Nethermind/Ethereum.Test.Base/FileTestsSource.cs index 3e85b70087f4..67b713e570ed 100644 --- a/src/Nethermind/Ethereum.Test.Base/FileTestsSource.cs +++ b/src/Nethermind/Ethereum.Test.Base/FileTestsSource.cs @@ -9,16 +9,10 @@ namespace Ethereum.Test.Base { - public class FileTestsSource + public class FileTestsSource(string fileName, string? wildcard = null) { - private readonly string _fileName; - private readonly string? _wildcard; - - public FileTestsSource(string fileName, string? wildcard = null) - { - _fileName = fileName ?? throw new ArgumentNullException(nameof(fileName)); - _wildcard = wildcard; - } + private readonly string _fileName = fileName ?? throw new ArgumentNullException(nameof(fileName)); + private readonly string? _wildcard = wildcard; public IEnumerable LoadTests(TestType testType) { diff --git a/src/Nethermind/Ethereum.Test.Base/GeneralStateTest.cs b/src/Nethermind/Ethereum.Test.Base/GeneralStateTest.cs index 800ff17e8437..da8142a513f6 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 a82748257d98..384b5465b196 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 998319565324..dcf87b0aa5c7 100644 --- a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs @@ -1,32 +1,33 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections.Generic; -using System.Diagnostics; using Autofac; -using Nethermind.Api; -using NUnit.Framework; using Nethermind.Config; using Nethermind.Consensus.Processing; 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; using Nethermind.Crypto; -using Nethermind.Int256; using Nethermind.Evm; using Nethermind.Evm.EvmObjectFormat; -using Nethermind.Evm.Tracing; 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.Specs.Test; -using Nethermind.State; +using Nethermind.State.Proofs; +using Nethermind.Trie; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Diagnostics; namespace Ethereum.Test.Base { @@ -63,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; @@ -88,11 +90,31 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer) IMainProcessingContext mainBlockProcessingContext = container.Resolve(); IWorldState stateProvider = mainBlockProcessingContext.WorldState; - using var _ = stateProvider.BeginScope(null); + using IDisposable _ = stateProvider.BeginScope(null); + IBlockValidator blockValidator = container.Resolve(); ITransactionProcessor transactionProcessor = mainBlockProcessingContext.TransactionProcessor; 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, Keccak.OfAnEmptySequenceRlp, @@ -101,85 +123,80 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer) test.CurrentNumber, test.CurrentGasLimit, test.CurrentTimestamp, - []); - header.BaseFeePerGas = test.Fork.IsEip1559Enabled ? test.CurrentBaseFee ?? _defaultBaseFeeForStateTest : UInt256.Zero; - header.StateRoot = test.PostHash; + []) + { + BaseFeePerGas = test.Fork.IsEip1559Enabled ? test.CurrentBaseFee ?? _defaultBaseFeeForStateTest : UInt256.Zero, + StateRoot = test.PostHash, + IsPostMerge = test.CurrentRandom is not null, + MixHash = test.CurrentRandom, + 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 ?? (spec.RequestsEnabled ? ExecutionRequestExtensions.EmptyRequestsHash : null), + TxRoot = TxTrie.CalculateRoot(transactions), + ReceiptsRoot = test.PostReceiptsRoot, + }; + header.Hash = header.CalculateHash(); - header.IsPostMerge = test.CurrentRandom is not null; - header.MixHash = test.CurrentRandom; - header.WithdrawalsRoot = test.CurrentWithdrawalsRoot; - header.ParentBeaconBlockRoot = test.CurrentBeaconRoot; - header.ExcessBlobGas = test.CurrentExcessBlobGas ?? (test.Fork is Cancun ? 0ul : null); - header.BlobGasUsed = BlobGasCalculator.CalculateBlobGas(test.Transaction); - header.RequestsHash = test.RequestsHash; + 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(); + if (txResult is not null && txResult.Value == TransactionResult.Ok) { 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.' - if (!stateProvider.AccountExists(test.CurrentCoinbase)) + // '@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.CreateAccount(test.CurrentCoinbase, 0); + 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); - EthereumTestResult testResult = new(test.Name, test.ForkName, differences.Count == 0); - testResult.TimeInMs = stopwatch.Elapsed.TotalMilliseconds; - testResult.StateRoot = stateProvider.StateRoot; + EthereumTestResult testResult = new(test.Name, test.ForkName, differences.Count == 0) + { + TimeInMs = stopwatch.Elapsed.TotalMilliseconds, + StateRoot = stateProvider.StateRoot + }; if (differences.Count > 0) { _logger.Info($"\nDifferences from expected\n{string.Join("\n", differences)}"); } - // Assert.Zero(differences.Count, "differences"); return testResult; } diff --git a/src/Nethermind/Ethereum.Test.Base/ITestBlockTracer.cs b/src/Nethermind/Ethereum.Test.Base/ITestBlockTracer.cs new file mode 100644 index 000000000000..23c7c8df1328 --- /dev/null +++ b/src/Nethermind/Ethereum.Test.Base/ITestBlockTracer.cs @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Evm.Tracing; + +namespace Ethereum.Test.Base; + +public interface ITestBlockTracer : IBlockTracer +{ + void TestFinished(string testName, bool pass, IReleaseSpec spec, TimeSpan? duration, Hash256? headStateRoot); +} diff --git a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs index 9a09b17017c1..0eda74a34590 100644 --- a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.Json; using Nethermind.Config; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -14,6 +15,7 @@ using Nethermind.Crypto; using Nethermind.Evm.EvmObjectFormat; using Nethermind.Int256; +using Nethermind.Merge.Plugin.Data; using Nethermind.Serialization.Json; using Nethermind.Serialization.Rlp; using Nethermind.Specs; @@ -56,36 +58,89 @@ public static BlockHeader Convert(TestBlockHeaderJson? headerJson) (long)Bytes.FromHexString(headerJson.Number).ToUInt256(), (long)Bytes.FromHexString(headerJson.GasLimit).ToUnsignedBigInteger(), (ulong)Bytes.FromHexString(headerJson.Timestamp).ToUnsignedBigInteger(), - Bytes.FromHexString(headerJson.ExtraData) - ); - - header.Bloom = new Bloom(Bytes.FromHexString(headerJson.Bloom)); - header.GasUsed = (long)Bytes.FromHexString(headerJson.GasUsed).ToUnsignedBigInteger(); - header.Hash = new Hash256(headerJson.Hash); - header.MixHash = new Hash256(headerJson.MixHash); - header.Nonce = (ulong)Bytes.FromHexString(headerJson.Nonce).ToUnsignedBigInteger(); - header.ReceiptsRoot = new Hash256(headerJson.ReceiptTrie); - header.StateRoot = new Hash256(headerJson.StateRoot); - header.TxRoot = new Hash256(headerJson.TransactionsTrie); + Bytes.FromHexString(headerJson.ExtraData), + headerJson.BlobGasUsed is null ? null : (ulong)Bytes.FromHexString(headerJson.BlobGasUsed).ToUnsignedBigInteger(), + headerJson.ExcessBlobGas is null ? null : (ulong)Bytes.FromHexString(headerJson.ExcessBlobGas).ToUnsignedBigInteger(), + headerJson.ParentBeaconBlockRoot is null ? null : new Hash256(headerJson.ParentBeaconBlockRoot), + headerJson.RequestsHash is null ? null : new Hash256(headerJson.RequestsHash) + ) + { + Bloom = new Bloom(Bytes.FromHexString(headerJson.Bloom)), + GasUsed = (long)Bytes.FromHexString(headerJson.GasUsed).ToUnsignedBigInteger(), + Hash = new Hash256(headerJson.Hash), + MixHash = new Hash256(headerJson.MixHash), + Nonce = (ulong)Bytes.FromHexString(headerJson.Nonce).ToUnsignedBigInteger(), + ReceiptsRoot = new Hash256(headerJson.ReceiptTrie), + StateRoot = new Hash256(headerJson.StateRoot), + TxRoot = new Hash256(headerJson.TransactionsTrie), + WithdrawalsRoot = headerJson.WithdrawalsRoot is null ? null : new Hash256(headerJson.WithdrawalsRoot), + // BlockAccessListHash = headerJson.BlockAccessListHash is null ? null : new Hash256(headerJson.BlockAccessListHash), + }; + + if (headerJson.BaseFeePerGas is not null) + { + header.BaseFeePerGas = (ulong)Bytes.FromHexString(headerJson.BaseFeePerGas).ToUnsignedBigInteger(); + } + return header; } + public static IEnumerable<(ExecutionPayloadV3, string[]?, string[]?, int, int)> Convert(TestEngineNewPayloadsJson[]? executionPayloadsJson) + { + if (executionPayloadsJson is null) + { + throw new InvalidDataException("Execution payloads JSON was null when constructing test."); + } + + foreach (TestEngineNewPayloadsJson engineNewPayload in executionPayloadsJson) + { + TestEngineNewPayloadsJson.ParamsExecutionPayload executionPayload = engineNewPayload.Params[0].Deserialize(EthereumJsonSerializer.JsonOptions); + string[]? blobVersionedHashes = engineNewPayload.Params.Length > 1 ? engineNewPayload.Params[1].Deserialize(EthereumJsonSerializer.JsonOptions) : null; + string? parentBeaconBlockRoot = engineNewPayload.Params.Length > 2 ? engineNewPayload.Params[2].Deserialize(EthereumJsonSerializer.JsonOptions) : null; + string[]? validationError = engineNewPayload.Params.Length > 3 ? engineNewPayload.Params[3].Deserialize(EthereumJsonSerializer.JsonOptions) : null; + yield return (new ExecutionPayloadV3() + { + BaseFeePerGas = (ulong)Bytes.FromHexString(executionPayload.BaseFeePerGas).ToUnsignedBigInteger(), + BlockHash = new(executionPayload.BlockHash), + BlockNumber = (long)Bytes.FromHexString(executionPayload.BlockNumber).ToUnsignedBigInteger(), + ExtraData = Bytes.FromHexString(executionPayload.ExtraData), + FeeRecipient = new(executionPayload.FeeRecipient), + GasLimit = (long)Bytes.FromHexString(executionPayload.GasLimit).ToUnsignedBigInteger(), + GasUsed = (long)Bytes.FromHexString(executionPayload.GasUsed).ToUnsignedBigInteger(), + LogsBloom = new(Bytes.FromHexString(executionPayload.LogsBloom)), + ParentHash = new(executionPayload.ParentHash), + PrevRandao = new(executionPayload.PrevRandao), + ReceiptsRoot = new(executionPayload.ReceiptsRoot), + StateRoot = new(executionPayload.StateRoot), + Timestamp = (ulong)Bytes.FromHexString(executionPayload.Timestamp).ToUnsignedBigInteger(), + // BlockAccessList = executionPayload.BlockAccessList is null ? null : Bytes.FromHexString(executionPayload.BlockAccessList), + BlobGasUsed = executionPayload.BlobGasUsed is null ? null : (ulong)Bytes.FromHexString(executionPayload.BlobGasUsed).ToUnsignedBigInteger(), + ExcessBlobGas = executionPayload.ExcessBlobGas is null ? null : (ulong)Bytes.FromHexString(executionPayload.ExcessBlobGas).ToUnsignedBigInteger(), + ParentBeaconBlockRoot = parentBeaconBlockRoot is null ? null : new(parentBeaconBlockRoot), + Withdrawals = executionPayload.Withdrawals is null ? null : [.. executionPayload.Withdrawals.Select(x => Rlp.Decode(Bytes.FromHexString(x)))], + Transactions = [.. executionPayload.Transactions.Select(x => Bytes.FromHexString(x))], + ExecutionRequests = [] + }, blobVersionedHashes, validationError, int.Parse(engineNewPayload.NewPayloadVersion ?? "4"), int.Parse(engineNewPayload.ForkChoiceUpdatedVersion ?? "3")); + } + } + public static Transaction Convert(PostStateJson postStateJson, TransactionJson transactionJson) { - Transaction transaction = new(); - - transaction.Type = transactionJson.Type; - transaction.Value = transactionJson.Value[postStateJson.Indexes.Value]; - transaction.GasLimit = transactionJson.GasLimit[postStateJson.Indexes.Gas]; - transaction.GasPrice = transactionJson.GasPrice ?? transactionJson.MaxPriorityFeePerGas ?? 0; - transaction.DecodedMaxFeePerGas = transactionJson.MaxFeePerGas ?? 0; - transaction.Nonce = transactionJson.Nonce; - transaction.To = transactionJson.To; - transaction.Data = transactionJson.Data[postStateJson.Indexes.Data]; - transaction.SenderAddress = new PrivateKey(transactionJson.SecretKey).Address; - transaction.Signature = new Signature(1, 1, 27); - transaction.BlobVersionedHashes = transactionJson.BlobVersionedHashes; - transaction.MaxFeePerBlobGas = transactionJson.MaxFeePerBlobGas; + Transaction transaction = new() + { + Type = transactionJson.Type, + Value = transactionJson.Value[postStateJson.Indexes.Value], + GasLimit = transactionJson.GasLimit[postStateJson.Indexes.Gas], + GasPrice = transactionJson.GasPrice ?? transactionJson.MaxPriorityFeePerGas ?? 0, + DecodedMaxFeePerGas = transactionJson.MaxFeePerGas ?? 0, + Nonce = transactionJson.Nonce, + To = transactionJson.To, + Data = transactionJson.Data[postStateJson.Indexes.Data], + SenderAddress = new PrivateKey(transactionJson.SecretKey).Address, + Signature = new Signature(1, 1, 27), + BlobVersionedHashes = transactionJson.BlobVersionedHashes, + MaxFeePerBlobGas = transactionJson.MaxFeePerBlobGas + }; transaction.Hash = transaction.CalculateHash(); AccessList.Builder builder = new(); @@ -100,7 +155,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; @@ -108,7 +168,7 @@ public static Transaction Convert(PostStateJson postStateJson, TransactionJson t if (transactionJson.AuthorizationList is not null) { transaction.AuthorizationList = - transactionJson.AuthorizationList + [.. transactionJson.AuthorizationList .Select(i => { if (i.Nonce > ulong.MaxValue) @@ -148,7 +208,7 @@ public static Transaction Convert(PostStateJson postStateJson, TransactionJson t (byte)i.V, r, s); - }).ToArray(); + })]; if (transaction.AuthorizationList.Any()) { transaction.Type = TxType.SetCode; @@ -172,14 +232,16 @@ public static void ProcessAccessList(AccessListItemJson[]? accessList, AccessLis public static Transaction Convert(LegacyTransactionJson transactionJson) { - Transaction transaction = new(); - transaction.Value = transactionJson.Value; - transaction.GasLimit = transactionJson.GasLimit; - transaction.GasPrice = transactionJson.GasPrice; - transaction.Nonce = transactionJson.Nonce; - transaction.To = transactionJson.To; - transaction.Data = transactionJson.Data; - transaction.Signature = new Signature(transactionJson.R, transactionJson.S, transactionJson.V); + Transaction transaction = new() + { + Value = transactionJson.Value, + GasLimit = transactionJson.GasLimit, + GasPrice = transactionJson.GasPrice, + Nonce = transactionJson.Nonce, + To = transactionJson.To, + Data = transactionJson.Data, + Signature = new Signature(transactionJson.R, transactionJson.S, transactionJson.V) + }; transaction.Hash = transaction.CalculateHash(); return transaction; } @@ -191,41 +253,40 @@ public static IEnumerable Convert(string name, string category return Enumerable.Repeat(new GeneralStateTest { Name = name, Category = category, LoadFailure = testJson.LoadFailure }, 1); } - List blockchainTests = new(); + List blockchainTests = []; foreach (KeyValuePair postStateBySpec in testJson.Post) { int iterationNumber = 0; foreach (PostStateJson stateJson in postStateBySpec.Value) { - GeneralStateTest test = new(); - test.Name = Path.GetFileName(name) + - $"_d{stateJson.Indexes.Data}g{stateJson.Indexes.Gas}v{stateJson.Indexes.Value}_"; + GeneralStateTest test = new() + { + Name = Path.GetFileName(name) + + $"_d{stateJson.Indexes.Data}g{stateJson.Indexes.Gas}v{stateJson.Indexes.Value}_", + Category = category, + ForkName = postStateBySpec.Key, + Fork = SpecNameParser.Parse(postStateBySpec.Key), + PreviousHash = testJson.Env.PreviousHash, + CurrentCoinbase = testJson.Env.CurrentCoinbase, + CurrentDifficulty = testJson.Env.CurrentDifficulty, + CurrentGasLimit = testJson.Env.CurrentGasLimit, + CurrentNumber = testJson.Env.CurrentNumber, + CurrentTimestamp = testJson.Env.CurrentTimestamp, + CurrentBaseFee = testJson.Env.CurrentBaseFee, + CurrentRandom = testJson.Env.CurrentRandom, + CurrentBeaconRoot = testJson.Env.CurrentBeaconRoot, + CurrentWithdrawalsRoot = testJson.Env.CurrentWithdrawalsRoot, + CurrentExcessBlobGas = testJson.Env.CurrentExcessBlobGas, + PostReceiptsRoot = stateJson.Logs, + PostHash = stateJson.Hash, + Pre = testJson.Pre.ToDictionary(p => p.Key, p => p.Value), + Transaction = Convert(stateJson, testJson.Transaction) + }; + if (testJson.Info?.Labels?.ContainsKey(iterationNumber.ToString()) ?? false) { test.Name += testJson.Info?.Labels?[iterationNumber.ToString()]?.Replace(":label ", string.Empty); } - test.Category = category; - - test.ForkName = postStateBySpec.Key; - test.Fork = SpecNameParser.Parse(postStateBySpec.Key); - test.PreviousHash = testJson.Env.PreviousHash; - test.CurrentCoinbase = testJson.Env.CurrentCoinbase; - test.CurrentDifficulty = testJson.Env.CurrentDifficulty; - test.CurrentGasLimit = testJson.Env.CurrentGasLimit; - test.CurrentNumber = testJson.Env.CurrentNumber; - test.CurrentTimestamp = testJson.Env.CurrentTimestamp; - test.CurrentBaseFee = testJson.Env.CurrentBaseFee; - test.CurrentRandom = testJson.Env.CurrentRandom; - test.CurrentBeaconRoot = testJson.Env.CurrentBeaconRoot; - test.CurrentWithdrawalsRoot = testJson.Env.CurrentWithdrawalsRoot; - test.CurrentExcessBlobGas = testJson.Env.CurrentExcessBlobGas; - test.ParentBlobGasUsed = testJson.Env.ParentBlobGasUsed; - test.ParentExcessBlobGas = testJson.Env.ParentExcessBlobGas; - test.PostReceiptsRoot = stateJson.Logs; - test.PostHash = stateJson.Hash; - test.Pre = testJson.Pre.ToDictionary(p => p.Key, p => p.Value); - test.Transaction = Convert(stateJson, testJson.Transaction); - blockchainTests.Add(test); ++iterationNumber; } @@ -241,17 +302,20 @@ public static BlockchainTest Convert(string name, string category, BlockchainTes return new BlockchainTest { Name = name, Category = category, LoadFailure = testJson.LoadFailure }; } - BlockchainTest test = new(); - test.Name = name; - test.Category = category; - test.Network = testJson.EthereumNetwork; - test.NetworkAfterTransition = testJson.EthereumNetworkAfterTransition; - test.TransitionForkActivation = testJson.TransitionForkActivation; - test.LastBlockHash = new Hash256(testJson.LastBlockHash); - test.GenesisRlp = testJson.GenesisRlp is null ? null : new Rlp(Bytes.FromHexString(testJson.GenesisRlp)); - test.GenesisBlockHeader = testJson.GenesisBlockHeader; - test.Blocks = testJson.Blocks; - test.Pre = testJson.Pre.ToDictionary(p => p.Key, p => p.Value); + BlockchainTest test = new() + { + Name = name, + Category = category, + Network = testJson.EthereumNetwork, + NetworkAfterTransition = testJson.EthereumNetworkAfterTransition, + TransitionForkActivation = testJson.TransitionForkActivation, + LastBlockHash = new Hash256(testJson.LastBlockHash), + GenesisRlp = testJson.GenesisRlp is null ? null : new Rlp(Bytes.FromHexString(testJson.GenesisRlp)), + GenesisBlockHeader = testJson.GenesisBlockHeader, + Blocks = testJson.Blocks, + EngineNewPayloads = testJson.EngineNewPayloads, + Pre = testJson.Pre.ToDictionary(p => p.Key, p => p.Value) + }; HalfBlockchainTestJson half = testJson as HalfBlockchainTestJson; if (half is not null) @@ -272,7 +336,7 @@ public static BlockchainTest Convert(string name, string category, BlockchainTes public static IEnumerable ConvertToEofTests(string json) { Dictionary testsInFile = _serializer.Deserialize>(json); - List tests = new(); + List tests = []; foreach (KeyValuePair namedTest in testsInFile) { (string name, string category) = GetNameAndCategory(namedTest.Key); @@ -281,11 +345,13 @@ public static IEnumerable ConvertToEofTests(string json) foreach (KeyValuePair pair in namedTest.Value.Vectors) { VectorTestJson vectorJson = pair.Value; - VectorTest vector = new(); - vector.Code = Bytes.FromHexString(vectorJson.Code); - vector.ContainerKind = ParseContainerKind(vectorJson.ContainerKind); + VectorTest vector = new() + { + Code = Bytes.FromHexString(vectorJson.Code), + ContainerKind = ParseContainerKind(vectorJson.ContainerKind) + }; - foreach (var result in vectorJson.Results) + foreach (KeyValuePair result in vectorJson.Results) { EofTest test = new() { @@ -293,10 +359,10 @@ public static IEnumerable ConvertToEofTests(string json) Category = $"{category} [{result.Key}]", Url = url, Description = description, - Spec = spec + Spec = spec, + Vector = vector, + Result = result.ToTestResult() }; - test.Vector = vector; - test.Result = result.ToTestResult(); tests.Add(test); } } @@ -305,7 +371,7 @@ public static IEnumerable ConvertToEofTests(string json) return tests; static ValidationStrategy ParseContainerKind(string containerKind) - => ("INITCODE".Equals(containerKind) ? ValidationStrategy.ValidateInitCodeMode : ValidationStrategy.ValidateRuntimeMode); + => "INITCODE".Equals(containerKind) ? ValidationStrategy.ValidateInitCodeMode : ValidationStrategy.ValidateRuntimeMode; static void GetTestMetaData(KeyValuePair namedTest, out string? description, out string? url, out string? spec) { @@ -332,7 +398,7 @@ public static IEnumerable ConvertStateTest(string json) Dictionary testsInFile = _serializer.Deserialize>(json); - List tests = new(); + List tests = []; foreach (KeyValuePair namedTest in testsInFile) { (string name, string category) = GetNameAndCategory(namedTest.Key); @@ -351,15 +417,16 @@ public static IEnumerable ConvertToBlockchainTests(string json) } catch (Exception) { - var half = _serializer.Deserialize>(json); - testsInFile = new Dictionary(); + Dictionary half = + _serializer.Deserialize>(json); + testsInFile = []; foreach (KeyValuePair pair in half) { testsInFile[pair.Key] = pair.Value; } } - List testsByName = new(); + List testsByName = []; foreach ((string testName, BlockchainTestJson testSpec) in testsInFile) { string[] transitionInfo = testSpec.Network.Split("At"); diff --git a/src/Nethermind/Ethereum.Test.Base/LoadBlockchainTestFileStrategy.cs b/src/Nethermind/Ethereum.Test.Base/LoadBlockchainTestFileStrategy.cs index 6f7aad55a2c3..71c23519184d 100644 --- a/src/Nethermind/Ethereum.Test.Base/LoadBlockchainTestFileStrategy.cs +++ b/src/Nethermind/Ethereum.Test.Base/LoadBlockchainTestFileStrategy.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using Ethereum.Test.Base.Interfaces; diff --git a/src/Nethermind/Ethereum.Test.Base/LoadBlockchainTestsStrategy.cs b/src/Nethermind/Ethereum.Test.Base/LoadBlockchainTestsStrategy.cs index 1e9a135021cd..5c91a45ea1ca 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 1a8e108ded58..135cb4570974 100644 --- a/src/Nethermind/Ethereum.Test.Base/LoadEipTestsStrategy.cs +++ b/src/Nethermind/Ethereum.Test.Base/LoadEipTestsStrategy.cs @@ -16,8 +16,6 @@ public IEnumerable Load(string testsDirectoryName, string wildcard if (!Path.IsPathRooted(testsDirectoryName)) { string testsDirectory = GetGeneralStateTestsDirectory(); - - testDirs = Directory.EnumerateDirectories(testsDirectory, testsDirectoryName, new EnumerationOptions { RecurseSubdirectories = true }); } else @@ -25,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 7344f0b354f3..ea99491f854a 100644 --- a/src/Nethermind/Ethereum.Test.Base/LoadEofTestsStrategy.cs +++ b/src/Nethermind/Ethereum.Test.Base/LoadEofTestsStrategy.cs @@ -16,8 +16,6 @@ public IEnumerable Load(string testsDirectoryName, string wildcard if (!Path.IsPathRooted(testsDirectoryName)) { string testsDirectory = GetGeneralStateTestsDirectory(); - - testDirs = Directory.EnumerateDirectories(testsDirectory, testsDirectoryName, new EnumerationOptions { RecurseSubdirectories = true }); } else @@ -25,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 b77bea91383c..564f4a0ea750 100644 --- a/src/Nethermind/Ethereum.Test.Base/LoadGeneralStateTestsStrategy.cs +++ b/src/Nethermind/Ethereum.Test.Base/LoadGeneralStateTestsStrategy.cs @@ -16,8 +16,6 @@ public IEnumerable Load(string testsDirectoryName, string wildcard if (!Path.IsPathRooted(testsDirectoryName)) { string testsDirectory = GetGeneralStateTestsDirectory(); - - testDirs = Directory.EnumerateDirectories(testsDirectory, testsDirectoryName, new EnumerationOptions { RecurseSubdirectories = true }); } else @@ -25,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 537dd221457c..ad5d8a85b6a0 100644 --- a/src/Nethermind/Ethereum.Test.Base/LoadLegacyBlockchainTestsStrategy.cs +++ b/src/Nethermind/Ethereum.Test.Base/LoadLegacyBlockchainTestsStrategy.cs @@ -24,24 +24,24 @@ 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 GetLegacyBlockchainTestsDirectory() + private static string GetLegacyBlockchainTestsDirectory() { - char pathSeparator = Path.AltDirectorySeparatorChar; string currentDirectory = AppDomain.CurrentDomain.BaseDirectory; + string rootDirectory = currentDirectory.Remove(currentDirectory.LastIndexOf("src")); - return Path.Combine(currentDirectory.Remove(currentDirectory.LastIndexOf("src")), "src", "tests", "LegacyTests", "Constantinople", "BlockchainTests"); + return Path.Combine(rootDirectory, "src", "tests", "LegacyTests", "Cancun", "BlockchainTests"); } - private IEnumerable LoadTestsFromDirectory(string testDir, string wildcard) + private static IEnumerable LoadTestsFromDirectory(string testDir, string wildcard) { List testsByName = new(); IEnumerable testFiles = Directory.EnumerateFiles(testDir); diff --git a/src/Nethermind/Ethereum.Test.Base/LoadLegacyGeneralStateTestsStrategy.cs b/src/Nethermind/Ethereum.Test.Base/LoadLegacyGeneralStateTestsStrategy.cs index 99a07a29ac66..1c3a2309ad27 100644 --- a/src/Nethermind/Ethereum.Test.Base/LoadLegacyGeneralStateTestsStrategy.cs +++ b/src/Nethermind/Ethereum.Test.Base/LoadLegacyGeneralStateTestsStrategy.cs @@ -24,20 +24,21 @@ 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 GetLegacyGeneralStateTestsDirectory() + private static string GetLegacyGeneralStateTestsDirectory() { string currentDirectory = AppDomain.CurrentDomain.BaseDirectory; + string rootDirectory = currentDirectory.Remove(currentDirectory.LastIndexOf("src")); - return Path.Combine(currentDirectory.Remove(currentDirectory.LastIndexOf("src")), "src", "tests", "LegacyTests", "Constantinople", "GeneralStateTests"); + return Path.Combine(rootDirectory, "src", "tests", "LegacyTests", "Cancun", "GeneralStateTests"); } private IEnumerable LoadTestsFromDirectory(string testDir, string wildcard) @@ -49,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.Test.Base/TestBlockHeaderJson.cs b/src/Nethermind/Ethereum.Test.Base/TestBlockHeaderJson.cs index 3508dba38c66..c154f53e5743 100644 --- a/src/Nethermind/Ethereum.Test.Base/TestBlockHeaderJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/TestBlockHeaderJson.cs @@ -21,5 +21,12 @@ public class TestBlockHeaderJson public string Timestamp { get; set; } public string TransactionsTrie { get; set; } public string UncleHash { get; set; } + public string? BaseFeePerGas { get; set; } + public string? WithdrawalsRoot { get; set; } + public string? ParentBeaconBlockRoot { get; set; } + public string? RequestsHash { get; set; } + public string? BlockAccessListHash { get; set; } + public string? BlobGasUsed { get; set; } + public string? ExcessBlobGas { get; set; } } } diff --git a/src/Nethermind/Ethereum.Test.Base/TestBlockJson.cs b/src/Nethermind/Ethereum.Test.Base/TestBlockJson.cs index 2454b75e495f..a2c2fbcd04e4 100644 --- a/src/Nethermind/Ethereum.Test.Base/TestBlockJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/TestBlockJson.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Text.Json; using System.Text.Json.Serialization; namespace Ethereum.Test.Base diff --git a/src/Nethermind/Ethereum.Test.Base/TestBlockhashProvider.cs b/src/Nethermind/Ethereum.Test.Base/TestBlockhashProvider.cs index f09f460b940a..6e1ca6a02b17 100644 --- a/src/Nethermind/Ethereum.Test.Base/TestBlockhashProvider.cs +++ b/src/Nethermind/Ethereum.Test.Base/TestBlockhashProvider.cs @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Threading; +using System.Threading.Tasks; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; @@ -10,14 +12,10 @@ namespace Ethereum.Test.Base { public class TestBlockhashProvider : IBlockhashProvider { - public Hash256 GetBlockhash(BlockHeader currentBlock, long number) - => GetBlockhash(currentBlock, number, null); - public Hash256? GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec? spec) - { - if (number != 0) - return Keccak.Zero; - return Keccak.Compute(number.ToString()); - } + public Hash256? GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec? spec) => + number != 0 ? Keccak.Zero : Keccak.Compute(number.ToString()); + + public Task Prefetch(BlockHeader currentBlock, CancellationToken token) => Task.CompletedTask; } } diff --git a/src/Nethermind/Ethereum.Test.Base/TestChunkFilter.cs b/src/Nethermind/Ethereum.Test.Base/TestChunkFilter.cs new file mode 100644 index 000000000000..027425574ffa --- /dev/null +++ b/src/Nethermind/Ethereum.Test.Base/TestChunkFilter.cs @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ethereum.Test.Base; + +/// +/// Filters tests into chunks for parallel CI execution. +/// Set TEST_CHUNK environment variable to "1of3", "2of3", "3of3" etc. +/// Partitions tests by index for consistent and even distribution. +/// +public static class TestChunkFilter +{ + public static IEnumerable FilterByChunk(IEnumerable tests) + { + (int Index, int Total)? chunkConfig = GetChunkConfig(); + + if (chunkConfig is null) + { + return tests; + } + + ICollection testList = tests as ICollection ?? [.. tests]; + (int chunkIndex, int totalChunks) = chunkConfig.Value; + + int count = testList.Count; + int chunkSize = count / totalChunks; + int remainder = count % totalChunks; + + int skip = (chunkIndex - 1) * chunkSize; + int take = chunkSize + (chunkIndex == totalChunks ? remainder : 0); + + return testList.Skip(skip).Take(take); + + static (int Index, int Total)? GetChunkConfig() + { + string? chunkEnv = Environment.GetEnvironmentVariable("TEST_CHUNK"); + + if (string.IsNullOrEmpty(chunkEnv)) + { + return null; + } + + string[] parts = chunkEnv.Split("of", StringSplitOptions.None); + + if (parts.Length != 2 || + !int.TryParse(parts[0], out int index) || + !int.TryParse(parts[1], out int total) || + index < 1 || index > total || total < 1) + { + throw new ArgumentException($"Invalid TEST_CHUNK format: '{chunkEnv}'. Expected format: '1of3', '2of5', etc."); + } + + return (index, total); + } + } +} diff --git a/src/Nethermind/Ethereum.Test.Base/TestEngineNewPayloadsJson.cs b/src/Nethermind/Ethereum.Test.Base/TestEngineNewPayloadsJson.cs new file mode 100644 index 000000000000..b89dcd2869b2 --- /dev/null +++ b/src/Nethermind/Ethereum.Test.Base/TestEngineNewPayloadsJson.cs @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json; + +namespace Ethereum.Test.Base +{ + public class TestEngineNewPayloadsJson + { + public JsonElement[] Params { get; set; } + public string? NewPayloadVersion { get; set; } + public string? ForkChoiceUpdatedVersion { get; set; } + + public class ParamsExecutionPayload + { + public string ParentHash { get; set; } + public string FeeRecipient { get; set; } + public string StateRoot { get; set; } + public string ReceiptsRoot { get; set; } + public string LogsBloom { get; set; } + public string BlockNumber { get; set; } + public string GasLimit { get; set; } + public string GasUsed { get; set; } + public string Timestamp { get; set; } + public string ExtraData { get; set; } + public string PrevRandao { get; set; } + public string BaseFeePerGas { get; set; } + public string BlobGasUsed { get; set; } + public string ExcessBlobGas { get; set; } + public string BlockHash { get; set; } + public string[] Transactions { get; set; } + public string[]? Withdrawals { get; set; } + public string? BlockAccessList { get; set; } + } + } +} diff --git a/src/Nethermind/Ethereum.Test.Base/TestResultJson.cs b/src/Nethermind/Ethereum.Test.Base/TestResultJson.cs index 3255830ae710..c4c6d3794152 100644 --- a/src/Nethermind/Ethereum.Test.Base/TestResultJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/TestResultJson.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only namespace Ethereum.Test.Base; + public class TestResultJson { public string? Exception { get; set; } diff --git a/src/Nethermind/Ethereum.Test.Base/TestsSourceLoader.cs b/src/Nethermind/Ethereum.Test.Base/TestsSourceLoader.cs index 8d8108d6abc5..4c20099ce566 100644 --- a/src/Nethermind/Ethereum.Test.Base/TestsSourceLoader.cs +++ b/src/Nethermind/Ethereum.Test.Base/TestsSourceLoader.cs @@ -24,12 +24,15 @@ public TestsSourceLoader(ITestLoadStrategy testLoadStrategy, string path, string public IEnumerable LoadTests() { - return _testLoadStrategy.Load(_path, _wildcard); + IEnumerable tests = _testLoadStrategy.Load(_path, _wildcard); + return TestChunkFilter.FilterByChunk(tests); } + public IEnumerable LoadTests() where TTestType : EthereumTest { - return _testLoadStrategy.Load(_path, _wildcard).Cast(); + IEnumerable tests = _testLoadStrategy.Load(_path, _wildcard).Cast(); + return TestChunkFilter.FilterByChunk(tests); } } } diff --git a/src/Nethermind/Ethereum.Transaction.Test/Ethereum.Transaction.Test.csproj b/src/Nethermind/Ethereum.Transaction.Test/Ethereum.Transaction.Test.csproj index 19f745904c0d..8c4d72e50f8f 100644 --- a/src/Nethermind/Ethereum.Transaction.Test/Ethereum.Transaction.Test.csproj +++ b/src/Nethermind/Ethereum.Transaction.Test/Ethereum.Transaction.Test.csproj @@ -1,14 +1,19 @@ - + + - + + + + %(RecursiveDir)%(FileName)%(Extension) PreserveNewest + diff --git a/src/Nethermind/Ethereum.Transaction.Test/TransactionJsonTest.cs b/src/Nethermind/Ethereum.Transaction.Test/TransactionJsonTest.cs new file mode 100644 index 000000000000..7e79ef7c51d1 --- /dev/null +++ b/src/Nethermind/Ethereum.Transaction.Test/TransactionJsonTest.cs @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Ethereum.Test.Base; +using FluentAssertions; +using Nethermind.Core; +using Nethermind.Core.Test.Builders; +using Nethermind.Int256; +using Nethermind.Serialization.Json; +using NUnit.Framework; + +namespace Ethereum.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class TransactionJsonTest : GeneralStateTestBase +{ + [Test] + public void Can_load_access_lists() + { + const string lists = + "{\"accessLists\": [[{\"address\": \"0x0001020304050607080900010203040506070809\", \"storageKeys\": [\"0x00\", \"0x01\"]}]]}"; + + EthereumJsonSerializer serializer = new EthereumJsonSerializer(); + TransactionJson txJson = serializer.Deserialize(lists); + txJson.SecretKey = TestItem.PrivateKeyA.KeyBytes; + txJson.Value = new UInt256[1]; + txJson.GasLimit = new long[1]; + txJson.Data = new byte[1][]; + txJson.AccessLists.Should().NotBeNull(); + txJson.AccessLists[0][0].Address.Should() + .BeEquivalentTo(new Address("0x0001020304050607080900010203040506070809")); + txJson.AccessLists[0][0].StorageKeys[1][0].Should().Be((byte)1); + + Nethermind.Core.Transaction tx = JsonToEthereumTest.Convert(new PostStateJson { Indexes = new IndexesJson() }, txJson); + tx.AccessList.Should().NotBeNull(); + } +} diff --git a/src/Nethermind/Ethereum.Transaction.Test/TransactionTests.cs b/src/Nethermind/Ethereum.Transaction.Test/TransactionTests.cs index 5995dfe02032..062eb09c2966 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.Transition.Test/BerlinToLondonTests.cs b/src/Nethermind/Ethereum.Transition.Test/BerlinToLondonTests.cs deleted file mode 100644 index 6bdc4a8dce38..000000000000 --- a/src/Nethermind/Ethereum.Transition.Test/BerlinToLondonTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using System.Threading.Tasks; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.Transition.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.None)] - public class BerlinToLondonTests : BlockchainTestBase - { - [TestCaseSource(nameof(LoadTests))] - public async Task Test(BlockchainTest test) - { - await RunTest(test); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadBlockchainTestsStrategy(), "bcBerlinToLondon"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/Ethereum.Trie.Test/Permutations.cs b/src/Nethermind/Ethereum.Trie.Test/Permutations.cs index 61aa35185af3..ce54da79625b 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 65a5fc9a126e..445fc2575e8b 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 637c77717f0a..3c07411bb09f 100644 --- a/src/Nethermind/Ethereum.Trie.Test/TrieTests.cs +++ b/src/Nethermind/Ethereum.Trie.Test/TrieTests.cs @@ -5,8 +5,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using System.Text.Json.Serialization; - using Ethereum.Test.Base; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; @@ -311,7 +309,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 +321,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 +333,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 +346,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/Ethereum.VM.Test/LogTests.cs b/src/Nethermind/Ethereum.VM.Test/LogTests.cs deleted file mode 100644 index 2273f4a7c598..000000000000 --- a/src/Nethermind/Ethereum.VM.Test/LogTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Ethereum.Test.Base; -using NUnit.Framework; - -namespace Ethereum.VM.Test -{ - [TestFixture] - [Parallelizable(ParallelScope.All)] - public class LogTests : GeneralStateTestBase - { - [TestCaseSource(nameof(LoadTests))] - public void Test(GeneralStateTest test) - { - Assert.That(RunTest(test).Pass, Is.True); - } - - public static IEnumerable LoadTests() - { - var loader = new TestsSourceLoader(new LoadGeneralStateTestsStrategy(), "vmLogTest"); - return loader.LoadTests(); - } - } -} diff --git a/src/Nethermind/EthereumTests.slnx b/src/Nethermind/EthereumTests.slnx index 3f484f1caf62..b5e21d12b74a 100644 --- a/src/Nethermind/EthereumTests.slnx +++ b/src/Nethermind/EthereumTests.slnx @@ -19,6 +19,7 @@ + @@ -47,22 +48,21 @@ - - - + + + + - - diff --git a/src/Nethermind/Nethermind.Abi.Test/AbiTests.cs b/src/Nethermind/Nethermind.Abi.Test/AbiTests.cs index 16a9aba9bddd..1b95b5e1d910 100644 --- a/src/Nethermind/Nethermind.Abi.Test/AbiTests.cs +++ b/src/Nethermind/Nethermind.Abi.Test/AbiTests.cs @@ -27,10 +27,10 @@ public class AbiTests public void Dynamic_array_of_dynamic_array_of_uint(AbiEncodingStyle encodingStyle) { AbiType type = new AbiArray(new AbiArray(AbiType.UInt256)); - AbiSignature signature = new AbiSignature("abc", type); - UInt256[] element = { 1, 2, 3 }; - UInt256[][] data = { element, element }; - byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, new object[] { data }); + AbiSignature signature = new("abc", type); + UInt256[] element = [1, 2, 3]; + UInt256[][] data = [element, element]; + byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, [data]); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); Assert.That(arguments[0], Is.EqualTo(data)); } @@ -42,7 +42,7 @@ public void Dynamic_array_of_dynamic_array_of_uint(AbiEncodingStyle encodingStyl public void Dynamic_array_of_dynamic_array_of_uint_empty(AbiEncodingStyle encodingStyle) { AbiType type = new AbiArray(new AbiArray(AbiType.UInt256)); - AbiSignature signature = new AbiSignature("abc", type); + AbiSignature signature = new("abc", type); BigInteger[] data = []; byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, data); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); @@ -56,9 +56,9 @@ public void Dynamic_array_of_dynamic_array_of_uint_empty(AbiEncodingStyle encodi public void Dynamic_array_of_string(AbiEncodingStyle encodingStyle) { AbiType type = new AbiArray(AbiType.String); - AbiSignature signature = new AbiSignature("abc", type); - string[] data = { "a", "bc", "def" }; - byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, new object[] { data }); + AbiSignature signature = new("abc", type); + string[] data = ["a", "bc", "def"]; + byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, [data]); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); Assert.That(arguments[0], Is.EqualTo(data)); } @@ -70,8 +70,8 @@ public void Dynamic_array_of_string(AbiEncodingStyle encodingStyle) public void Dynamic_array_of_uint(AbiEncodingStyle encodingStyle) { AbiType type = new AbiArray(AbiType.UInt256); - AbiSignature signature = new AbiSignature("abc", type); - UInt256[] data = { 1, 2, 3 }; + AbiSignature signature = new("abc", type); + UInt256[] data = [1, 2, 3]; byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, data); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); Assert.That(arguments[0], Is.EqualTo(data)); @@ -84,10 +84,10 @@ public void Dynamic_array_of_uint(AbiEncodingStyle encodingStyle) public void Fixed_array_of_fixed_array_of_uint(AbiEncodingStyle encodingStyle) { AbiType type = new AbiFixedLengthArray(new AbiFixedLengthArray(AbiType.UInt256, 2), 3); - UInt256[] element = { 1, 1 }; - UInt256[][] data = { element, element, element }; - AbiSignature signature = new AbiSignature("abc", type); - byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, new object[] { data }); + UInt256[] element = [1, 1]; + UInt256[][] data = [element, element, element]; + 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)); } @@ -99,9 +99,9 @@ public void Fixed_array_of_fixed_array_of_uint(AbiEncodingStyle encodingStyle) public void Fixed_array_of_string(AbiEncodingStyle encodingStyle) { AbiType type = new AbiFixedLengthArray(AbiType.String, 3); - AbiSignature signature = new AbiSignature("abc", type); - string[] data = { "a", "bc", "def" }; - byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, new object[] { data }); + AbiSignature signature = new("abc", type); + string[] data = ["a", "bc", "def"]; + byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, [data]); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); Assert.That(arguments[0], Is.EqualTo(data)); } @@ -113,8 +113,8 @@ public void Fixed_array_of_string(AbiEncodingStyle encodingStyle) public void Fixed_array_of_uint(AbiEncodingStyle encodingStyle) { AbiType type = new AbiFixedLengthArray(AbiType.UInt256, 2); - UInt256[] data = { 1, 1 }; - AbiSignature signature = new AbiSignature("abc", type); + UInt256[] data = [1, 1]; + 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)); @@ -127,8 +127,8 @@ public void Fixed_array_of_uint(AbiEncodingStyle encodingStyle) public void Test_bytes(AbiEncodingStyle encodingStyle) { AbiType type = new AbiBytes(19); - byte[] data = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }; - AbiSignature signature = new AbiSignature("abc", type); + byte[] data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]; + AbiSignature signature = new("abc", type); byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, data); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); Assert.That(Bytes.AreEqual((byte[])arguments[0], data), Is.True); @@ -142,7 +142,7 @@ public void Test_bytes_invalid_length(AbiEncodingStyle encodingStyle) { AbiType type = new AbiBytes(19); byte[] data = new byte[23]; - AbiSignature signature = new AbiSignature("abc", type); + AbiSignature signature = new("abc", type); Assert.Throws(() => _abiEncoder.Encode(encodingStyle, signature, data)); } @@ -153,8 +153,8 @@ public void Test_bytes_invalid_length(AbiEncodingStyle encodingStyle) public void Test_dynamic_bytes(AbiEncodingStyle encodingStyle) { AbiType type = AbiType.DynamicBytes; - byte[] data = new byte[17] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }; - AbiSignature signature = new AbiSignature("abc", type); + byte[] data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]; + AbiSignature signature = new("abc", type); byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, data); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); Assert.That(Bytes.AreEqual((byte[])arguments[0], data), Is.True); @@ -168,7 +168,7 @@ public void Test_fixed(AbiEncodingStyle encodingStyle) { AbiFixed type = AbiType.Fixed; BigRational data = BigRational.FromBigInt(123456789) * BigRational.Reciprocal(BigRational.Pow(BigRational.FromInt(10), type.Precision)); - AbiSignature signature = new AbiSignature("abc", type); + 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)); @@ -181,8 +181,8 @@ public void Test_fixed(AbiEncodingStyle encodingStyle) public void Test_single_address(AbiEncodingStyle encodingStyle) { AbiType type = AbiType.Address; - AbiSignature signature = new AbiSignature("abc", type); - Address arg = new Address(Keccak.OfAnEmptyString); + AbiSignature signature = new("abc", type); + Address arg = new(Keccak.OfAnEmptyString); byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, arg); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); Assert.That(arguments[0], Is.EqualTo(arg)); @@ -195,7 +195,7 @@ public void Test_single_address(AbiEncodingStyle encodingStyle) public void Test_single_bool(AbiEncodingStyle encodingStyle) { AbiType type = AbiType.Bool; - AbiSignature signature = new AbiSignature("abc", type); + AbiSignature signature = new("abc", type); byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, true); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); Assert.That(arguments[0], Is.EqualTo(true)); @@ -209,7 +209,7 @@ public void Test_single_function(AbiEncodingStyle encodingStyle) { AbiType type = AbiType.Function; byte[] data = new byte[24]; - AbiSignature signature = new AbiSignature("abc", type); + AbiSignature signature = new("abc", type); byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, data); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); Assert.That(Bytes.AreEqual((byte[])arguments[0], data), Is.True); @@ -222,7 +222,7 @@ public void Test_single_function(AbiEncodingStyle encodingStyle) public void Test_single_int(AbiEncodingStyle encodingStyle) { AbiType type = AbiType.Int256; - AbiSignature signature = new AbiSignature("abc", type); + AbiSignature signature = new("abc", type); byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, BigInteger.MinusOne); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); Assert.That(arguments[0], Is.EqualTo(BigInteger.MinusOne)); @@ -232,7 +232,7 @@ public void Test_single_int(AbiEncodingStyle encodingStyle) public void Test_single_uint_with_casting(AbiEncodingStyle encodingStyle) { AbiType type = AbiType.UInt256; - AbiSignature signature = new AbiSignature("abc", type); + AbiSignature signature = new("abc", type); byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, UInt256.One); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); @@ -262,7 +262,7 @@ public void Test_single_uint_with_casting(AbiEncodingStyle encodingStyle) public void Test_single_uint(AbiEncodingStyle encodingStyle) { AbiType type = AbiType.UInt256; - AbiSignature signature = new AbiSignature("abc", type); + AbiSignature signature = new("abc", type); byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, BigInteger.Zero); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); Assert.That(arguments[0], Is.EqualTo(UInt256.Zero)); @@ -275,7 +275,7 @@ public void Test_single_uint(AbiEncodingStyle encodingStyle) public void Test_single_uint32(AbiEncodingStyle encodingStyle) { AbiType type = new AbiUInt(32); - AbiSignature signature = new AbiSignature("abc", type); + AbiSignature signature = new("abc", type); byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, 123U); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); Assert.That(arguments[0], Is.EqualTo(123U)); @@ -289,7 +289,7 @@ public void Test_string(AbiEncodingStyle encodingStyle) { AbiType type = AbiType.String; string data = "def"; - AbiSignature signature = new AbiSignature("abc", type); + 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)); @@ -304,7 +304,7 @@ public void Test_ufixed(AbiEncodingStyle encodingStyle) AbiUFixed type = AbiType.UFixed; BigRational data = BigRational.FromBigInt(-123456789) * BigRational.Reciprocal(BigRational.Pow(BigRational.FromInt(10), type.Precision)); - AbiSignature signature = new AbiSignature("abc", type); + 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)); @@ -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)] @@ -355,8 +363,8 @@ public void Test_uint_exception(int length) public void Test_single_address_no_signature(AbiEncodingStyle encodingStyle) { AbiType type = AbiType.Address; - AbiSignature signature = new AbiSignature("abc", type); - Address arg = new Address(Keccak.OfAnEmptyString); + AbiSignature signature = new("abc", type); + Address arg = new(Keccak.OfAnEmptyString); byte[] encoded = _abiEncoder.Encode(AbiEncodingStyle.None, signature, arg); object[] arguments = _abiEncoder.Decode(AbiEncodingStyle.None, signature, encoded); Assert.That(arguments[0], Is.EqualTo(arg)); @@ -374,7 +382,7 @@ public void Test_packed(AbiEncodingStyle encodingStyle) uint units = 10U; byte[] salt = new byte[16]; - AbiSignature abiDef = new AbiSignature("example", + AbiSignature abiDef = new("example", new AbiBytes(32), new AbiUInt(32), new AbiUInt(96), @@ -395,9 +403,9 @@ public void Static_tuple(AbiEncodingStyle encodingStyle) { AbiType type = new AbiTuple(AbiType.UInt256, AbiType.Address, AbiType.Bool); - AbiSignature signature = new AbiSignature("abc", type); + AbiSignature signature = new("abc", type); - ValueTuple staticTuple = new ValueTuple((UInt256)1000, Address.SystemUser, true); + ValueTuple staticTuple = new((UInt256)1000, Address.SystemUser, true); byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, staticTuple); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); Assert.That(arguments[0], Is.EqualTo(staticTuple)); @@ -409,9 +417,9 @@ public void Dynamic_tuple(AbiEncodingStyle encodingStyle) { AbiType type = new AbiTuple(AbiType.DynamicBytes, AbiType.Address, AbiType.DynamicBytes); - AbiSignature signature = new AbiSignature("abc", type); + AbiSignature signature = new("abc", type); - ValueTuple dynamicTuple = new ValueTuple(Bytes.FromHexString("0x004749fa3d"), Address.SystemUser, Bytes.Zero32); + ValueTuple dynamicTuple = new(Bytes.FromHexString("0x004749fa3d"), Address.SystemUser, Bytes.Zero32); byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, dynamicTuple); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); Assert.That(arguments[0], Is.EqualTo(dynamicTuple)); @@ -426,9 +434,9 @@ public void Multiple_params_with_one_of_them_a_tuple(AbiEncodingStyle encodingSt { AbiType type = new AbiTuple(AbiType.UInt256, AbiType.Address, AbiType.Bool); - AbiSignature signature = new AbiSignature("abc", type, AbiType.String); + AbiSignature signature = new("abc", type, AbiType.String); - ValueTuple staticTuple = new ValueTuple((UInt256)1000, Address.SystemUser, true); + ValueTuple staticTuple = new((UInt256)1000, Address.SystemUser, true); const string stringParam = "hello there!"; byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, staticTuple, stringParam); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); @@ -444,9 +452,9 @@ public void Multiple_params_with_one_of_them_a_tuple_dynamic_first(AbiEncodingSt { AbiType type = new AbiTuple(AbiType.UInt256, AbiType.Address, AbiType.Bool); - AbiSignature signature = new AbiSignature("abc", AbiType.String, type); + AbiSignature signature = new("abc", AbiType.String, type); - ValueTuple staticTuple = new ValueTuple((UInt256)1000, Address.SystemUser, true); + ValueTuple staticTuple = new((UInt256)1000, Address.SystemUser, true); const string stringParam = "hello there!"; byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, stringParam, staticTuple); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); @@ -462,9 +470,9 @@ public void Tuple_with_inner_static_tuple(AbiEncodingStyle encodingStyle) { AbiType type = new AbiTuple(AbiType.UInt256, new AbiTuple(AbiType.UInt256, AbiType.Address), AbiType.Bool); - AbiSignature signature = new AbiSignature("abc", type); + AbiSignature signature = new("abc", type); - ValueTuple, bool> staticTuple = new ValueTuple, bool>((UInt256)1000, new ValueTuple((UInt256)400, Address.SystemUser), true); + ValueTuple, bool> staticTuple = new((UInt256)1000, new ValueTuple((UInt256)400, Address.SystemUser), true); byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, staticTuple); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); Assert.That(arguments[0], Is.EqualTo(staticTuple)); @@ -476,9 +484,9 @@ public void Tuple_with_inner_dynamic_tuple(AbiEncodingStyle encodingStyle) { AbiType type = new AbiTuple(AbiType.UInt256, new AbiTuple(AbiType.DynamicBytes, AbiType.Address), AbiType.Bool); - AbiSignature signature = new AbiSignature("abc", type); + AbiSignature signature = new("abc", type); - ValueTuple, bool> dynamicTuple = new ValueTuple, bool>((UInt256)1000, new ValueTuple(Bytes.FromHexString("0x019283fa3d"), Address.SystemUser), true); + ValueTuple, bool> dynamicTuple = new((UInt256)1000, new ValueTuple(Bytes.FromHexString("0x019283fa3d"), Address.SystemUser), true); byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, dynamicTuple); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); Assert.That(arguments[0], Is.EqualTo(dynamicTuple)); @@ -491,9 +499,9 @@ public void Dynamic_tuple_with_inner_dynamic_tuple(AbiEncodingStyle encodingStyl { AbiType type = new AbiTuple(AbiType.DynamicBytes, new AbiTuple(AbiType.DynamicBytes, AbiType.Address), AbiType.Bool); - AbiSignature signature = new AbiSignature("abc", type); + AbiSignature signature = new("abc", type); - ValueTuple, bool> dynamicTuple = new ValueTuple, bool>(Bytes.FromHexString("0x019283fa3d"), new ValueTuple(Bytes.FromHexString("0x019283fa3d"), Address.SystemUser), true); + ValueTuple, bool> dynamicTuple = new(Bytes.FromHexString("0x019283fa3d"), new ValueTuple(Bytes.FromHexString("0x019283fa3d"), Address.SystemUser), true); byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, dynamicTuple); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); Assert.That(arguments[0], Is.EqualTo(dynamicTuple)); @@ -507,9 +515,9 @@ public void Tuple_with_inner_tuple_with_inner_tuple(AbiEncodingStyle encodingSty { AbiType type = new AbiTuple(new AbiTuple(new AbiTuple(AbiType.UInt256))); - AbiSignature signature = new AbiSignature("abc", type); + AbiSignature signature = new("abc", type); - ValueTuple>> tupleception = new ValueTuple>>(new ValueTuple>(new ValueTuple(88888))); + ValueTuple>> tupleception = new(new ValueTuple>(new ValueTuple(88888))); byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, tupleception); object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); Assert.That(arguments[0], Is.EqualTo(tupleception)); @@ -519,11 +527,12 @@ public void Tuple_with_inner_tuple_with_inner_tuple(AbiEncodingStyle encodingSty public void Can_decode_array_of_dynamic_tuples() { AbiType type = new AbiArray(new AbiTuple()); - AbiSignature signature = new AbiSignature("handleOps", type, AbiType.Address); + AbiSignature signature = new("handleOps", type, AbiType.Address); object[] objects = _abiEncoder.Decode(AbiEncodingStyle.IncludeSignature, signature, Bytes.FromHexString("0x9984521800000000000000000000000000000000000000000000000000000000000000400000000000000000000000004173c8ce71a385e325357d8d79d6b7bc1c708f40000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000004ed7c70f96b99c776995fb64377f0d4ab3b0e1c10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000001a5b8000000000000000000000000000000000000000000000000000000000007a1200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000260000000000000000000000000fc7c490fc83e74556aa353ac360cf766e0d4313e000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084be6002c200000000000000000000000009635f643e140090a9a8dcd712ed6285858cebef0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000406661abd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041c0b5810722f6d3ff73d1e22ec2120670a6ae63ee916c026517a55754e7dd9a7b5d9b6aa5046bb35d009e034aace90845823e8365dbb22c2aa591fb60cd5c40001c00000000000000000000000000000000000000000000000000000000000000")); - object[] expectedObjects = { + object[] expectedObjects = + [ new[] {new UserOperationAbi { Target = new Address("0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1"), Nonce = UInt256.Zero, @@ -539,7 +548,7 @@ public void Can_decode_array_of_dynamic_tuples() Signature = Bytes.FromHexString("0xc0b5810722f6d3ff73d1e22ec2120670a6ae63ee916c026517a55754e7dd9a7b5d9b6aa5046bb35d009e034aace90845823e8365dbb22c2aa591fb60cd5c40001c") }}, new Address("0x4173c8cE71a385e325357d8d79d6B7bc1c708F40") - }; + ]; objects.Should().BeEquivalentTo(expectedObjects); } @@ -560,6 +569,26 @@ public void Should_encode_arrays_and_lists_equally() abi.Encode(pool, false).Should().BeEquivalentTo(encoded); } + [Test] + public void Should_throw_on_malformed_abi() + { + var abi = new AbiSignature( + "DepositEvent", + AbiType.DynamicBytes, + AbiType.DynamicBytes, + AbiType.DynamicBytes, + AbiType.DynamicBytes, + AbiType.DynamicBytes + ); + + // Malformed ABI: declares length=200 but insufficient data. + byte[] data = new byte[256]; + data[31] = 160; // First offset. + data[191] = 200; // Length = 200 (oversized for available data). + + Assert.Throws(() => new AbiEncoder().Decode(AbiEncodingStyle.None, abi, data)); + } + private class UserOperationAbi { public Address Target { get; set; } @@ -576,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 /// @@ -594,7 +666,7 @@ public void Tutorial_test() "000000000000000000000000000000000000000000000000000000000000000d" + "48656c6c6f2c20776f726c642100000000000000000000000000000000000000"); - AbiSignature signature = new AbiSignature( + AbiSignature signature = new( "f", AbiType.UInt256, new AbiArray(new AbiUInt(32)), diff --git a/src/Nethermind/Nethermind.Abi.Test/Json/AbiParameterConverterTests.cs b/src/Nethermind/Nethermind.Abi.Test/Json/AbiParameterConverterTests.cs index fbc227188095..b95d067d2b78 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 5f09bf4857fa..9764f1222029 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 42288e1d191a..701134ffd8f5 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); @@ -87,24 +87,32 @@ internal static (Array, int) DecodeSequence(Type elementType, int length, IEnume int position = startPosition; int dynamicPosition = 0; using IEnumerator typesEnumerator = types.GetEnumerator(); - object? item; for (int i = 0; i < length; i++) { typesEnumerator.MoveNext(); AbiType type = typesEnumerator.Current; - if (type.IsDynamic) + try { - (UInt256 offset, int nextPosition) = UInt256.DecodeUInt(data, position, packed); - (item, dynamicPosition) = type.Decode(data, startPosition + (int)offset, packed); - position = nextPosition; + object? item; + + if (type.IsDynamic) + { + (UInt256 offset, int nextPosition) = UInt256.DecodeUInt(data, position, packed); + (item, dynamicPosition) = type.Decode(data, startPosition + (int)offset, packed); + position = nextPosition; + } + else + { + (item, position) = type.Decode(data, position, packed); + } + + sequence.SetValue(item, i); } - else + catch (Exception e) when (e is OverflowException or ArgumentException) { - (item, position) = type.Decode(data, position, packed); + throw new AbiException($"Failed to decode ABI sequence at element {i} for type {type}", e); } - - sequence.SetValue(item, i); } return (sequence, Math.Max(position, dynamicPosition)); diff --git a/src/Nethermind/Nethermind.Abi/AbiType.cs b/src/Nethermind/Nethermind.Abi/AbiType.cs index 11b73b2a13b2..085624632735 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 56dcdf62c4ce..38dd9f4d4ee0 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.Analytics/AnalyticsConfig.cs b/src/Nethermind/Nethermind.Analytics/AnalyticsConfig.cs deleted file mode 100644 index bfa4c7c3f3bc..000000000000 --- a/src/Nethermind/Nethermind.Analytics/AnalyticsConfig.cs +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Analytics -{ - public class AnalyticsConfig : IAnalyticsConfig - { - public bool PluginsEnabled { get; set; } - public bool StreamTransactions { get; set; } - public bool StreamBlocks { get; set; } - public bool LogPublishedData { get; set; } - } -} diff --git a/src/Nethermind/Nethermind.Analytics/AnalyticsPlugin.cs b/src/Nethermind/Nethermind.Analytics/AnalyticsPlugin.cs deleted file mode 100644 index e77611a104dd..000000000000 --- a/src/Nethermind/Nethermind.Analytics/AnalyticsPlugin.cs +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - - -using System.Threading.Tasks; -using Autofac; -using Autofac.Core; -using Nethermind.Analytics; -using Nethermind.Api; -using Nethermind.Api.Extensions; -using Nethermind.Api.Steps; -using Nethermind.Core; -using Nethermind.Core.PubSub; -using Nethermind.JsonRpc.Modules; -using Nethermind.Serialization.Json.PubSub; - -namespace Nethermind.Analytics -{ - public class AnalyticsPlugin(IInitConfig initConfig, IAnalyticsConfig analyticsConfig) : INethermindPlugin - { - public bool Enabled => initConfig.WebSocketsEnabled && - (analyticsConfig.PluginsEnabled || - analyticsConfig.StreamBlocks || - analyticsConfig.StreamTransactions); - - public string Name => "Analytics"; - - public string Description => "Various Analytics Extensions"; - - public string Author => "Nethermind"; - - public IModule Module => new AnalyticsModule(); - } -} - -public class AnalyticsModule : Module -{ - protected override void Load(ContainerBuilder builder) => builder - - // Standard IPublishers, which seems to be things that publish when txpool got transaction - .AddSingleton() - .Bind() // send to websocket - .AddSingleton() // Send to log - - // Rpc - .RegisterSingletonJsonRpcModule() - - // Step - .AddStep(typeof(AnalyticsSteps)) - ; -} diff --git a/src/Nethermind/Nethermind.Analytics/AnalyticsRpcModule.cs b/src/Nethermind/Nethermind.Analytics/AnalyticsRpcModule.cs deleted file mode 100644 index 7a4755d3124b..000000000000 --- a/src/Nethermind/Nethermind.Analytics/AnalyticsRpcModule.cs +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Threading; -using Nethermind.Blockchain; -using Nethermind.Int256; -using Nethermind.JsonRpc; -using Nethermind.Logging; -using Nethermind.State; - -namespace Nethermind.Analytics -{ - public class AnalyticsRpcModule : IAnalyticsRpcModule - { - private readonly IBlockTree _blockTree; - private readonly IStateReader _stateReader; - private readonly ILogManager _logManager; - - public AnalyticsRpcModule(IBlockTree blockTree, IStateReader stateReader, ILogManager logManager) - { - _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); - _stateReader = stateReader ?? throw new ArgumentNullException(nameof(stateReader)); - _logManager = logManager ?? throw new ArgumentNullException(nameof(logManager)); - } - - public ResultWrapper analytics_verifySupply() - { - SupplyVerifier supplyVerifier = new SupplyVerifier(_logManager.GetClassLogger()); - _stateReader.RunTreeVisitor(supplyVerifier, _blockTree.Head.StateRoot); - return ResultWrapper.Success(supplyVerifier.Balance); - } - - public ResultWrapper analytics_verifyRewards() - { - RewardsVerifier rewardsVerifier = new RewardsVerifier(_logManager, (_blockTree.Head?.Number ?? 0) + 1); - _blockTree.Accept(rewardsVerifier, CancellationToken.None); - return ResultWrapper.Success(rewardsVerifier.BlockRewards); - } - } -} diff --git a/src/Nethermind/Nethermind.Analytics/AnalyticsSteps.cs b/src/Nethermind/Nethermind.Analytics/AnalyticsSteps.cs deleted file mode 100644 index bcb0d6626d4f..000000000000 --- a/src/Nethermind/Nethermind.Analytics/AnalyticsSteps.cs +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Threading; -using System.Threading.Tasks; -using Nethermind.Analytics; -using Nethermind.Api.Steps; -using Nethermind.Core.PubSub; -using Nethermind.Init.Steps; -using Nethermind.Sockets; -using Nethermind.TxPool; - -/// -/// Publish txpool `NewDiscovered` event to all `IPublisher`. -/// -/// -/// -/// -/// -/// -[RunnerStepDependencies(typeof(InitializeBlockchain))] -public class AnalyticsSteps( - ITxPool txPool, - AnalyticsWebSocketsModule webSocketsModule, - IWebSocketsManager webSocketsManager, - IAnalyticsConfig analyticsConfig, - IPublisher[] publishers -) : IStep -{ - public Task Execute(CancellationToken cancellationToken) - { - txPool.NewDiscovered += TxPoolOnNewDiscovered; - webSocketsManager!.AddModule(webSocketsModule, true); - return Task.CompletedTask; - } - - private void TxPoolOnNewDiscovered(object sender, TxEventArgs e) - { - if (analyticsConfig.StreamTransactions) - { - foreach (IPublisher publisher in publishers) - { - // TODO: probably need to serialize first - publisher.PublishAsync(e.Transaction); - } - } - } -} diff --git a/src/Nethermind/Nethermind.Analytics/AnalyticsWebSocketsModule.cs b/src/Nethermind/Nethermind.Analytics/AnalyticsWebSocketsModule.cs deleted file mode 100644 index 186f277db38a..000000000000 --- a/src/Nethermind/Nethermind.Analytics/AnalyticsWebSocketsModule.cs +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Concurrent; -using System.Linq; -using System.Net.WebSockets; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Nethermind.Core.PubSub; -using Nethermind.Logging; -using Nethermind.Serialization.Json; -using Nethermind.Sockets; - -namespace Nethermind.Analytics; - -public class AnalyticsWebSocketsModule : IWebSocketsModule, IPublisher -{ - private readonly ConcurrentDictionary _clients = new(); - - private readonly IJsonSerializer _jsonSerializer; - private readonly ILogManager _logManager; - - public string Name { get; } = "analytics"; - - public AnalyticsWebSocketsModule(IJsonSerializer jsonSerializer, ILogManager logManager) - { - _jsonSerializer = jsonSerializer; - _logManager = logManager; - } - - public ValueTask CreateClient(WebSocket webSocket, string clientName, HttpContext httpContext) - { - SocketClient socketsClient = new(clientName, new WebSocketMessageStream(webSocket, _logManager), _jsonSerializer); - _clients.TryAdd(socketsClient.Id, socketsClient); - - return ValueTask.FromResult(socketsClient); - } - - public void RemoveClient(string id) => _clients.TryRemove(id, out _); - - public Task PublishAsync(T data) where T : class - => SendAsync(new SocketsMessage("analytics", null, data)); - - public Task SendAsync(SocketsMessage message) - => Task.WhenAll(_clients.Values.Select(v => v.SendAsync(message))); - - public void Dispose() - { - } -} diff --git a/src/Nethermind/Nethermind.Analytics/IAnalyticsConfig.cs b/src/Nethermind/Nethermind.Analytics/IAnalyticsConfig.cs deleted file mode 100644 index ac6a8eef84e6..000000000000 --- a/src/Nethermind/Nethermind.Analytics/IAnalyticsConfig.cs +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Config; - -namespace Nethermind.Analytics -{ - [ConfigCategory(DisabledForCli = true, HiddenFromDocs = true)] - public interface IAnalyticsConfig : IConfig - { - [ConfigItem(Description = "If 'false' then no analytics plugins will be loaded", DefaultValue = "false")] - public bool PluginsEnabled { get; set; } - - [ConfigItem(Description = "If 'false' then transactions are not streamed by default to gRPC endpoints.", DefaultValue = "false")] - public bool StreamTransactions { get; set; } - - [ConfigItem(Description = "If 'false' then blocks are not streamed by default to gRPC endpoints.", DefaultValue = "false")] - public bool StreamBlocks { get; set; } - - [ConfigItem(Description = "If 'true' then all analytics will be also output to logger", DefaultValue = "false")] - public bool LogPublishedData { get; set; } - } -} diff --git a/src/Nethermind/Nethermind.Analytics/IAnalyticsRpcModule.cs b/src/Nethermind/Nethermind.Analytics/IAnalyticsRpcModule.cs deleted file mode 100644 index 8ff8a485c625..000000000000 --- a/src/Nethermind/Nethermind.Analytics/IAnalyticsRpcModule.cs +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Int256; -using Nethermind.JsonRpc; -using Nethermind.JsonRpc.Modules; - -namespace Nethermind.Analytics -{ - [RpcModule(ModuleType.Clique)] - public interface IAnalyticsRpcModule : IRpcModule - { - [JsonRpcMethod(Description = "Retrieves ETH supply counted from state.", IsImplemented = true)] - ResultWrapper analytics_verifySupply(); - - [JsonRpcMethod(Description = "Retrieves ETH supply counted from rewards.", IsImplemented = true)] - ResultWrapper analytics_verifyRewards(); - } -} diff --git a/src/Nethermind/Nethermind.Analytics/Nethermind.Analytics.csproj b/src/Nethermind/Nethermind.Analytics/Nethermind.Analytics.csproj deleted file mode 100644 index ddfd7afb6685..000000000000 --- a/src/Nethermind/Nethermind.Analytics/Nethermind.Analytics.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/Nethermind/Nethermind.Analytics/RewardsVerifier.cs b/src/Nethermind/Nethermind.Analytics/RewardsVerifier.cs deleted file mode 100644 index ac876d041d00..000000000000 --- a/src/Nethermind/Nethermind.Analytics/RewardsVerifier.cs +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Threading; -using System.Threading.Tasks; -using Nethermind.Blockchain.Visitors; -using Nethermind.Consensus.Rewards; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Int256; -using Nethermind.Logging; -using Nethermind.Specs; - -namespace Nethermind.Analytics -{ - public class RewardsVerifier : IBlockTreeVisitor - { - private readonly ILogger _logger; - public bool PreventsAcceptingNewBlocks => true; - public long StartLevelInclusive => 0; - public long EndLevelExclusive { get; } - - private readonly UInt256 _genesisAllocations = UInt256.Parse("72009990499480000000000000"); - private UInt256 _uncles; - - public UInt256 BlockRewards { get; private set; } - - public RewardsVerifier(ILogManager logManager, long endLevelExclusive) - { - _logger = logManager.GetClassLogger(); - EndLevelExclusive = endLevelExclusive; - BlockRewards = _genesisAllocations; - } - - private readonly RewardCalculator _rewardCalculator = new(MainnetSpecProvider.Instance); - - public Task VisitBlock(Block block, CancellationToken cancellationToken) - { - BlockReward[] rewards = _rewardCalculator.CalculateRewards(block); - for (int i = 0; i < rewards.Length; i++) - { - if (rewards[i].RewardType == BlockRewardType.Uncle) - { - _uncles += rewards[i].Value; - } - else - { - BlockRewards += rewards[i].Value; - } - } - - _logger.Info($"Visiting block {block.Number}, total supply is (genesis + miner rewards + uncle rewards) | {_genesisAllocations} + {BlockRewards} + {_uncles}"); - return Task.FromResult(BlockVisitOutcome.None); - } - - public Task VisitLevelStart(ChainLevelInfo chainLevelInfo, long levelNumber, CancellationToken cancellationToken) - => Task.FromResult(LevelVisitOutcome.None); - - public Task VisitMissing(Hash256 hash, CancellationToken cancellationToken) - => Task.FromResult(true); - - public Task VisitHeader(BlockHeader header, CancellationToken cancellationToken) - => Task.FromResult(HeaderVisitOutcome.None); - - public Task VisitLevelEnd(ChainLevelInfo chainLevelInfo, long levelNumber, CancellationToken cancellationToken) - => Task.FromResult(LevelVisitOutcome.None); - } -} diff --git a/src/Nethermind/Nethermind.Analytics/SupplyVerifier.cs b/src/Nethermind/Nethermind.Analytics/SupplyVerifier.cs deleted file mode 100644 index 5e53bd492305..000000000000 --- a/src/Nethermind/Nethermind.Analytics/SupplyVerifier.cs +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Int256; -using Nethermind.Logging; -using Nethermind.Trie; - -namespace Nethermind.Analytics -{ - public class SupplyVerifier : ITreeVisitor - { - private readonly ILogger _logger; - private readonly HashSet _ignoreThisOne = new(Hash256AsKeyComparer.Instance); - private readonly HashSet.AlternateLookup _ignoreThisOneLookup; - private int _accountsVisited; - private int _nodesVisited; - - public SupplyVerifier(ILogger logger) - { - _logger = logger; - _ignoreThisOneLookup = _ignoreThisOne.GetAlternateLookup(); - } - - public UInt256 Balance { get; set; } = UInt256.Zero; - - public bool IsFullDbScan => false; - - public bool ShouldVisit(in OldStyleTrieVisitContext _, in ValueHash256 nextNode) - { - if (_ignoreThisOne.Count > 16) - { - _logger.Warn($"Ignore count leak -> {_ignoreThisOne.Count}"); - } - - if (_ignoreThisOneLookup.Remove(nextNode)) - { - return false; - } - - return true; - } - - public void VisitTree(in OldStyleTrieVisitContext _, in ValueHash256 rootHash) - { - } - - public void VisitMissingNode(in OldStyleTrieVisitContext _, in ValueHash256 nodeHash) - { - _logger.Warn($"Missing node {nodeHash}"); - } - - public void VisitBranch(in OldStyleTrieVisitContext trieVisitContext, TrieNode node) - { - _logger.Info($"Balance after visiting {_accountsVisited} accounts and {_nodesVisited} nodes: {Balance}"); - _nodesVisited++; - - if (trieVisitContext.IsStorage) - { - for (int i = 0; i < 16; i++) - { - Hash256 childHash = node.GetChildHash(i); - if (childHash is not null) - { - _ignoreThisOne.Add(childHash); - } - } - } - } - - public void VisitExtension(in OldStyleTrieVisitContext trieVisitContext, TrieNode node) - { - _nodesVisited++; - if (trieVisitContext.IsStorage) - { - _ignoreThisOne.Add(node.GetChildHash(0)); - } - } - - public void VisitLeaf(in OldStyleTrieVisitContext trieVisitContext, TrieNode node) - { - } - - public void VisitAccount(in OldStyleTrieVisitContext _, TrieNode node, in AccountStruct account) - { - _nodesVisited++; - Balance += account.Balance; - _accountsVisited++; - - _logger.Info($"Balance after visiting {_accountsVisited} accounts and {_nodesVisited} nodes: {Balance}"); - } - } -} diff --git a/src/Nethermind/Nethermind.Api.Test/PluginLoaderTests.cs b/src/Nethermind/Nethermind.Api.Test/PluginLoaderTests.cs index 726408c40720..28e73642c7f7 100644 --- a/src/Nethermind/Nethermind.Api.Test/PluginLoaderTests.cs +++ b/src/Nethermind/Nethermind.Api.Test/PluginLoaderTests.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO.Abstractions; -using System.Linq; using System.Threading.Tasks; using FluentAssertions; using Nethermind.Api.Extensions; @@ -13,7 +12,6 @@ using Nethermind.Consensus.AuRa; using Nethermind.Consensus.Clique; using Nethermind.Consensus.Ethash; -using Nethermind.Consensus.Transactions; using Nethermind.Core; using Nethermind.HealthChecks; using Nethermind.Hive; diff --git a/src/Nethermind/Nethermind.Api/BasicApiExtensions.cs b/src/Nethermind/Nethermind.Api/BasicApiExtensions.cs index 9f8fa9db333a..108b96aa3289 100644 --- a/src/Nethermind/Nethermind.Api/BasicApiExtensions.cs +++ b/src/Nethermind/Nethermind.Api/BasicApiExtensions.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Config; -using Nethermind.Db; namespace Nethermind.Api { diff --git a/src/Nethermind/Nethermind.Api/Extensions/IConsensusWrapperPlugin.cs b/src/Nethermind/Nethermind.Api/Extensions/IConsensusWrapperPlugin.cs index b16d41fa1a7f..b77a2a5196ef 100644 --- a/src/Nethermind/Nethermind.Api/Extensions/IConsensusWrapperPlugin.cs +++ b/src/Nethermind/Nethermind.Api/Extensions/IConsensusWrapperPlugin.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Consensus; -using Nethermind.Consensus.Transactions; namespace Nethermind.Api.Extensions { diff --git a/src/Nethermind/Nethermind.Api/Extensions/INethermindPlugin.cs b/src/Nethermind/Nethermind.Api/Extensions/INethermindPlugin.cs index f902c211db7f..ca3ef4c59982 100644 --- a/src/Nethermind/Nethermind.Api/Extensions/INethermindPlugin.cs +++ b/src/Nethermind/Nethermind.Api/Extensions/INethermindPlugin.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Threading.Tasks; using Autofac.Core; diff --git a/src/Nethermind/Nethermind.Api/IApiWithBlockchain.cs b/src/Nethermind/Nethermind.Api/IApiWithBlockchain.cs index 412e3c4d02d8..a953a91278c2 100644 --- a/src/Nethermind/Nethermind.Api/IApiWithBlockchain.cs +++ b/src/Nethermind/Nethermind.Api/IApiWithBlockchain.cs @@ -11,7 +11,6 @@ using Nethermind.Consensus.Scheduler; using Nethermind.Consensus.Validators; using Nethermind.Core; -using Nethermind.Facade; using Nethermind.State; using Nethermind.TxPool; diff --git a/src/Nethermind/Nethermind.Api/IBasicApi.cs b/src/Nethermind/Nethermind.Api/IBasicApi.cs index 577294912bc5..29ce719bcb52 100644 --- a/src/Nethermind/Nethermind.Api/IBasicApi.cs +++ b/src/Nethermind/Nethermind.Api/IBasicApi.cs @@ -32,7 +32,7 @@ public interface IBasicApi IDbProvider DbProvider { get; } IEthereumEcdsa EthereumEcdsa { get; } [SkipServiceCollection] - IJsonSerializer EthereumJsonSerializer { get; } + EthereumJsonSerializer EthereumJsonSerializer { get; } IFileSystem FileSystem { get; } IKeyStore? KeyStore { get; set; } [SkipServiceCollection] diff --git a/src/Nethermind/Nethermind.Api/IInitConfig.cs b/src/Nethermind/Nethermind.Api/IInitConfig.cs index b5380ec9ffab..525c066d1886 100644 --- a/src/Nethermind/Nethermind.Api/IInitConfig.cs +++ b/src/Nethermind/Nethermind.Api/IInitConfig.cs @@ -27,9 +27,6 @@ public interface IInitConfig : IConfig [ConfigItem(Description = "Whether to connect to newly discovered peers.", DefaultValue = "true")] bool PeerManagerEnabled { get; set; } - [ConfigItem(Description = "Whether to seal/mine new blocks.", DefaultValue = "false")] - bool IsMining { get; set; } - [ConfigItem(Description = "The path to the chain spec file.", DefaultValue = "chainspec/foundation.json")] string ChainSpecPath { get; set; } @@ -42,10 +39,13 @@ public interface IInitConfig : IConfig [ConfigItem(Description = "The hash of the genesis block. If not specified, the genesis block validity is not checked which is useful in the case of ad hoc test/private networks.", DefaultValue = "null")] string? GenesisHash { get; set; } - [ConfigItem(Description = "The path to the static nodes file.", DefaultValue = "Data/static-nodes.json")] + [ConfigItem(Description = "The network id. If not specified, taken from the chain spec file.", DefaultValue = "null", HiddenFromDocs = true)] + ulong? NetworkId { get; set; } + + [ConfigItem(Description = "The path to the static nodes file.", DefaultValue = "static-nodes.json")] string StaticNodesPath { get; set; } - [ConfigItem(Description = "The path to the trusted nodes file.", DefaultValue = "Data/trusted-nodes.json")] + [ConfigItem(Description = "The path to the trusted nodes file.", DefaultValue = "trusted-nodes.json")] string TrustedNodesPath { get; set; } [ConfigItem(Description = "The name of the log file.", DefaultValue = "log.txt")] @@ -57,12 +57,6 @@ public interface IInitConfig : IConfig [ConfigItem(Description = "The logs format as `LogPath:LogLevel;*`", DefaultValue = "null")] string? LogRules { get; set; } - [ConfigItem(Description = "Moved to ReceiptConfig.", DefaultValue = "true", HiddenFromDocs = true)] - bool StoreReceipts { get; set; } - - [ConfigItem(Description = "Moved to ReceiptConfig.", DefaultValue = "false", HiddenFromDocs = true)] - bool ReceiptsMigration { get; set; } - [ConfigItem(Description = "The diagnostic mode.", DefaultValue = "None")] DiagnosticMode DiagnosticMode { get; set; } @@ -78,6 +72,9 @@ public interface IInitConfig : IConfig [ConfigItem(Description = "The maximum number of bad blocks observed on the network that will be stored on disk.", DefaultValue = "100")] long? BadBlocksStored { get; set; } + [ConfigItem(Description = "The path to the Nethermind data directory. Defaults to Nethermind's current directory.", DefaultValue = "null", HiddenFromDocs = true)] + string? DataDir { get; set; } + [ConfigItem(Description = "[TECHNICAL] Disable garbage collector on newPayload", DefaultValue = "true", HiddenFromDocs = true)] bool DisableGcOnNewPayload { get; set; } @@ -96,7 +93,7 @@ public interface IInitConfig : IConfig [ConfigItem(Description = "[TECHNICAL] Specify concurrency limit for background task.", DefaultValue = "2", HiddenFromDocs = true)] int BackgroundTaskConcurrency { get; set; } - [ConfigItem(Description = "[TECHNICAL] Specify max number of background task.", DefaultValue = "1024", HiddenFromDocs = true)] + [ConfigItem(Description = "[TECHNICAL] Specify max number of background task.", DefaultValue = "2048", HiddenFromDocs = true)] int BackgroundTaskMaxNumber { get; set; } [ConfigItem(Description = "[TECHNICAL] True when in runner test. Disable some wait.", DefaultValue = "false", HiddenFromDocs = true)] diff --git a/src/Nethermind/Nethermind.Api/InitConfig.cs b/src/Nethermind/Nethermind.Api/InitConfig.cs index d663135b8b40..914849cc7be8 100644 --- a/src/Nethermind/Nethermind.Api/InitConfig.cs +++ b/src/Nethermind/Nethermind.Api/InitConfig.cs @@ -14,13 +14,13 @@ public class InitConfig : IInitConfig public bool DiscoveryEnabled { get; set; } = true; public bool ProcessingEnabled { get; set; } = true; public bool PeerManagerEnabled { get; set; } = true; - public bool IsMining { get; set; } = false; public string ChainSpecPath { get; set; } = "chainspec/foundation.json"; public string BaseDbPath { get; set; } = "db"; public string LogFileName { get; set; } = "log.txt"; public string? GenesisHash { get; set; } - public string StaticNodesPath { get; set; } = "Data/static-nodes.json"; - public string TrustedNodesPath { get; set; } = "Data/trusted-nodes.json"; + public ulong? NetworkId { get; set; } + public string StaticNodesPath { get; set; } = "static-nodes.json"; + public string TrustedNodesPath { get; set; } = "trusted-nodes.json"; public string? KzgSetupPath { get; set; } = null; public string LogDirectory { get; set; } = "logs"; public string? LogRules { get; set; } = null; @@ -29,7 +29,7 @@ public class InitConfig : IInitConfig public DiagnosticMode DiagnosticMode { get; set; } = DiagnosticMode.None; public DumpOptions AutoDump { get; set; } = DumpOptions.Default; - public string RpcDbUrl { get; set; } = String.Empty; + public string RpcDbUrl { get; set; } = string.Empty; public long? MemoryHint { get; set; } public long? BadBlocksStored { get; set; } = 100; public bool DisableGcOnNewPayload { get; set; } = true; @@ -38,8 +38,9 @@ public class InitConfig : IInitConfig public long? ExitOnBlockNumber { get; set; } = null; public bool ExitOnInvalidBlock { get; set; } = false; public int BackgroundTaskConcurrency { get; set; } = 2; - public int BackgroundTaskMaxNumber { get; set; } = 1024; + public int BackgroundTaskMaxNumber { get; set; } = 2048; public bool InRunnerTest { get; set; } = false; + public string? DataDir { get; set; } [Obsolete("Use DiagnosticMode with MemDb instead")] public bool UseMemDb diff --git a/src/Nethermind/Nethermind.Api/NethermindApi.cs b/src/Nethermind/Nethermind.Api/NethermindApi.cs index 4e1e6cd57f1a..7dbae388e628 100644 --- a/src/Nethermind/Nethermind.Api/NethermindApi.cs +++ b/src/Nethermind/Nethermind.Api/NethermindApi.cs @@ -6,7 +6,6 @@ using Autofac; using Nethermind.Api.Extensions; using Nethermind.Blockchain; -using Nethermind.Blockchain.Blocks; using Nethermind.Blockchain.Receipts; using Nethermind.Config; using Nethermind.Consensus; @@ -21,7 +20,6 @@ using Nethermind.Crypto; using Nethermind.Db; using Nethermind.Db.Blooms; -using Nethermind.Facade; using Nethermind.JsonRpc; using Nethermind.JsonRpc.Modules; using Nethermind.KeyStore; @@ -38,8 +36,6 @@ using Nethermind.TxPool; using Nethermind.Wallet; using Nethermind.Consensus.Processing.CensorshipDetector; -using Nethermind.Facade.Find; -using Nethermind.History; namespace Nethermind.Api { @@ -49,7 +45,7 @@ public class NethermindApi(NethermindApi.Dependencies dependencies) : INethermin // A simple class to prevent having to modify subclass of NethermindApi many times public record Dependencies( IConfigProvider ConfigProvider, - IJsonSerializer JsonSerializer, + EthereumJsonSerializer JsonSerializer, ILogManager LogManager, ChainSpec ChainSpec, ISpecProvider SpecProvider, @@ -82,7 +78,7 @@ ILifetimeScope Context new BuildBlocksWhenRequested(); public IIPResolver IpResolver => Context.Resolve(); - public IJsonSerializer EthereumJsonSerializer => _dependencies.JsonSerializer; + public EthereumJsonSerializer EthereumJsonSerializer => _dependencies.JsonSerializer; public IKeyStore? KeyStore { get; set; } public ILogManager LogManager => _dependencies.LogManager; public IMessageSerializationService MessageSerializationService => Context.Resolve(); diff --git a/src/Nethermind/Nethermind.AuRa.Test/AuRaBlockFinalizationManagerTests.cs b/src/Nethermind/Nethermind.AuRa.Test/AuRaBlockFinalizationManagerTests.cs index 639d9bf48fbb..662a604c657f 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/AuRaBlockFinalizationManagerTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/AuRaBlockFinalizationManagerTests.cs @@ -22,7 +22,6 @@ namespace Nethermind.AuRa.Test { public class AuRaBlockFinalizationManagerTests { - private IChainLevelInfoRepository _chainLevelInfoRepository; private IBranchProcessor _blockProcessor; private IValidatorStore _validatorStore; private ILogManager _logManager; @@ -31,13 +30,13 @@ public class AuRaBlockFinalizationManagerTests [SetUp] public void Initialize() { - _chainLevelInfoRepository = Substitute.For(); + Substitute.For(); _blockProcessor = Substitute.For(); _validatorStore = Substitute.For(); _logManager = LimboLogs.Instance; _validSealerStrategy = Substitute.For(); - _validatorStore.GetValidators(Arg.Any()).Returns(new Address[] { TestItem.AddressA, TestItem.AddressB, TestItem.AddressC }); + _validatorStore.GetValidators(Arg.Any()).Returns([TestItem.AddressA, TestItem.AddressB, TestItem.AddressC]); } [Test] @@ -97,7 +96,7 @@ public void correctly_finalizes_blocks_in_chain(int chainLength, long twoThirdsM } }; - blockTreeBuilder.OfChainLength(chainLength, 0, 0, false, blockCreators); + blockTreeBuilder.OfChainLength(chainLength, blockBeneficiaries: blockCreators); int start = 0; for (int i = start; i < chainLength; i++) @@ -117,7 +116,7 @@ public void correctly_finalizes_blocks_in_chain(int chainLength, long twoThirdsM public void correctly_finalizes_blocks_in_already_in_chain_on_initialize() { int count = 2; - BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree().OfChainLength(count, 0, 0, false, TestItem.AddressA, TestItem.AddressB); + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree().OfChainLength(count, blockBeneficiaries: [TestItem.AddressA, TestItem.AddressB]); AuRaBlockFinalizationManager finalizationManager = new(blockTreeBuilder.TestObject, blockTreeBuilder.ChainLevelInfoRepository, _validatorStore, _validSealerStrategy, _logManager); finalizationManager.SetMainBlockBranchProcessor(_blockProcessor); @@ -146,8 +145,8 @@ void ProcessBlock(BlockTreeBuilder blockTreeBuilder1, int level, int index) finalizationManager.SetMainBlockBranchProcessor(_blockProcessor); blockTreeBuilder - .OfChainLength(out Block headBlock, chainLength, 1, 0, false, TestItem.Addresses.Take(validators).ToArray()) - .OfChainLength(out Block alternativeHeadBlock, chainLength, 0, splitFrom: 2, false, TestItem.Addresses.Skip(validators).Take(validators).ToArray()); + .OfChainLength(out Block headBlock, chainLength, 1, blockBeneficiaries: TestItem.Addresses.Take(validators).ToArray()) + .OfChainLength(out Block alternativeHeadBlock, chainLength, 0, splitFrom: 2, blockBeneficiaries: TestItem.Addresses.Skip(validators).Take(validators).ToArray()); for (int i = 0; i < chainLength - 1; i++) { @@ -184,7 +183,7 @@ public static IEnumerable GetLastFinalizedByTests public long GetLastFinalizedBy_test(int chainLength, Address[] beneficiaries, int minForFinalization) { SetupValidators(minForFinalization, beneficiaries); - BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree().OfChainLength(chainLength, 0, 0, false, beneficiaries); + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree().OfChainLength(chainLength, blockBeneficiaries: beneficiaries); BlockTree blockTree = blockTreeBuilder.TestObject; AuRaBlockFinalizationManager finalizationManager = new(blockTree, blockTreeBuilder.ChainLevelInfoRepository, _validatorStore, _validSealerStrategy, _logManager); finalizationManager.SetMainBlockBranchProcessor(_blockProcessor); @@ -215,7 +214,7 @@ public static IEnumerable GetFinalizationLevelTests public long? GetFinalizationLevel_tests(int chainLength, Address[] beneficiaries, int minForFinalization, long level) { SetupValidators(minForFinalization, beneficiaries); - BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree().OfChainLength(chainLength, 0, 0, false, beneficiaries); + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree().OfChainLength(chainLength, blockBeneficiaries: beneficiaries); BlockTree blockTree = blockTreeBuilder.TestObject; AuRaBlockFinalizationManager finalizationManager = new(blockTree, blockTreeBuilder.ChainLevelInfoRepository, _validatorStore, _validSealerStrategy, _logManager); finalizationManager.SetMainBlockBranchProcessor(_blockProcessor); @@ -261,7 +260,7 @@ public void correctly_de_finalizes_blocks_on_block_reprocessing(int chainLength, twoThirdsMajorityTransition ? 0 : long.MaxValue); finalizationManager.SetMainBlockBranchProcessor(_blockProcessor); - blockTreeBuilder.OfChainLength(chainLength, 0, 0, false, blockCreators); + blockTreeBuilder.OfChainLength(chainLength, blockBeneficiaries: blockCreators); FinalizeToLevel(chainLength, blockTreeBuilder.ChainLevelInfoRepository); List blocks = Enumerable.Range(1, rerun) diff --git a/src/Nethermind/Nethermind.AuRa.Test/AuRaPluginTests.cs b/src/Nethermind/Nethermind.AuRa.Test/AuRaPluginTests.cs index c5d7b358c468..3c71adcc54c8 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/AuRaPluginTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/AuRaPluginTests.cs @@ -11,10 +11,8 @@ using Nethermind.Consensus.AuRa.InitializationSteps; using Nethermind.Core; using Nethermind.Core.Specs; -using Nethermind.Core.Test.Blockchain; using Nethermind.Core.Test.Builders; using Nethermind.Core.Test.Modules; -using Nethermind.Db; using Nethermind.Logging; using Nethermind.Serialization.Json; using Nethermind.Specs.ChainSpecStyle; @@ -27,7 +25,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 0b6513134d49..710fd0c6d5d4 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/AuraBlockProcessorTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/AuraBlockProcessorTests.cs @@ -21,16 +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; @@ -94,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() @@ -127,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; @@ -136,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(); @@ -143,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( - HoleskySpecProvider.Instance, + AuRaBlockProcessor processor = new( + GnosisSpecProvider.Instance, TestBlockValidator.AlwaysValid, NoBlockRewards.Instance, new BlockProcessor.BlockValidationTransactionsExecutor(new ExecuteTransactionProcessorAdapter(transactionProcessor), stateProvider), @@ -181,21 +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, - HoleskySpecProvider.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 fa61a1c7971e..991a2aed820f 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; @@ -20,7 +19,7 @@ public class ChainSpecLoaderTest { private static ChainSpec LoadChainSpec(string path) { - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance); var chainSpec = loader.LoadEmbeddedOrFromFile(path); return chainSpec; } @@ -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 f88647fade18..f5f04ba300e2 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; @@ -13,7 +12,6 @@ using Nethermind.Consensus.AuRa.Contracts; using Nethermind.Consensus.AuRa.Contracts.DataStore; using Nethermind.Core; -using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; using Nethermind.Logging; using NSubstitute; @@ -91,7 +89,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 +107,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 +126,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 +144,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 +167,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 +190,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 +226,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 9624eea6e505..9d1f68f70926 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 }; @@ -79,18 +79,24 @@ public void combines_contract_and_local_data_correctly() testCase.ContractDataStore.GetItemsFromContractAtBlock(blockHeader).Should().BeEquivalentTo(expected.Cast()); Block secondBlock = Build.A.Block.WithHeader(Build.A.BlockHeader.WithNumber(3).WithHash(TestItem.KeccakB).WithParentHash(TestItem.KeccakC).TestObject).TestObject; - expected = new[] { TestItem.AddressC, TestItem.AddressB }; + expected = [TestItem.AddressC, TestItem.AddressB]; testCase.DataContract.GetAllItemsFromBlock(secondBlock.Header).Returns(new[] { TestItem.AddressB }); testCase.ContractDataStore.GetItemsFromContractAtBlock(blockHeader); testCase.BlockTree.NewHeadBlock += Raise.EventWith(new BlockEventArgs(secondBlock)); - testCase.ContractDataStore.GetItemsFromContractAtBlock(secondBlock.Header).Should().BeEquivalentTo(expected.Cast()); + Assert.That( + () => testCase.ContractDataStore.GetItemsFromContractAtBlock(secondBlock.Header), + Is.EquivalentTo(expected.Cast()).After(200, 20) + ); localDataSource.Data.Returns(new[] { TestItem.AddressC, TestItem.AddressD }); - expected = new[] { TestItem.AddressC, TestItem.AddressD, TestItem.AddressB }; + expected = [TestItem.AddressC, TestItem.AddressD, TestItem.AddressB]; localDataSource.Changed += Raise.Event(); - testCase.ContractDataStore.GetItemsFromContractAtBlock(secondBlock.Header).Should().BeEquivalentTo(expected.Cast()); + Assert.That( + () => testCase.ContractDataStore.GetItemsFromContractAtBlock(secondBlock.Header), + Is.EquivalentTo(expected.Cast()).After(200, 20) + ); } protected override TestCase BuildTestCase(IComparer keyComparer = null, IComparer valueComparer = null) => diff --git a/src/Nethermind/Nethermind.AuRa.Test/Contract/TestContractBlockchain.cs b/src/Nethermind/Nethermind.AuRa.Test/Contract/TestContractBlockchain.cs index ed7b1640dce6..48700223bb77 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Contract/TestContractBlockchain.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Contract/TestContractBlockchain.cs @@ -10,6 +10,7 @@ using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Core.Test.Blockchain; +using Nethermind.Logging; using Nethermind.Serialization.Json; using Nethermind.Specs.ChainSpecStyle; @@ -28,7 +29,7 @@ protected TestContractBlockchain() { (ChainSpec ChainSpec, ISpecProvider SpecProvider) GetSpecProvider() { - ChainSpecLoader loader = new(new EthereumJsonSerializer()); + ChainSpecLoader loader = new(new EthereumJsonSerializer(), LimboLogs.Instance); string name = string.IsNullOrEmpty(testSuffix) ? $"{typeof(TTestClass).FullName}.json" : $"{typeof(TTestClass).FullName}.{testSuffix}.json"; using Stream? stream = typeof(TTestClass).Assembly.GetManifestResourceStream(name); ChainSpec chainSpec = loader.Load(stream); diff --git a/src/Nethermind/Nethermind.AuRa.Test/Contract/TxPriorityContractTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Contract/TxPriorityContractTests.cs index f993ded0501e..9f0c4ec6dd4b 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Contract/TxPriorityContractTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Contract/TxPriorityContractTests.cs @@ -314,12 +314,13 @@ await AddBlock( ); } - public override async Task AddBlock(params Transaction[] transactions) + public override async Task AddBlock(params Transaction[] transactions) { - await base.AddBlock(transactions); + var b = await base.AddBlock(transactions); // ContractDataStore track item from block async. await Task.Delay(100); + return b; } private Transaction[] SignTransactions(IEthereumEcdsa ecdsa, PrivateKey key, UInt256 baseNonce, params Transaction[] transactions) diff --git a/src/Nethermind/Nethermind.AuRa.Test/Contract/ValidatorContractTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Contract/ValidatorContractTests.cs index 6674d8119491..706462c43ec8 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Contract/ValidatorContractTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Contract/ValidatorContractTests.cs @@ -17,7 +17,6 @@ using Nethermind.Evm.TransactionProcessing; using Nethermind.Logging; using Nethermind.Evm.State; -using Nethermind.State; using NSubstitute; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.AuRa.Test/Reward/AuRaRewardCalculatorTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Reward/AuRaRewardCalculatorTests.cs index 34065ffb344c..fc227248651d 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Reward/AuRaRewardCalculatorTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Reward/AuRaRewardCalculatorTests.cs @@ -20,7 +20,6 @@ using Nethermind.Int256; using NSubstitute; using NUnit.Framework; -using Nethermind.Evm; namespace Nethermind.AuRa.Test.Reward { diff --git a/src/Nethermind/Nethermind.AuRa.Test/Transactions/TxCertifierFilterTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Transactions/TxCertifierFilterTests.cs index a2e4c6ae271e..2dc25d19fe66 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Transactions/TxCertifierFilterTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Transactions/TxCertifierFilterTests.cs @@ -8,24 +8,14 @@ using FluentAssertions; using Nethermind.Abi; using Nethermind.AuRa.Test.Contract; -using Nethermind.Blockchain; -using Nethermind.Blockchain.BeaconBlockRoot; -using Nethermind.Consensus.AuRa; using Nethermind.Consensus.AuRa.Contracts; using Nethermind.Consensus.AuRa.Transactions; -using Nethermind.Consensus.ExecutionRequests; -using Nethermind.Consensus.Processing; -using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Transactions; -using Nethermind.Consensus.Validators; -using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Core.Test.Blockchain; using Nethermind.Core.Test.Builders; -using Nethermind.Evm.TransactionProcessing; using Nethermind.Logging; -using Nethermind.Evm.State; using Nethermind.TxPool; using NSubstitute; using NSubstitute.ExceptionExtensions; @@ -120,14 +110,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 188dcc4bce6e..36cb7315565e 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs @@ -28,11 +28,9 @@ using NSubstitute; using NUnit.Framework; using BlockTree = Nethermind.Blockchain.BlockTree; -using Nethermind.Evm; using System.Text.Json; using Nethermind.Blockchain.Tracing; using Nethermind.Consensus.Processing; -using Nethermind.State; namespace Nethermind.AuRa.Test.Validators; @@ -111,7 +109,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(); @@ -568,12 +566,12 @@ static IEnumerable GetAllBlocks(BlockTree bt) return new[] { Build.A.LogEntry.WithAddress(_contractAddress) - .WithData(new[] {(byte) (block.Number * 10 + i++)}) + .WithData([(byte) (block.Number * 10 + i++)]) .WithTopics(_validatorContract.AbiDefinition.Events[ValidatorContract.InitiateChange].GetHash(), block.ParentHash) .TestObject }; }) - .OfChainLength(9, 0, 0, false, validators); + .OfChainLength(9, blockBeneficiaries: validators); BlockTree blockTree = blockTreeBuilder.TestObject; SetupInitialValidators(blockTree.Head?.Header, blockTree.FindHeader(blockTree.Head?.ParentHash, BlockTreeLookupOptions.None), validators); diff --git a/src/Nethermind/Nethermind.AuRa.Test/Validators/MultiValidatorTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Validators/MultiValidatorTests.cs index 45a7bda8c9a9..b3e7b235a9c5 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.Runner/Program.cs b/src/Nethermind/Nethermind.Benchmark.Runner/Program.cs index e1bd7ca375ee..a22f9c03bfc7 100644 --- a/src/Nethermind/Nethermind.Benchmark.Runner/Program.cs +++ b/src/Nethermind/Nethermind.Benchmark.Runner/Program.cs @@ -12,7 +12,6 @@ using System.Linq; using BenchmarkDotNet.Toolchains.InProcess.NoEmit; using BenchmarkDotNet.Columns; -using Nethermind.Benchmarks.State; using Nethermind.Precompiles.Benchmark; namespace Nethermind.Benchmark.Runner diff --git a/src/Nethermind/Nethermind.Benchmark/Core/ByteArrayToHexBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Core/ByteArrayToHexBenchmarks.cs index aa4ab3250b80..d52cba7a7288 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/FastHashBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Core/FastHashBenchmarks.cs new file mode 100644 index 000000000000..a6481332d087 --- /dev/null +++ b/src/Nethermind/Nethermind.Benchmark/Core/FastHashBenchmarks.cs @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Runtime.InteropServices; +using BenchmarkDotNet.Attributes; +using Nethermind.Core.Extensions; + +namespace Nethermind.Benchmarks.Core; + +[ShortRunJob] +[DisassemblyDiagnoser] +[MemoryDiagnoser] +public class FastHashBenchmarks +{ + private byte[] _data = null!; + + [Params(16, 20, 32, 64, 128, 256, 512, 1024)] + public int Size; + + [GlobalSetup] + public void Setup() + { + _data = new byte[Size]; + Random.Shared.NextBytes(_data); + } + + [Benchmark(Baseline = true)] + public int FastHash() + { + return ((ReadOnlySpan)_data).FastHash(); + } + + [Benchmark] + public int FastHashAes() + { + ref byte start = ref MemoryMarshal.GetReference(_data); + return SpanExtensions.FastHashAesX64(ref start, _data.Length, SpanExtensions.ComputeSeed(_data.Length)); + } + + [Benchmark] + public int FastHashCrc() + { + ref byte start = ref MemoryMarshal.GetReference(_data); + return SpanExtensions.FastHashCrc(ref start, _data.Length, SpanExtensions.ComputeSeed(_data.Length)); + } +} diff --git a/src/Nethermind/Nethermind.Benchmark/Core/Keccak256Benchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Core/Keccak256Benchmarks.cs index 85d737f14a6c..7e0c156295bc 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 fd511255d29c..0b4b149501c7 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 f9e00b8dc299..ab3861cf03fb 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 2ec1cd60b184..e510a78b6a44 100644 --- a/src/Nethermind/Nethermind.Benchmark/Core/RecoverSignaturesBenchmark.cs +++ b/src/Nethermind/Nethermind.Benchmark/Core/RecoverSignaturesBenchmark.cs @@ -10,9 +10,6 @@ using System; using System.Collections.Generic; using Nethermind.Consensus.Processing; -using Nethermind.TxPool; -using Nethermind.Int256; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; @@ -162,13 +159,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/Core/SeqlockCacheBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Core/SeqlockCacheBenchmarks.cs new file mode 100644 index 000000000000..9ba71e47e39e --- /dev/null +++ b/src/Nethermind/Nethermind.Benchmark/Core/SeqlockCacheBenchmarks.cs @@ -0,0 +1,345 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +#nullable enable + +using System; +using System.Collections.Concurrent; +using BenchmarkDotNet.Attributes; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Int256; + +namespace Nethermind.Benchmarks.Core; + +[MemoryDiagnoser] +[DisassemblyDiagnoser(maxDepth: 3)] +public class SeqlockCacheBenchmarks +{ + private SeqlockCache _seqlockCache = null!; + private ConcurrentDictionary _concurrentDict = null!; + + private StorageCell[] _keys = null!; + private byte[][] _values = null!; + private StorageCell _missKey; + + [Params(1000)] + public int KeyCount { get; set; } + + [GlobalSetup] + public void Setup() + { + _seqlockCache = new SeqlockCache(); + _concurrentDict = new ConcurrentDictionary(); + + _keys = new StorageCell[KeyCount]; + _values = new byte[KeyCount][]; + + var random = new Random(42); + for (int i = 0; i < KeyCount; i++) + { + var addressBytes = new byte[20]; + random.NextBytes(addressBytes); + var address = new Address(addressBytes); + var index = new UInt256((ulong)i); + + _keys[i] = new StorageCell(address, index); + _values[i] = new byte[32]; + random.NextBytes(_values[i]); + + // Pre-populate both caches + _seqlockCache.Set(in _keys[i], _values[i]); + _concurrentDict[_keys[i]] = _values[i]; + } + + // Create a key that won't be in the cache + var missAddressBytes = new byte[20]; + random.NextBytes(missAddressBytes); + _missKey = new StorageCell(new Address(missAddressBytes), UInt256.MaxValue); + } + + // ==================== TryGetValue (Hit) ==================== + + [Benchmark(Baseline = true)] + public bool SeqlockCache_TryGetValue_Hit() + { + return _seqlockCache.TryGetValue(in _keys[500], out _); + } + + [Benchmark] + public bool ConcurrentDict_TryGetValue_Hit() + { + return _concurrentDict.TryGetValue(_keys[500], out _); + } + + // ==================== TryGetValue (Miss) ==================== + + [Benchmark] + public bool SeqlockCache_TryGetValue_Miss() + { + return _seqlockCache.TryGetValue(in _missKey, out _); + } + + [Benchmark] + public bool ConcurrentDict_TryGetValue_Miss() + { + return _concurrentDict.TryGetValue(_missKey, out _); + } + + // ==================== Set (Existing Key) ==================== + + [Benchmark] + public void SeqlockCache_Set_Existing() + { + _seqlockCache.Set(in _keys[500], _values[500]); + } + + [Benchmark] + public void ConcurrentDict_Set_Existing() + { + _concurrentDict[_keys[500]] = _values[500]; + } + + // ==================== GetOrAdd (Hit) ==================== + + [Benchmark] + public byte[]? SeqlockCache_GetOrAdd_Hit() + { + return _seqlockCache.GetOrAdd(in _keys[500], static (in StorageCell _) => new byte[32]); + } + + [Benchmark] + public byte[] ConcurrentDict_GetOrAdd_Hit() + { + return _concurrentDict.GetOrAdd(_keys[500], static _ => new byte[32]); + } + + // ==================== GetOrAdd (Miss - measures factory overhead) ==================== + + private int _missCounter; + + [Benchmark] + public byte[]? SeqlockCache_GetOrAdd_Miss() + { + // Use incrementing key to always miss + var key = new StorageCell(_keys[0].Address, new UInt256((ulong)(KeyCount + _missCounter++))); + return _seqlockCache.GetOrAdd(in key, static (in StorageCell _) => new byte[32]); + } + + [Benchmark] + public byte[] ConcurrentDict_GetOrAdd_Miss() + { + var key = new StorageCell(_keys[0].Address, new UInt256((ulong)(KeyCount + _missCounter++))); + return _concurrentDict.GetOrAdd(key, static _ => new byte[32]); + } +} + +/// +/// Benchmark comparing read-heavy workloads (90% reads, 10% writes) +/// +[MemoryDiagnoser] +public class SeqlockCacheMixedWorkloadBenchmarks +{ + private SeqlockCache _seqlockCache = null!; + private ConcurrentDictionary _concurrentDict = null!; + + private StorageCell[] _keys = null!; + private byte[][] _values = null!; + + private const int KeyCount = 10000; + private const int OperationsPerInvoke = 1000; + + [GlobalSetup] + public void Setup() + { + _seqlockCache = new SeqlockCache(); + _concurrentDict = new ConcurrentDictionary(); + + _keys = new StorageCell[KeyCount]; + _values = new byte[KeyCount][]; + + var random = new Random(42); + for (int i = 0; i < KeyCount; i++) + { + var addressBytes = new byte[20]; + random.NextBytes(addressBytes); + var address = new Address(addressBytes); + var index = new UInt256((ulong)i); + + _keys[i] = new StorageCell(address, index); + _values[i] = new byte[32]; + random.NextBytes(_values[i]); + + // Pre-populate both caches + _seqlockCache.Set(in _keys[i], _values[i]); + _concurrentDict[_keys[i]] = _values[i]; + } + } + + [Benchmark(Baseline = true, OperationsPerInvoke = OperationsPerInvoke)] + public int SeqlockCache_MixedWorkload_90Read_10Write() + { + int hits = 0; + for (int i = 0; i < OperationsPerInvoke; i++) + { + int keyIndex = i % KeyCount; + if (i % 10 == 0) + { + // 10% writes + _seqlockCache.Set(in _keys[keyIndex], _values[keyIndex]); + } + else + { + // 90% reads + if (_seqlockCache.TryGetValue(in _keys[keyIndex], out _)) + hits++; + } + } + return hits; + } + + [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] + public int ConcurrentDict_MixedWorkload_90Read_10Write() + { + int hits = 0; + for (int i = 0; i < OperationsPerInvoke; i++) + { + int keyIndex = i % KeyCount; + if (i % 10 == 0) + { + // 10% writes + _concurrentDict[_keys[keyIndex]] = _values[keyIndex]; + } + else + { + // 90% reads + if (_concurrentDict.TryGetValue(_keys[keyIndex], out _)) + hits++; + } + } + return hits; + } + + [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] + public int SeqlockCache_ReadOnly() + { + int hits = 0; + for (int i = 0; i < OperationsPerInvoke; i++) + { + int keyIndex = i % KeyCount; + if (_seqlockCache.TryGetValue(in _keys[keyIndex], out _)) + hits++; + } + return hits; + } + + [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] + public int ConcurrentDict_ReadOnly() + { + int hits = 0; + for (int i = 0; i < OperationsPerInvoke; i++) + { + int keyIndex = i % KeyCount; + if (_concurrentDict.TryGetValue(_keys[keyIndex], out _)) + hits++; + } + return hits; + } +} + +/// +/// Benchmark measuring effective hit rate after populating with N keys. +/// This directly measures the impact of collision rate. +/// +public class SeqlockCacheHitRateBenchmarks +{ + private SeqlockCache _seqlockCache = null!; + private StorageCell[] _keys = null!; + private byte[][] _values = null!; + + [Params(1000, 5000, 10000, 20000)] + public int KeyCount { get; set; } + + [GlobalSetup] + public void Setup() + { + _seqlockCache = new SeqlockCache(); + _keys = new StorageCell[KeyCount]; + _values = new byte[KeyCount][]; + + var random = new Random(42); + for (int i = 0; i < KeyCount; i++) + { + var addressBytes = new byte[20]; + random.NextBytes(addressBytes); + _keys[i] = new StorageCell(new Address(addressBytes), new UInt256((ulong)i)); + _values[i] = new byte[32]; + random.NextBytes(_values[i]); + _seqlockCache.Set(in _keys[i], _values[i]); + } + } + + [Benchmark] + public double MeasureHitRate() + { + int hits = 0; + for (int i = 0; i < KeyCount; i++) + { + if (_seqlockCache.TryGetValue(in _keys[i], out byte[]? val) && ReferenceEquals(val, _values[i])) + hits++; + } + return (double)hits / KeyCount * 100; + } +} + +[MemoryDiagnoser] +public class SeqlockCacheCallSiteBenchmarks +{ + private SeqlockCache _cache = null!; + private SeqlockCache.ValueFactory _cachedFactory = null!; + private StorageCell _key; + private byte[] _value = null!; + + [GlobalSetup] + public void Setup() + { + _cache = new SeqlockCache(); + + byte[] addressBytes = new byte[20]; + new Random(123).NextBytes(addressBytes); + _key = new StorageCell(new Address(addressBytes), UInt256.One); + _value = new byte[32]; + + _cache.Set(in _key, _value); + _cachedFactory = LoadFromBackingStore; + } + + [Benchmark(Baseline = true)] + public byte[]? GetOrAdd_Hit_PerCallMethodGroup() + { + return _cache.GetOrAdd(in _key, LoadFromBackingStore); + } + + [Benchmark] + public byte[]? GetOrAdd_Hit_CachedDelegate() + { + return _cache.GetOrAdd(in _key, _cachedFactory); + } + + [Benchmark] + public bool TryGetValue_WithIn() + { + return _cache.TryGetValue(in _key, out _); + } + + [Benchmark] + public bool TryGetValue_WithoutIn() + { + return _cache.TryGetValue(_key, out _); + } + + private byte[] LoadFromBackingStore(in StorageCell _) + { + return _value; + } +} diff --git a/src/Nethermind/Nethermind.Benchmark/Evm/BitwiseAndBenchmark.cs b/src/Nethermind/Nethermind.Benchmark/Evm/BitwiseAndBenchmark.cs index b89833f126d3..2b95807b74af 100644 --- a/src/Nethermind/Nethermind.Benchmark/Evm/BitwiseAndBenchmark.cs +++ b/src/Nethermind/Nethermind.Benchmark/Evm/BitwiseAndBenchmark.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -24,9 +25,9 @@ public void Setup() [Benchmark(Baseline = true)] public void Current() { - ref var refA = ref MemoryMarshal.AsRef(a); - ref var refB = ref MemoryMarshal.AsRef(b); - ref var refBuffer = ref MemoryMarshal.AsRef(c); + ref var refA = ref MemoryMarshal.AsRef(a.AsSpan()); + ref var refB = ref MemoryMarshal.AsRef(b.AsSpan()); + ref var refBuffer = ref MemoryMarshal.AsRef(c.AsSpan()); refBuffer = refA & refB; Unsafe.Add(ref refBuffer, 1) = Unsafe.Add(ref refA, 1) & Unsafe.Add(ref refB, 1); diff --git a/src/Nethermind/Nethermind.Benchmark/Evm/BitwiseNotBenchmark.cs b/src/Nethermind/Nethermind.Benchmark/Evm/BitwiseNotBenchmark.cs index ce721090a609..699084278c77 100644 --- a/src/Nethermind/Nethermind.Benchmark/Evm/BitwiseNotBenchmark.cs +++ b/src/Nethermind/Nethermind.Benchmark/Evm/BitwiseNotBenchmark.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -30,8 +31,8 @@ public void Setup() [Benchmark(Baseline = true)] public void Current() { - ref var refA = ref MemoryMarshal.AsRef(a); - ref var refBuffer = ref MemoryMarshal.AsRef(c); + ref var refA = ref MemoryMarshal.AsRef(a.AsSpan()); + ref var refBuffer = ref MemoryMarshal.AsRef(c.AsSpan()); refBuffer = ~refA; Unsafe.Add(ref refBuffer, 1) = ~Unsafe.Add(ref refA, 1); diff --git a/src/Nethermind/Nethermind.Benchmark/Evm/BitwiseOrBenchmark.cs b/src/Nethermind/Nethermind.Benchmark/Evm/BitwiseOrBenchmark.cs index 8c69398724e2..d3de5db1f93a 100644 --- a/src/Nethermind/Nethermind.Benchmark/Evm/BitwiseOrBenchmark.cs +++ b/src/Nethermind/Nethermind.Benchmark/Evm/BitwiseOrBenchmark.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -24,9 +25,9 @@ public void Setup() [Benchmark(Baseline = true)] public void Current() { - ref var refA = ref MemoryMarshal.AsRef(a); - ref var refB = ref MemoryMarshal.AsRef(b); - ref var refBuffer = ref MemoryMarshal.AsRef(c); + ref var refA = ref MemoryMarshal.AsRef(a.AsSpan()); + ref var refB = ref MemoryMarshal.AsRef(b.AsSpan()); + ref var refBuffer = ref MemoryMarshal.AsRef(c.AsSpan()); refBuffer = refA | refB; Unsafe.Add(ref refBuffer, 1) = Unsafe.Add(ref refA, 1) | Unsafe.Add(ref refB, 1); diff --git a/src/Nethermind/Nethermind.Benchmark/Evm/BitwiseXorBenchmark.cs b/src/Nethermind/Nethermind.Benchmark/Evm/BitwiseXorBenchmark.cs index 99e780f69b09..cd53d13d52d3 100644 --- a/src/Nethermind/Nethermind.Benchmark/Evm/BitwiseXorBenchmark.cs +++ b/src/Nethermind/Nethermind.Benchmark/Evm/BitwiseXorBenchmark.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -24,9 +25,9 @@ public void Setup() [Benchmark(Baseline = true)] public void Current() { - ref var refA = ref MemoryMarshal.AsRef(a); - ref var refB = ref MemoryMarshal.AsRef(b); - ref var refBuffer = ref MemoryMarshal.AsRef(c); + ref var refA = ref MemoryMarshal.AsRef(a.AsSpan()); + ref var refB = ref MemoryMarshal.AsRef(b.AsSpan()); + ref var refBuffer = ref MemoryMarshal.AsRef(c.AsSpan()); refBuffer = refA ^ refB; Unsafe.Add(ref refBuffer, 1) = Unsafe.Add(ref refA, 1) ^ Unsafe.Add(ref refB, 1); diff --git a/src/Nethermind/Nethermind.Benchmark/Evm/Blake2Benchmark.cs b/src/Nethermind/Nethermind.Benchmark/Evm/Blake2Benchmark.cs index 2bbfe76480c3..55ce475eb35f 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 a2f6ac7b7fee..be9e86212db8 100644 --- a/src/Nethermind/Nethermind.Benchmark/Evm/MemoryCostBenchmark.cs +++ b/src/Nethermind/Nethermind.Benchmark/Evm/MemoryCostBenchmark.cs @@ -9,8 +9,7 @@ namespace Nethermind.Benchmarks.Evm { public class MemoryCostBenchmark { - private IEvmMemory _current = new EvmPooledMemory(); - private IEvmMemory _improved = new EvmPooledMemory(); + private readonly IEvmMemory _current = new EvmPooledMemory(); private UInt256 _location; private UInt256 _length; @@ -36,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 47d883bbd2e8..4fe68806389e 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/Rlp/RlpDecodeBlockBenchmark.cs b/src/Nethermind/Nethermind.Benchmark/Rlp/RlpDecodeBlockBenchmark.cs index 99f343246f41..6c96c94ef7f5 100644 --- a/src/Nethermind/Nethermind.Benchmark/Rlp/RlpDecodeBlockBenchmark.cs +++ b/src/Nethermind/Nethermind.Benchmark/Rlp/RlpDecodeBlockBenchmark.cs @@ -7,7 +7,6 @@ using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Int256; -using Nethermind.Logging; namespace Nethermind.Benchmarks.Rlp { diff --git a/src/Nethermind/Nethermind.Benchmark/Rlp/RlpEncodeBlockBenchmark.cs b/src/Nethermind/Nethermind.Benchmark/Rlp/RlpEncodeBlockBenchmark.cs index 12324708b79c..786e6d7159ca 100644 --- a/src/Nethermind/Nethermind.Benchmark/Rlp/RlpEncodeBlockBenchmark.cs +++ b/src/Nethermind/Nethermind.Benchmark/Rlp/RlpEncodeBlockBenchmark.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Linq; using BenchmarkDotNet.Attributes; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -10,7 +9,6 @@ using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Int256; -using Nethermind.Logging; using Nethermind.Serialization.Rlp; namespace Nethermind.Benchmarks.Rlp diff --git a/src/Nethermind/Nethermind.Benchmark/Rlp/RlpEncodeHeaderBenchmark.cs b/src/Nethermind/Nethermind.Benchmark/Rlp/RlpEncodeHeaderBenchmark.cs index eb30eb284efa..482fae3aea90 100644 --- a/src/Nethermind/Nethermind.Benchmark/Rlp/RlpEncodeHeaderBenchmark.cs +++ b/src/Nethermind/Nethermind.Benchmark/Rlp/RlpEncodeHeaderBenchmark.cs @@ -2,14 +2,12 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Linq; using BenchmarkDotNet.Attributes; using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Int256; -using Nethermind.Logging; using Nethermind.Serialization.Rlp; namespace Nethermind.Benchmarks.Rlp diff --git a/src/Nethermind/Nethermind.Benchmark/Rlp/RlpEncodeLongBenchmark.cs b/src/Nethermind/Nethermind.Benchmark/Rlp/RlpEncodeLongBenchmark.cs index 8fd13561638f..b7b8acdc2af9 100644 --- a/src/Nethermind/Nethermind.Benchmark/Rlp/RlpEncodeLongBenchmark.cs +++ b/src/Nethermind/Nethermind.Benchmark/Rlp/RlpEncodeLongBenchmark.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Linq; using BenchmarkDotNet.Attributes; using Nethermind.Core.Extensions; diff --git a/src/Nethermind/Nethermind.Benchmark/Rlp/RlpEncodeTransactionBenchmark.cs b/src/Nethermind/Nethermind.Benchmark/Rlp/RlpEncodeTransactionBenchmark.cs index 5fa30720434b..e3f17b96c559 100644 --- a/src/Nethermind/Nethermind.Benchmark/Rlp/RlpEncodeTransactionBenchmark.cs +++ b/src/Nethermind/Nethermind.Benchmark/Rlp/RlpEncodeTransactionBenchmark.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Linq; using BenchmarkDotNet.Attributes; using Nethermind.Core; using Nethermind.Core.Extensions; diff --git a/src/Nethermind/Nethermind.Benchmark/Scheduler/BackgroundTaskSchedulerBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Scheduler/BackgroundTaskSchedulerBenchmarks.cs new file mode 100644 index 000000000000..5ab4d42ecf03 --- /dev/null +++ b/src/Nethermind/Nethermind.Benchmark/Scheduler/BackgroundTaskSchedulerBenchmarks.cs @@ -0,0 +1,181 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Scheduler; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.TxPool; + +namespace Nethermind.Benchmarks.Scheduler; + +/// +/// Benchmarks the throughput of the BackgroundTaskScheduler under concurrent task +/// scheduling with periodic block-processing pauses — the scenario that caused +/// the "Background task queue is full" issue on synced nodes. +/// +[MemoryDiagnoser] +[SimpleJob(warmupCount: 2, iterationCount: 5)] +public class BackgroundTaskSchedulerBenchmarks +{ + private StubBranchProcessor _branchProcessor = null!; + private StubChainHeadInfoProvider _chainHeadInfo = null!; + + [Params(1024, 2048)] + public int Capacity { get; set; } + + [Params(2)] + public int Concurrency { get; set; } + + [Params(50)] + public int BlockProcessingDurationMs { get; set; } + + [Params(5)] + public int BlockProcessingCycles { get; set; } + + [GlobalSetup] + public void Setup() + { + _branchProcessor = new StubBranchProcessor(); + _chainHeadInfo = new StubChainHeadInfoProvider(); + } + + /// + /// Simulates the real-world scenario: a background producer keeps scheduling tasks + /// while block-processing cycles pause and resume execution. Measures total wall-clock + /// time for scheduling + draining all tasks across several block-processing windows. + /// + [Benchmark] + public async Task ScheduleAndDrainDuringBlockProcessing() + { + await using BackgroundTaskScheduler scheduler = new( + _branchProcessor, _chainHeadInfo, Concurrency, Capacity, LimboLogs.Instance); + + int totalScheduled = 0; + int totalExecuted = 0; + int totalDropped = 0; + + for (int cycle = 0; cycle < BlockProcessingCycles; cycle++) + { + // Simulate block arriving — cancels current tasks, pauses non-expired ones + _branchProcessor.RaiseBlocksProcessing(); + + // Schedule a burst of tasks while block is being processed + int batchSize = Capacity / 2; + for (int i = 0; i < batchSize; i++) + { + bool accepted = scheduler.TryScheduleTask(i, (_, token) => + { + Interlocked.Increment(ref totalExecuted); + return Task.CompletedTask; + }, TimeSpan.FromMilliseconds(BlockProcessingDurationMs + 100)); + + if (accepted) + Interlocked.Increment(ref totalScheduled); + else + Interlocked.Increment(ref totalDropped); + } + + // Simulate block processing time + await Task.Delay(BlockProcessingDurationMs); + + // Block done — resume normal task execution + _branchProcessor.RaiseBlockProcessed(); + + // Wait for all scheduled tasks to drain before next cycle + SpinWait spin = default; + while (Volatile.Read(ref totalExecuted) < Volatile.Read(ref totalScheduled)) + { + spin.SpinOnce(); + if (spin.Count % 100 == 0) + await Task.Yield(); + } + } + } + + /// + /// Measures pure scheduling throughput without block-processing interruptions. + /// Useful as a baseline to compare against . + /// + [Benchmark(Baseline = true)] + public async Task ScheduleAndDrainWithoutBlockProcessing() + { + await using BackgroundTaskScheduler scheduler = new( + _branchProcessor, _chainHeadInfo, Concurrency, Capacity, LimboLogs.Instance); + + int totalScheduled = 0; + int totalExecuted = 0; + + int totalTasks = (Capacity / 2) * BlockProcessingCycles; + for (int i = 0; i < totalTasks; i++) + { + bool accepted = scheduler.TryScheduleTask(i, (_, _) => + { + Interlocked.Increment(ref totalExecuted); + return Task.CompletedTask; + }); + if (accepted) + Interlocked.Increment(ref totalScheduled); + } + + SpinWait spin = default; + while (Volatile.Read(ref totalExecuted) < Volatile.Read(ref totalScheduled)) + { + spin.SpinOnce(); + if (spin.Count % 100 == 0) + await Task.Yield(); + } + } + + /// + /// Minimal stub for to expose events without any real block processing. + /// + private sealed class StubBranchProcessor : IBranchProcessor + { + public event EventHandler? BlockProcessed; + public event EventHandler? BlocksProcessing; +#pragma warning disable CS0067 // Event is never used + public event EventHandler? BlockProcessing; +#pragma warning restore CS0067 + + public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlocks, + ProcessingOptions processingOptions, IBlockTracer blockTracer, CancellationToken token = default) + => []; + + public void RaiseBlocksProcessing() => + BlocksProcessing?.Invoke(this, new BlocksProcessingEventArgs([])); + + public void RaiseBlockProcessed() => + BlockProcessed?.Invoke(this, new BlockProcessedEventArgs(null!, null!)); + } + + /// + /// Minimal stub for — reports node as not syncing. + /// + private sealed class StubChainHeadInfoProvider : IChainHeadInfoProvider + { + public IChainHeadSpecProvider SpecProvider => null!; + public IReadOnlyStateProvider ReadOnlyStateProvider => null!; + public long HeadNumber => 0; + public long? BlockGasLimit => null; + public UInt256 CurrentBaseFee => UInt256.Zero; + public UInt256 CurrentFeePerBlobGas => UInt256.Zero; + public ProofVersion CurrentProofVersion => ProofVersion.V0; + public bool IsSyncing => false; + public bool IsProcessingBlock => false; +#pragma warning disable CS0067 // Event is never used + public event EventHandler? HeadChanged; +#pragma warning restore CS0067 + } +} diff --git a/src/Nethermind/Nethermind.Benchmark/State/StorageCellBenchmark.cs b/src/Nethermind/Nethermind.Benchmark/State/StorageCellBenchmark.cs index 6cbfe7775e81..febbe1762285 100644 --- a/src/Nethermind/Nethermind.Benchmark/State/StorageCellBenchmark.cs +++ b/src/Nethermind/Nethermind.Benchmark/State/StorageCellBenchmark.cs @@ -4,7 +4,6 @@ using System; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; -using Nethermind.Evm.State; using Nethermind.Core; using Nethermind.Int256; using Nethermind.Evm.Tracing.State; diff --git a/src/Nethermind/Nethermind.Benchmark/Store/PatriciaTreeBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Store/PatriciaTreeBenchmarks.cs index 9061fff098b4..174a7d520a86 100644 --- a/src/Nethermind/Nethermind.Benchmark/Store/PatriciaTreeBenchmarks.cs +++ b/src/Nethermind/Nethermind.Benchmark/Store/PatriciaTreeBenchmarks.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; using Nethermind.Core; using Nethermind.Core.Collections; @@ -11,15 +10,12 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; -using Nethermind.Core.Threading; using Nethermind.Db; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.State; using Nethermind.Trie; using Nethermind.Trie.Pruning; -using NSubstitute.Routing.Handlers; -using NUnit.Framework; namespace Nethermind.Benchmarks.Store { @@ -138,7 +134,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); @@ -433,7 +429,7 @@ public void LargeBulkSetPreSorted() StateTree tempTree = new StateTree(trieStore, NullLogManager.Instance); tempTree.RootHash = Keccak.EmptyTreeHash; - using ArrayPoolList bulkSet = new ArrayPoolList(_largerEntryCount); + using ArrayPoolListRef bulkSet = new(_largerEntryCount); for (int i = 0; i < _largerEntryCount; i++) { @@ -524,7 +520,7 @@ private void DoBulkSetRepeatedly(int repeatBatchSize) var originalRootHash = Keccak.EmptyTreeHash; tempTree.RootHash = Keccak.EmptyTreeHash; - using ArrayPoolList bulkSet = new ArrayPoolList(_repeatedlyFactor); + using ArrayPoolListRef bulkSet = new(_repeatedlyFactor); for (int i = 0; i < _largerEntryCount; i++) { if (i % repeatBatchSize == 0) @@ -551,7 +547,7 @@ private void DoBulkSetRepeatedlyNoParallel(int repeatBatchSize) var originalRootHash = Keccak.EmptyTreeHash; tempTree.RootHash = Keccak.EmptyTreeHash; - using ArrayPoolList bulkSet = new ArrayPoolList(_repeatedlyFactor); + using ArrayPoolListRef bulkSet = new(_repeatedlyFactor); for (int i = 0; i < _largerEntryCount; i++) { if (i % repeatBatchSize == 0) diff --git a/src/Nethermind/Nethermind.Benchmark/Store/WorldStateBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Store/WorldStateBenchmarks.cs index 8a37c31c0c31..d71f3d257d33 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; @@ -13,7 +14,6 @@ using Nethermind.Evm.State; using Nethermind.Int256; using Nethermind.Specs.Forks; -using Nethermind.State; namespace Nethermind.Benchmarks.Store; @@ -44,7 +44,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 eba2ada07de7..73a57efc3928 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 dc13573519c6..6b34eab9585d 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/BeaconBlockRootHandlerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs index a82668f7d741..abbd0cb530af 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs @@ -5,10 +5,8 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Eip2930; -using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; -using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; using Nethermind.Int256; @@ -19,6 +17,8 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class BeaconBlockRootHandlerTests { private BeaconBlockRootHandler _beaconBlockRootHandler; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockFinderExtensionsTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockFinderExtensionsTests.cs index 7665e94c284e..c5dd1954b463 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockFinderExtensionsTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockFinderExtensionsTests.cs @@ -6,11 +6,14 @@ using Nethermind.Core; using Nethermind.Core.Test.Builders; using Nethermind.Int256; +using Nethermind.JsonRpc; +using Nethermind.JsonRpc.Modules; using NSubstitute; using NUnit.Framework; namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class BlockFinderExtensionsTests { [Test, MaxTime(Timeout.MaxTestTime)] @@ -28,4 +31,49 @@ public void Can_upgrade_maybe_parent() blockFinder.FindParentHeader(child, BlockTreeLookupOptions.TotalDifficultyNotNeeded).Should().Be(parent); blockFinder.FindParentHeader(child, BlockTreeLookupOptions.None)!.TotalDifficulty.Should().Be((UInt256?)UInt256.One); } + + [TestCase(BlockParameterType.Latest, "latest")] + [TestCase(BlockParameterType.Earliest, "earliest")] + [TestCase(BlockParameterType.Pending, "pending")] + [TestCase(BlockParameterType.Finalized, "finalized")] + [TestCase(BlockParameterType.Safe, "safe")] + [MaxTime(Timeout.MaxTestTime)] + public void BlockParameter_ToString_ReturnsLowercaseTypeName(BlockParameterType type, string expected) + { + new BlockParameter(type).ToString().Should().Be(expected); + } + + [Test, MaxTime(Timeout.MaxTestTime)] + public void BlockParameter_ToString_ReturnsBlockNumber() + { + new BlockParameter(12345L).ToString().Should().Be("12345"); + } + + [Test, MaxTime(Timeout.MaxTestTime)] + public void BlockParameter_ToString_ReturnsBlockHash() + { + var hash = TestItem.KeccakA; + new BlockParameter(hash).ToString().Should().Be(hash.ToString()); + } + + [Test, MaxTime(Timeout.MaxTestTime)] + public void SearchForBlock_WhenBlockIsPruned_IncludesBlockNumberInError() + { + IBlockFinder blockFinder = Substitute.For(); + BlockHeader head = Build.A.BlockHeader.WithNumber(1000).TestObject; + Block headBlock = Build.A.Block.WithHeader(head).TestObject; + blockFinder.Head.Returns(headBlock); + blockFinder.GetLowestBlock().Returns(100); + + // Mock the underlying method that will be called + blockFinder.FindBlock(50, BlockTreeLookupOptions.None).Returns((Block?)null); + + BlockParameter blockParameter = new BlockParameter(50); + SearchResult result = blockFinder.SearchForBlock(blockParameter); + + result.IsError.Should().BeTrue(); + result.ErrorCode.Should().Be(ErrorCodes.PrunedHistoryUnavailable); + result.Error.Should().Contain("50"); + result.Error.Should().Contain("pruned history unavailable"); + } } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs index add9079bfe89..4e180459cf7d 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs @@ -12,13 +12,11 @@ using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Withdrawals; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Core.Test; using Nethermind.Core.Test.Blockchain; using Nethermind.Core.Test.Builders; -using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; using Nethermind.JsonRpc.Test.Modules; using Nethermind.Logging; @@ -34,10 +32,11 @@ using System.Threading; using System.Threading.Tasks; using Nethermind.Blockchain.Tracing; -using Nethermind.State; +using Nethermind.Evm; namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class BlockProcessorTests { [Test, MaxTime(Timeout.MaxTestTime)] @@ -45,7 +44,7 @@ public void Prepared_block_contains_author_field() { IWorldState stateProvider = TestWorldStateFactory.CreateForTest(); ITransactionProcessor transactionProcessor = Substitute.For(); - BlockProcessor processor = new BlockProcessor(HoleskySpecProvider.Instance, + BlockProcessor processor = new(HoodiSpecProvider.Instance, TestBlockValidator.AlwaysValid, NoBlockRewards.Instance, new BlockProcessor.BlockValidationTransactionsExecutor(new ExecuteTransactionProcessorAdapter(transactionProcessor), stateProvider), @@ -56,11 +55,12 @@ public void Prepared_block_contains_author_field() LimboLogs.Instance, new WithdrawalProcessor(stateProvider, LimboLogs.Instance), new ExecutionRequestsProcessor(transactionProcessor)); - BranchProcessor branchProcessor = new BranchProcessor( + BranchProcessor branchProcessor = new( processor, - HoleskySpecProvider.Instance, + HoodiSpecProvider.Instance, stateProvider, new BeaconBlockRootHandler(transactionProcessor, stateProvider), + Substitute.For(), LimboLogs.Instance); BlockHeader header = Build.A.BlockHeader.WithAuthor(TestItem.AddressD).TestObject; @@ -79,8 +79,8 @@ public void Recovers_state_on_cancel() { IWorldState stateProvider = TestWorldStateFactory.CreateForTest(); ITransactionProcessor transactionProcessor = Substitute.For(); - BlockProcessor processor = new BlockProcessor( - HoleskySpecProvider.Instance, + BlockProcessor processor = new( + HoodiSpecProvider.Instance, TestBlockValidator.AlwaysValid, new RewardCalculator(MainnetSpecProvider.Instance), new BlockProcessor.BlockValidationTransactionsExecutor(new ExecuteTransactionProcessorAdapter(transactionProcessor), stateProvider), @@ -91,11 +91,12 @@ public void Recovers_state_on_cancel() LimboLogs.Instance, new WithdrawalProcessor(stateProvider, LimboLogs.Instance), new ExecutionRequestsProcessor(transactionProcessor)); - BranchProcessor branchProcessor = new BranchProcessor( + BranchProcessor branchProcessor = new( processor, - HoleskySpecProvider.Instance, + HoodiSpecProvider.Instance, stateProvider, new BeaconBlockRootHandler(transactionProcessor, stateProvider), + Substitute.For(), LimboLogs.Instance); BlockHeader header = Build.A.BlockHeader.WithNumber(1).WithAuthor(TestItem.AddressD).TestObject; @@ -127,7 +128,7 @@ public void Recovers_state_on_cancel() public async Task Process_long_running_branch(int blocksAmount) { Address address = TestItem.Addresses[0]; - TestSingleReleaseSpecProvider spec = new TestSingleReleaseSpecProvider(ConstantinopleFix.Instance); + TestSingleReleaseSpecProvider spec = new(ConstantinopleFix.Instance); TestRpcBlockchain testRpc = await TestRpcBlockchain.ForTest(SealEngineType.NethDev) .Build(spec); testRpc.TestWallet.UnlockAccount(address, new SecureString()); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeSuggestPacerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeSuggestPacerTests.cs index 34392783f02f..46a3ea978935 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeSuggestPacerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeSuggestPacerTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class BlockTreeSuggestPacerTests { [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs index 2d32e4b03952..d2f2a332753e 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; @@ -32,6 +32,8 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class BlockTreeTests { private TestMemDb _blocksInfosDb = null!; @@ -254,10 +256,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 +1052,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 +1144,7 @@ public void When_lowestInsertedHeaderWasNotPersisted_useBinarySearchToLoadLowest SyncConfig syncConfig = new() { FastSync = true, - PivotNumber = beginIndex.ToString(), + PivotNumber = beginIndex, }; BlockTreeBuilder builder = Build.A @@ -1177,7 +1175,7 @@ public void When_lowestInsertedHeaderWasPersisted_doNot_useBinarySearchToLoadLow SyncConfig syncConfig = new() { FastSync = true, - PivotNumber = "105", + PivotNumber = 105, }; BlockTreeBuilder builder = Build.A @@ -1218,7 +1216,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 +1273,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 +1310,7 @@ public void Loads_best_head_up_to_best_persisted_state() SyncConfig syncConfig = new() { - PivotNumber = "0", + PivotNumber = 0, FastSync = true, }; @@ -1366,7 +1364,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 +1400,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 +1421,7 @@ public void Cannot_insert_genesis() SyncConfig syncConfig = new() { - PivotNumber = pivotNumber.ToString(), + PivotNumber = pivotNumber, }; BlockTree tree = Build.A.BlockTree() @@ -1444,7 +1442,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 +1470,7 @@ public void Inserts_blooms() SyncConfig syncConfig = new() { - PivotNumber = pivotNumber.ToString(), + PivotNumber = pivotNumber, }; IBloomStorage bloomStorage = Substitute.For(); @@ -1503,7 +1501,7 @@ public void Block_loading_is_lazy() { SyncConfig syncConfig = new() { - PivotNumber = 0L.ToString(), + PivotNumber = 0L, }; BlockTreeBuilder builder = Build.A.BlockTree() @@ -1881,7 +1879,7 @@ public void On_restart_loads_already_processed_genesis_block(bool wereProcessed) // First run { Hash256 uncleHash = new("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"); - BlockTree tree = Build.A.BlockTree(HoleskySpecProvider.Instance) + BlockTree tree = Build.A.BlockTree(HoodiSpecProvider.Instance) .WithBlockStore(new BlockStore(blocksDb)) .WithBlocksNumberDb(blockNumberDb) .WithHeadersDb(headersDb) @@ -1947,7 +1945,7 @@ public void On_restart_loads_already_processed_genesis_block(bool wereProcessed) // Assume Nethermind got restarted { - BlockTree tree = Build.A.BlockTree(HoleskySpecProvider.Instance) + BlockTree tree = Build.A.BlockTree(HoodiSpecProvider.Instance) .WithBlockStore(new BlockStore(blocksDb)) .WithBlocksNumberDb(blockNumberDb) .WithHeadersDb(headersDb) @@ -2037,7 +2035,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 +2048,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 +2067,7 @@ public void On_UpdateMainBranch_UpdateSyncPivot_ToLowestPersistedHeader() SyncConfig syncConfig = new() { FastSync = true, - PivotNumber = pivotNumber.ToString(), + PivotNumber = pivotNumber, PivotHash = TestItem.KeccakA.ToString(), }; @@ -2112,7 +2110,7 @@ public void On_ForkChoiceUpdated_UpdateSyncPivot_ToFinalizedHeader_BeforePersist SyncConfig syncConfig = new() { FastSync = true, - PivotNumber = pivotNumber.ToString(), + PivotNumber = pivotNumber, PivotHash = TestItem.KeccakA.ToString(), }; @@ -2160,7 +2158,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 17d9d5a0bc20..f27e4f2065a5 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Autofac; +using ConcurrentCollections; using FluentAssertions; using Nethermind.Blockchain.Tracing; using Nethermind.Consensus.Processing; @@ -19,10 +20,8 @@ using Nethermind.Core.Test; using Nethermind.Core.Test.Blockchain; using Nethermind.Core.Test.Builders; -using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Logging; -using Nethermind.Evm.State; using Nethermind.State; using Nethermind.TxPool; using NSubstitute; @@ -30,7 +29,8 @@ namespace Nethermind.Blockchain.Test; -[Parallelizable(ParallelScope.Self)] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class BlockchainProcessorTests { private class ProcessingTestContext @@ -41,11 +41,11 @@ private class BranchProcessorMock : IBranchProcessor { private readonly ILogger _logger; - private readonly HashSet _allowed = new(); + private readonly ConcurrentHashSet _allowed = new(); internal readonly HashSet Processed = new(); - private readonly HashSet _allowedToFail = new(); + private readonly ConcurrentHashSet _allowedToFail = new(); private readonly HashSet _rootProcessed = new(); @@ -89,7 +89,7 @@ public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlo Hash256 hash = suggestedBlock.Hash!; if (!_allowed.Contains(hash)) { - if (_allowedToFail.Remove(hash)) + if (_allowedToFail.TryRemove(hash)) { BlockProcessed?.Invoke(this, new BlockProcessedEventArgs(suggestedBlocks.Last(), [])); throw new InvalidBlockException(suggestedBlock, "allowed to fail"); @@ -197,7 +197,7 @@ public ProcessingTestContext(bool startProcessor) .TestObject; _branchProcessor = new BranchProcessorMock(_logManager, _stateReader); _recoveryStep = new RecoveryStepMock(_logManager); - _processor = new BlockchainProcessor(_blockTree, _branchProcessor, _recoveryStep, _stateReader, LimboLogs.Instance, BlockchainProcessor.Options.Default); + _processor = new BlockchainProcessor(_blockTree, _branchProcessor, _recoveryStep, _stateReader, LimboLogs.Instance, BlockchainProcessor.Options.Default, Substitute.For()); _resetEvent = new AutoResetEvent(false); _queueEmptyResetEvent = new AutoResetEvent(false); @@ -285,11 +285,74 @@ public AfterBlock ProcessedFail(Block block) public ProcessingTestContext Suggested(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) { - AddBlockResult result = _blockTree.SuggestBlock(block, options); - if (result != AddBlockResult.Added) + if ((options & BlockTreeSuggestOptions.ShouldProcess) != 0) { - _logger.Info($"Finished waiting for {block.ToString(Block.Format.Short)} as block was ignored"); - _resetEvent.Set(); + // Use Task.Run to avoid blocking when AllowSynchronousContinuations + // causes inline processing on the calling thread. + // + // We wait for either BlockAdded (enqueued to processor) or SuggestBlock + // completion (non-best blocks that aren't enqueued) before returning. This + // prevents a race where two concurrent Enqueue calls both see _queueCount > 1 + // and go to the recovery queue in non-deterministic order, causing the + // processor to batch blocks together and deadlock the test. + // + // TaskCompletionSource is used instead of ManualResetEventSlim because the + // background Task.Run may outlive this method (when AllowSynchronousContinuations + // causes SuggestBlock to block indefinitely) and TrySetResult is safe to call + // on a completed TCS without disposal concerns. + TaskCompletionSource suggestCompleted = new(TaskCreationOptions.RunContinuationsAsynchronously); + void OnBlockAdded(object? sender, BlockEventArgs args) + { + if (args.Block.Hash == block.Hash) + suggestCompleted.TrySetResult(); + } + + ((IBlockProcessingQueue)_processor).BlockAdded += OnBlockAdded; + try + { + Task.Run(() => + { + try + { + AddBlockResult result = _blockTree.SuggestBlock(block, options); + if (result != AddBlockResult.Added) + { + _logger.Info($"Finished waiting for {block.ToString(Block.Format.Short)} as block was ignored"); + _resetEvent.Set(); + } + } + finally + { + // For new-best blocks, BlockAdded fires during SuggestBlock (before + // this point) so the TCS is already completed. For non-best blocks + // (same/lower difficulty), no enqueue occurs, so this is the signal. + // When AllowSynchronousContinuations causes inline processing, + // SuggestBlock blocks indefinitely but BlockAdded already fired. + suggestCompleted.TrySetResult(); + } + }); + Assert.That( + SpinWait.SpinUntil(() => _blockTree.IsKnownBlock(block.Number, block.Hash!), ProcessingWait), + Is.True, + $"Timed out waiting for {block.ToString(Block.Format.Short)} to appear in the block tree"); + Assert.That( + suggestCompleted.Task.Wait(ProcessingWait), + Is.True, + $"Timed out waiting for {block.ToString(Block.Format.Short)} to complete suggestion"); + } + finally + { + ((IBlockProcessingQueue)_processor).BlockAdded -= OnBlockAdded; + } + } + else + { + AddBlockResult result = _blockTree.SuggestBlock(block, options); + if (result != AddBlockResult.Added) + { + _logger.Info($"Finished waiting for {block.ToString(Block.Format.Short)} as block was ignored"); + _resetEvent.Set(); + } } return this; @@ -330,8 +393,7 @@ public ProcessingTestContext Recovered(Block block) public ProcessingTestContext CountIs(int expectedCount) { - var count = ((IBlockProcessingQueue)_processor).Count; - Assert.That(expectedCount, Is.EqualTo(count)); + Assert.That(() => ((IBlockProcessingQueue)_processor).Count, Is.EqualTo(expectedCount).After(ProcessingWait, 10)); return this; } @@ -623,7 +685,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 new file mode 100644 index 000000000000..17d1363095a6 --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockhashCacheTests.cs @@ -0,0 +1,385 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Nethermind.Blockchain.Headers; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Db; +using Nethermind.Logging; +using NUnit.Framework; + +namespace Nethermind.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class BlockhashCacheTests +{ + private const int FlatCacheItemLength = BlockhashCache.MaxDepth + 1; + + [Test] + public void GetHash_with_depth_zero_returns_block_hash() + { + (BlockTree tree, BlockhashCache cache) = BuildTest(1); + Hash256? result = cache.GetHash(tree.Genesis!, 0); + result.Should().Be(tree.Genesis!.Hash!); + cache.GetStats().Should().Be(new BlockhashCache.Stats(0, 0, 0)); + } + + [Test] + public void GetHash_returns_correct_ancestor() + { + (BlockTree tree, BlockhashCache cache) = BuildTest(10); + BlockHeader? head = tree.FindHeader(9, BlockTreeLookupOptions.None); + + // depth=1 should return block 8 + Hash256? result1 = cache.GetHash(head!, 1); + BlockHeader? block8 = tree.FindHeader(8, BlockTreeLookupOptions.None); + result1.Should().Be(block8!.Hash!); + + // depth=5 should return block 4 + Hash256? result5 = cache.GetHash(head!, 5); + BlockHeader? block4 = tree.FindHeader(4, BlockTreeLookupOptions.None); + result5.Should().Be(block4!.Hash!); + + // depth=9 should return block 0 (genesis) + Hash256? result9 = cache.GetHash(head!, 9); + result9.Should().Be(tree.Genesis!.Hash!); + + cache.GetStats().Should().Be(new BlockhashCache.Stats(10, 1, 0)); + } + + [Test] + public void GetHash_returns_null_for_missing_ancestor() + { + (BlockTree tree, BlockhashCache cache) = BuildTest(5); + BlockHeader? head = tree.FindHeader(4, BlockTreeLookupOptions.None); + Hash256? result = cache.GetHash(head!, 10); + result.Should().BeNull(); + cache.GetStats().Should().Be(new BlockhashCache.Stats(5, 1, 1)); + } + + [Test] + public void GetHash_caches_loaded_blocks() + { + (BlockTree tree, BlockhashCache cache) = BuildTest(10); + BlockHeader? head = tree.FindHeader(9, BlockTreeLookupOptions.None); + cache.GetHash(head!, 5); + + cache.Contains(head!.Hash!).Should().BeTrue(); + BlockHeader? ancestor = tree.FindHeader(4, BlockTreeLookupOptions.None); + cache.Contains(ancestor!.Hash!).Should().BeTrue(); + cache.GetStats().Should().Be(new BlockhashCache.Stats(6, 1, 0)); + } + + [Test] + public void GetHash_handles_max_depth_of_256() + { + (BlockTree tree, BlockhashCache cache) = BuildTest(300); + BlockHeader? head = tree.FindHeader(299, BlockTreeLookupOptions.None); + Hash256? result = cache.GetHash(head!, 256); + + BlockHeader? expected = tree.FindHeader(43, BlockTreeLookupOptions.None); + result.Should().Be(expected!.Hash!); + cache.GetStats().Should().Be(new BlockhashCache.Stats(FlatCacheItemLength, 1, 1)); + } + + [Test] + public void GetHash_does_not_go_beyond_depth_256() + { + (BlockTree tree, BlockhashCache cache) = BuildTest(300); + + BlockHeader? head = tree.FindHeader(299, BlockTreeLookupOptions.None); + Hash256? result300 = cache.GetHash(head!, 300); + Hash256? result256 = cache.GetHash(head!, 256); + + result300.Should().BeNull(); + result256.Should().NotBeNull(); + cache.GetStats().Should().Be(new BlockhashCache.Stats(FlatCacheItemLength, 1, 1)); + } + + [Test] + public void Contains_returns_true_for_cached_blocks() + { + (BlockTree tree, BlockhashCache cache) = BuildTest(10); + + BlockHeader? head = tree.FindHeader(9, BlockTreeLookupOptions.None); + cache.GetHash(head!, 5); + + cache.Contains(head!.Hash!).Should().BeTrue(); + cache.GetStats().Should().Be(new BlockhashCache.Stats(6, 1, 0)); + } + + [Test] + public void Contains_returns_false_for_uncached_blocks() + { + (BlockTree _, BlockhashCache cache) = BuildTest(1); + BlockHeader header = Build.A.BlockHeader.WithNumber(100).TestObject; + cache.Contains(header.Hash!).Should().BeFalse(); + } + + [Test] + public void PruneBefore_removes_old_blocks() + { + (BlockTree tree, BlockhashCache cache) = BuildTest(100); + + BlockHeader? head = tree.FindHeader(99, BlockTreeLookupOptions.None); + cache.GetHash(head!, 50); + cache.GetStats().Should().Be(new BlockhashCache.Stats(51, 1, 0)); + int removed = cache.PruneBefore(60); + + removed.Should().BeGreaterThan(0); + BlockHeader? old = tree.FindHeader(40, BlockTreeLookupOptions.None); + BlockHeader? kept = tree.FindHeader(60, BlockTreeLookupOptions.None); + cache.Contains(old!.Hash!).Should().BeFalse(); + cache.Contains(kept!.Hash!).Should().BeTrue(); + cache.GetStats().Should().Be(new BlockhashCache.Stats(40, 1, 0)); + } + + [Test] + public void Clear_removes_all_cached_blocks() + { + (BlockTree tree, BlockhashCache cache) = BuildTest(10); + + BlockHeader? head = tree.FindHeader(9, BlockTreeLookupOptions.None); + cache.GetHash(head!, 5); + cache.Clear(); + + cache.Contains(head!.Hash!).Should().BeFalse(); + cache.GetStats().Should().Be(new BlockhashCache.Stats(0, 0, 0)); + } + + [Test] + public async Task Prefetch_loads_ancestors_in_background() + { + (BlockTree tree, BlockhashCache cache) = BuildTest(100); + + BlockHeader? head = tree.FindHeader(99, BlockTreeLookupOptions.None); + BlockHeader? block1 = tree.FindHeader(1, BlockTreeLookupOptions.None); + await cache.Prefetch(head!); + + cache.Contains(head!.Hash!).Should().BeTrue(); + cache.Contains(block1!.Hash!).Should().BeTrue(); + cache.GetStats().Should().Be(new BlockhashCache.Stats(100, 1, 1)); + } + + [Test] + public async Task Concurrent_Prefetch_loads_correctly() + { + (BlockTree tree, BlockhashCache cache) = BuildTest(100); + + BlockHeader head = tree.FindHeader(99, BlockTreeLookupOptions.None)!; + BlockHeader block90 = tree.FindHeader(90, BlockTreeLookupOptions.None)!; + BlockHeader block1 = tree.FindHeader(1, BlockTreeLookupOptions.None)!; + Task prefetch99 = cache.Prefetch(head); + Task prefetch90 = cache.Prefetch(block90); + await Task.WhenAll(prefetch99, prefetch90); + + cache.GetHash(head, 98).Should().Be(block1.Hash!); + cache.GetHash(block90, 89).Should().Be(block1.Hash!); + cache.GetStats().Should().Be(new BlockhashCache.Stats(100, 1, 2)); + } + + [Test] + public void Sequential_queries_work_correctly() + { + (BlockTree tree, BlockhashCache cache) = BuildTest(512); + + for (int blockNum = 256; blockNum < 512; blockNum += 50) + { + BlockHeader? block = tree.FindHeader(blockNum, BlockTreeLookupOptions.None); + + for (int depth = 1; depth <= 100; depth += 25) + { + Hash256? result = cache.GetHash(block!, depth); + BlockHeader? expected = tree.FindHeader(blockNum - depth, BlockTreeLookupOptions.None); + result.Should().Be(expected!.Hash!, $"block {blockNum} depth {depth}"); + } + } + } + + [Test] + public async Task Periodic_pruning_maintains_cache_size() + { + (BlockTree tree, BlockhashCache cache) = BuildTest(1000); + + for (int i = 100; i < 1000; i += 100) + { + BlockHeader block = tree.FindHeader(i, BlockTreeLookupOptions.None)!; + await cache.Prefetch(block); + + if (i > 500) + { + int pruned = cache.PruneBefore(i - 400); + pruned.Should().BeGreaterThan(0); + cache.GetStats().Should().Be(new BlockhashCache.Stats(401, 1, 5)); + } + } + } + + [Test] + public void Can_stitch_block_ranges() + { + (BlockTree tree, BlockhashCache cache) = BuildTest(1000); + + for (int i = 100; i <= 500; i += 100) + { + BlockHeader block = tree.FindHeader(i, BlockTreeLookupOptions.None)!; + cache.GetHash(block, 50); + } + + cache.GetStats().Should().Be(new BlockhashCache.Stats(255, 5, 0)); + + for (int i = 100; i <= 500; i += 100) + { + BlockHeader block = tree.FindHeader(i, BlockTreeLookupOptions.None)!; + cache.GetHash(block, BlockhashCache.MaxDepth); + } + + cache.GetStats().Should().Be(new BlockhashCache.Stats(501, 1, 5)); + } + + [Test] + public async Task Can_support_multiple_forks() + { + Block genesis = Build.A.Block.Genesis.TestObject; + BlockTreeBuilder builder = Build.A.BlockTree(genesis).WithoutSettingHead + .OfChainLengthWithSharedSplits(out Block head1, 1000) + .OfChainLengthWithSharedSplits(out Block head2, 200, 1, 800) + .OfChainLengthWithSharedSplits(out Block head3, 200, 2, 800); + + BlockhashCache cache = new(builder.HeaderStore, LimboLogs.Instance); + + await cache.Prefetch(head1.Header); + await cache.Prefetch(head2.Header); + await cache.Prefetch(head3.Header); + cache.GetStats().Should().Be(new BlockhashCache.Stats(255 + 200 + 200, 3, 3)); + cache.PruneBefore(800); + cache.GetStats().Should().Be(new BlockhashCache.Stats(200 + 199 + 199, 3, 3)); + } + + [Test] + public async Task Can_prune_old_forks() + { + const int depth = BlockhashCache.MaxDepth * 5 + 1; + (BlockTree tree, BlockhashCache cache) = BuildTest(depth); + for (int i = BlockhashCache.MaxDepth; i < depth; i += BlockhashCache.MaxDepth) + { + cache.GetHash(tree.FindHeader(i, BlockTreeLookupOptions.RequireCanonical)!, BlockhashCache.MaxDepth); + } + + cache.GetStats().Should().Be(new BlockhashCache.Stats(depth, 1, 5)); + await cache.Prefetch(tree.FindHeader(depth - 1, BlockTreeLookupOptions.RequireCanonical)!); + await Task.Delay(100); + cache.GetStats().Should().Be(new BlockhashCache.Stats(BlockhashCache.MaxDepth * 2 + 1, 1, 3)); + } + + [Test] + public async Task Prefetch_prunes() + { + Block genesis = Build.A.Block.Genesis.TestObject; + BlockTreeBuilder builder = Build.A.BlockTree(genesis).WithoutSettingHead + .OfChainLengthWithSharedSplits(out Block head1, 1000) + .OfChainLengthWithSharedSplits(out Block head2, 300, 1, 300) + .OfChainLengthWithSharedSplits(out Block head3, 300, 2, 300); + + BlockhashCache cache = new(builder.HeaderStore, LimboLogs.Instance); + + await cache.Prefetch(head1.Header); + await cache.Prefetch(head2.Header); + await cache.Prefetch(head3.Header); + cache.GetStats().Should().Be(new BlockhashCache.Stats(FlatCacheItemLength + FlatCacheItemLength + FlatCacheItemLength, 3, 3)); + cache.PruneBefore(800); + cache.GetStats().Should().Be(new BlockhashCache.Stats(200, 1, 1)); + } + + [TestCase(300)] + [TestCase(50)] + public async Task Prefetch_reuses_parent_data(int chainDepth) + { + (BlockTree tree, BlockhashCache cache) = BuildTest(chainDepth); + BlockHeader head = tree.FindHeader(chainDepth - 1, BlockTreeLookupOptions.None)!; + BlockHeader prev = tree.FindHeader(chainDepth - 2, BlockTreeLookupOptions.None)!; + + Hash256[] prevHashes = (await cache.Prefetch(prev, CancellationToken.None))!; + Hash256[] headHashes = (await cache.Prefetch(head, CancellationToken.None))!; + cache.GetStats().Should().Be(new BlockhashCache.Stats(Math.Min(chainDepth - 1, FlatCacheItemLength), 1, 2)); + 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.ParentHash)); + } + ); + } + + [Test] + public async Task DoesNot_cache_cancelled_searches() + { + SlowHeaderStore headerStore = new(new HeaderStore(new MemDb(), new MemDb())); + (BlockTree tree, BlockhashCache cache) = BuildTest(260, headerStore); + + BlockHeader head = tree.FindHeader(FlatCacheItemLength, BlockTreeLookupOptions.None)!; + CancellationTokenSource cts = new(TimeSpan.FromMilliseconds(20)); + await cache.Prefetch(head, cts.Token); + 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; + BlockTreeBuilder builder = Build.A.BlockTree(genesis); + if (headerStore is not null) + { + builder.WithHeaderStore(headerStore); + } + + builder.WithoutSettingHead.OfChainLength(chainLength); + BlockTree tree = builder.TestObject; + BlockhashCache cache = new(builder.HeaderStore, LimboLogs.Instance); + return (tree, cache); + } +} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockhashProviderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockhashProviderTests.cs index 0a04c6bce7fb..ec9997f9df50 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockhashProviderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockhashProviderTests.cs @@ -1,9 +1,12 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using System.Threading; +using System.Threading.Tasks; using FluentAssertions; using Nethermind.Blockchain.Blocks; -using Nethermind.Blockchain.Find; +using Nethermind.Blockchain.Headers; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; @@ -14,7 +17,6 @@ using Nethermind.Specs.Forks; using Nethermind.Specs.Test; using Nethermind.Evm.State; -using Nethermind.State; using NUnit.Framework; namespace Nethermind.Blockchain.Test; @@ -25,17 +27,17 @@ public class BlockhashProviderTests private static (IWorldState, Hash256) CreateWorldState() { IWorldState worldState = TestWorldStateFactory.CreateForTest(); - using var _ = worldState.BeginScope(IWorldState.PreGenesis); + using IDisposable _ = worldState.BeginScope(IWorldState.PreGenesis); worldState.CreateAccount(Eip2935Constants.BlockHashHistoryAddress, 0, 1); worldState.Commit(Frontier.Instance); worldState.CommitTree(0); return (worldState, worldState.StateRoot); } - private static BlockhashProvider CreateBlockHashProvider(IBlockFinder tree, IReleaseSpec spec) + private static BlockhashProvider CreateBlockHashProvider(IHeaderFinder headerFinder, IReleaseSpec spec) { (IWorldState worldState, Hash256 _) = CreateWorldState(); - BlockhashProvider provider = new(tree, new TestSpecProvider(spec), worldState, LimboLogs.Instance); + BlockhashProvider provider = new(new BlockhashCache(headerFinder, LimboLogs.Instance), worldState, LimboLogs.Instance); return provider; } @@ -46,12 +48,13 @@ public void Can_get_parent_only_headers() Block genesis = Build.A.Block.Genesis.TestObject; - BlockTree tree = Build.A.BlockTree(genesis).OfHeadersOnly.OfChainLength(chainLength).TestObject; + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree(genesis).OfHeadersOnly.OfChainLength(chainLength); + BlockTree tree = blockTreeBuilder.TestObject; - BlockhashProvider provider = CreateBlockHashProvider(tree, Frontier.Instance); + BlockhashProvider provider = CreateBlockHashProvider(blockTreeBuilder.HeaderStore, Frontier.Instance); BlockHeader? head = tree.FindHeader(chainLength - 1, BlockTreeLookupOptions.None); Block current = Build.A.Block.WithParent(head!).TestObject; - Hash256? result = provider.GetBlockhash(current.Header, chainLength - 1); + Hash256? result = provider.GetBlockhash(current.Header, chainLength - 1, Frontier.Instance); Assert.That(result, Is.EqualTo(head?.Hash)); } @@ -61,12 +64,13 @@ public void Can_lookup_up_to_256_before_with_headers_only() const int chainLength = 512; Block genesis = Build.A.Block.Genesis.TestObject; - BlockTree tree = Build.A.BlockTree(genesis).OfHeadersOnly.OfChainLength(chainLength).TestObject; + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree(genesis).OfHeadersOnly.OfChainLength(chainLength); + BlockTree tree = blockTreeBuilder.TestObject; - BlockhashProvider provider = CreateBlockHashProvider(tree, Frontier.Instance); + BlockhashProvider provider = CreateBlockHashProvider(blockTreeBuilder.HeaderStore, Frontier.Instance); BlockHeader head = tree.FindHeader(chainLength - 1, BlockTreeLookupOptions.None)!; Block current = Build.A.Block.WithParent(head).TestObject; - Hash256? result = provider.GetBlockhash(current.Header, chainLength - 256); + Hash256? result = provider.GetBlockhash(current.Header, chainLength - 256, Frontier.Instance); Assert.That(result, Is.EqualTo(tree.FindHeader(256, BlockTreeLookupOptions.None)!.Hash)); } @@ -76,8 +80,10 @@ public void Can_lookup_correctly_on_disconnected_main_chain() const int chainLength = 512; Block genesis = Build.A.Block.Genesis.TestObject; - BlockTree tree = Build.A.BlockTree(genesis).OfChainLength(chainLength).TestObject; - BlockhashProvider provider = CreateBlockHashProvider(tree, Frontier.Instance); + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree(genesis).OfChainLength(chainLength); + BlockTree tree = blockTreeBuilder.TestObject; + + BlockhashProvider provider = CreateBlockHashProvider(blockTreeBuilder.HeaderStore, Frontier.Instance); BlockHeader notCanonParent = tree.FindHeader(chainLength - 4, BlockTreeLookupOptions.None)!; BlockHeader expectedHeader = tree.FindHeader(chainLength - 3, BlockTreeLookupOptions.None)!; @@ -92,7 +98,7 @@ public void Can_lookup_correctly_on_disconnected_main_chain() Block current = Build.A.Block.WithParent(head).TestObject; // At chainLength - Hash256? result = provider.GetBlockhash(current.Header, chainLength - 3); + Hash256? result = provider.GetBlockhash(current.Header, chainLength - 3, Frontier.Instance); Assert.That(result, Is.EqualTo(expectedHeader.Hash)); } @@ -102,13 +108,15 @@ public void Can_lookup_up_to_256_before_with_headers_only_and_competing_branches const int chainLength = 512; Block genesis = Build.A.Block.Genesis.TestObject; - BlockTree tree = Build.A.BlockTree(genesis).OfHeadersOnly.OfChainLength(out Block headBlock, chainLength) - .OfChainLength(out Block _, chainLength, 1).TestObject; - BlockhashProvider provider = CreateBlockHashProvider(tree, Frontier.Instance); + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree(genesis).OfHeadersOnly + .OfChainLength(out Block headBlock, chainLength) + .OfChainLength(out Block _, chainLength, 1); + + BlockhashProvider provider = CreateBlockHashProvider(blockTreeBuilder.HeaderStore, Frontier.Instance); Block current = Build.A.Block.WithParent(headBlock).TestObject; long lookupNumber = chainLength - 256; - Hash256? result = provider.GetBlockhash(current.Header, lookupNumber); + Hash256? result = provider.GetBlockhash(current.Header, lookupNumber, Frontier.Instance); Assert.That(result, Is.Not.Null); } @@ -118,15 +126,17 @@ public void Can_lookup_up_to_256_before_soon_after_fast_sync() const int chainLength = 512; Block genesis = Build.A.Block.Genesis.TestObject; - BlockTree tree = Build.A.BlockTree(genesis).OfHeadersOnly.OfChainLength(out Block headBlock, chainLength) - .OfChainLength(out Block _, chainLength, 1).TestObject; + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree(genesis).OfHeadersOnly + .OfChainLength(out Block headBlock, chainLength) + .OfChainLength(out Block _, chainLength, 1); + BlockTree tree = blockTreeBuilder.TestObject; - BlockhashProvider provider = CreateBlockHashProvider(tree, Frontier.Instance); + BlockhashProvider provider = CreateBlockHashProvider(blockTreeBuilder.HeaderStore, Frontier.Instance); Block current = Build.A.Block.WithParent(headBlock).TestObject; tree.SuggestBlock(current); tree.UpdateMainChain(current); long lookupNumber = chainLength - 256; - Hash256? result = provider.GetBlockhash(current.Header, lookupNumber); + Hash256? result = provider.GetBlockhash(current.Header, lookupNumber, Frontier.Instance); Assert.That(result, Is.Not.Null); } @@ -136,10 +146,12 @@ public void Can_lookup_up_to_256_before_some_blocks_after_fast_sync() const int chainLength = 512; Block genesis = Build.A.Block.Genesis.TestObject; - BlockTree tree = Build.A.BlockTree(genesis).OfHeadersOnly.OfChainLength(out Block headBlock, chainLength) - .OfChainLength(out Block _, chainLength, 1).TestObject; + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree(genesis).OfHeadersOnly + .OfChainLength(out Block headBlock, chainLength) + .OfChainLength(out Block _, chainLength, 1); + BlockTree tree = blockTreeBuilder.TestObject; - BlockhashProvider provider = CreateBlockHashProvider(tree, Frontier.Instance); + BlockhashProvider provider = CreateBlockHashProvider(blockTreeBuilder.HeaderStore, Frontier.Instance); Block current = Build.A.Block.WithParent(headBlock).TestObject; for (int i = 0; i < 6; i++) @@ -150,7 +162,7 @@ public void Can_lookup_up_to_256_before_some_blocks_after_fast_sync() } long lookupNumber = current.Number - 256; - Hash256? result = provider.GetBlockhash(current.Header, lookupNumber); + Hash256? result = provider.GetBlockhash(current.Header, lookupNumber, Frontier.Instance); Assert.That(result, Is.Not.Null); } @@ -160,8 +172,10 @@ public void Can_handle_non_main_chain_in_fast_sync() const int chainLength = 512; Block genesis = Build.A.Block.Genesis.TestObject; - BlockTree tree = Build.A.BlockTree(genesis).OfHeadersOnly.OfChainLength(out Block headBlock, chainLength) - .OfChainLength(out Block _, chainLength, 1).TestObject; + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree(genesis).OfHeadersOnly + .OfChainLength(out Block headBlock, chainLength) + .OfChainLength(out Block _, chainLength, 1); + BlockTree tree = blockTreeBuilder.TestObject; Block current = Build.A.Block.WithParent(headBlock).TestObject; for (int i = 0; i < 6; i++) { @@ -170,9 +184,9 @@ public void Can_handle_non_main_chain_in_fast_sync() current = Build.A.Block.WithParent(current).TestObject; } - BlockhashProvider provider = CreateBlockHashProvider(tree, Frontier.Instance); + BlockhashProvider provider = CreateBlockHashProvider(blockTreeBuilder.HeaderStore, Frontier.Instance); - Hash256? result = provider.GetBlockhash(current.Header, 509); + Hash256? result = provider.GetBlockhash(current.Header, 509, Frontier.Instance); Assert.That(result, Is.Not.Null); } @@ -183,12 +197,13 @@ public void Can_get_parent_hash() Block genesis = Build.A.Block.Genesis.TestObject; - BlockTree tree = Build.A.BlockTree(genesis).OfChainLength(chainLength).TestObject; + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree(genesis).OfChainLength(chainLength); + BlockTree tree = blockTreeBuilder.TestObject; - BlockhashProvider provider = CreateBlockHashProvider(tree, Frontier.Instance); + BlockhashProvider provider = CreateBlockHashProvider(blockTreeBuilder.HeaderStore, Frontier.Instance); BlockHeader head = tree.FindHeader(chainLength - 1, BlockTreeLookupOptions.None)!; Block current = Build.A.Block.WithParent(head).TestObject; - Hash256? result = provider.GetBlockhash(current.Header, chainLength - 1); + Hash256? result = provider.GetBlockhash(current.Header, chainLength - 1, Frontier.Instance); Assert.That(result, Is.EqualTo(head.Hash)); } @@ -198,12 +213,13 @@ public void Cannot_ask_for_self() const int chainLength = 512; Block genesis = Build.A.Block.Genesis.TestObject; - BlockTree tree = Build.A.BlockTree(genesis).OfChainLength(chainLength).TestObject; + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree(genesis).OfChainLength(chainLength); + BlockTree tree = blockTreeBuilder.TestObject; - BlockhashProvider provider = CreateBlockHashProvider(tree, Frontier.Instance); + BlockhashProvider provider = CreateBlockHashProvider(blockTreeBuilder.HeaderStore, Frontier.Instance); BlockHeader head = tree.FindHeader(chainLength - 1, BlockTreeLookupOptions.None)!; Block current = Build.A.Block.WithParent(head).TestObject; - Hash256? result = provider.GetBlockhash(current.Header, chainLength); + Hash256? result = provider.GetBlockhash(current.Header, chainLength, Frontier.Instance); Assert.That(result, Is.Null); } @@ -213,12 +229,13 @@ public void Cannot_ask_about_future() const int chainLength = 512; Block genesis = Build.A.Block.Genesis.TestObject; - BlockTree tree = Build.A.BlockTree(genesis).OfChainLength(chainLength).TestObject; + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree(genesis).OfChainLength(chainLength); + BlockTree tree = blockTreeBuilder.TestObject; - BlockhashProvider provider = CreateBlockHashProvider(tree, Frontier.Instance); + BlockhashProvider provider = CreateBlockHashProvider(blockTreeBuilder.HeaderStore, Frontier.Instance); BlockHeader head = tree.FindHeader(chainLength - 1, BlockTreeLookupOptions.None)!; Block current = Build.A.Block.WithParent(head).TestObject; - Hash256? result = provider.GetBlockhash(current.Header, chainLength + 1); + Hash256? result = provider.GetBlockhash(current.Header, chainLength + 1, Frontier.Instance); Assert.That(result, Is.Null); } @@ -228,12 +245,13 @@ public void Can_lookup_up_to_256_before() const int chainLength = 512; Block genesis = Build.A.Block.Genesis.TestObject; - BlockTree tree = Build.A.BlockTree(genesis).OfChainLength(chainLength).TestObject; + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree(genesis).OfChainLength(chainLength); + BlockTree tree = blockTreeBuilder.TestObject; - BlockhashProvider provider = CreateBlockHashProvider(tree, Frontier.Instance); + BlockhashProvider provider = CreateBlockHashProvider(blockTreeBuilder.HeaderStore, Frontier.Instance); BlockHeader head = tree.FindHeader(chainLength - 1, BlockTreeLookupOptions.None)!; Block current = Build.A.Block.WithParent(head).TestObject; - Hash256? result = provider.GetBlockhash(current.Header, chainLength - 256); + Hash256? result = provider.GetBlockhash(current.Header, chainLength - 256, Frontier.Instance); Assert.That(result, Is.EqualTo(tree.FindHeader(256, BlockTreeLookupOptions.None)!.Hash)); } @@ -243,12 +261,13 @@ public void No_lookup_more_than_256_before() const int chainLength = 512; Block genesis = Build.A.Block.Genesis.TestObject; - BlockTree tree = Build.A.BlockTree(genesis).OfChainLength(chainLength).TestObject; + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree(genesis).OfChainLength(chainLength); + BlockTree tree = blockTreeBuilder.TestObject; - BlockhashProvider provider = CreateBlockHashProvider(tree, Frontier.Instance); + BlockhashProvider provider = CreateBlockHashProvider(blockTreeBuilder.HeaderStore, Frontier.Instance); BlockHeader head = tree.FindHeader(chainLength - 1, BlockTreeLookupOptions.None)!; Block current = Build.A.Block.WithParent(head).TestObject; - Hash256? result = provider.GetBlockhash(current.Header, chainLength - 257); + Hash256? result = provider.GetBlockhash(current.Header, chainLength - 257, Frontier.Instance); Assert.That(result, Is.Null); } @@ -258,12 +277,13 @@ public void UInt_256_overflow() const int chainLength = 128; Block genesis = Build.A.Block.Genesis.TestObject; - BlockTree tree = Build.A.BlockTree(genesis).OfChainLength(chainLength).TestObject; + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree(genesis).OfChainLength(chainLength); + BlockTree tree = blockTreeBuilder.TestObject; - BlockhashProvider provider = CreateBlockHashProvider(tree, Frontier.Instance); + BlockhashProvider provider = CreateBlockHashProvider(blockTreeBuilder.HeaderStore, Frontier.Instance); BlockHeader head = tree.FindHeader(chainLength - 1, BlockTreeLookupOptions.None)!; Block current = Build.A.Block.WithParent(head).TestObject; - Hash256? result = provider.GetBlockhash(current.Header, 127); + Hash256? result = provider.GetBlockhash(current.Header, 127, Frontier.Instance); Assert.That(result, Is.EqualTo(head.Hash)); } @@ -275,7 +295,8 @@ public void UInt_256_overflow() public void Eip2935_enabled_Eip7709_disabled_and_then_get_hash(int chainLength) { Block genesis = Build.A.Block.Genesis.TestObject; - BlockTree tree = Build.A.BlockTree(genesis).OfHeadersOnly.OfChainLength(chainLength).TestObject; + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree(genesis).OfHeadersOnly.OfChainLength(chainLength); + BlockTree tree = blockTreeBuilder.TestObject; BlockHeader? head = tree.FindHeader(chainLength - 1, BlockTreeLookupOptions.None); // number = chainLength @@ -288,12 +309,12 @@ public void Eip2935_enabled_Eip7709_disabled_and_then_get_hash(int chainLength) var specProvider = new CustomSpecProvider( (new ForkActivation(0, genesis.Timestamp), Frontier.Instance), (new ForkActivation(0, current.Timestamp), Prague.Instance)); - BlockhashProvider provider = new(tree, specProvider, worldState, LimboLogs.Instance); - BlockhashStore store = new(specProvider, worldState); + BlockhashProvider provider = new(new BlockhashCache(blockTreeBuilder.HeaderStore, LimboLogs.Instance), worldState, LimboLogs.Instance); + BlockhashStore store = new(worldState); using var _ = worldState.BeginScope(current.Header); - Hash256? result = provider.GetBlockhash(current.Header, chainLength - 1); + Hash256? result = provider.GetBlockhash(current.Header, chainLength - 1, Frontier.Instance); Assert.That(result, Is.EqualTo(head?.Hash)); AssertGenesisHash(Prague.Instance, provider, current.Header, genesis.Hash); @@ -302,21 +323,19 @@ public void Eip2935_enabled_Eip7709_disabled_and_then_get_hash(int chainLength) current = Build.A.Block.WithParent(head!).TestObject; tree.SuggestHeader(current.Header); - store.ApplyBlockhashStateChanges(current.Header); - result = provider.GetBlockhash(current.Header, chainLength); + store.ApplyBlockhashStateChanges(current.Header, specProvider.GetSpec(current.Header)); + result = provider.GetBlockhash(current.Header, chainLength, Frontier.Instance); Assert.That(result, Is.EqualTo(head?.Hash)); AssertGenesisHash(Prague.Instance, provider, current.Header, genesis.Hash); } - private static void AssertGenesisHash(IReleaseSpec spec, BlockhashProvider provider, BlockHeader currentHeader, - Hash256? genesisHash) + private static void AssertGenesisHash(IReleaseSpec spec, BlockhashProvider provider, BlockHeader currentHeader, Hash256? genesisHash) { - Hash256? result = provider.GetBlockhash(currentHeader, 0); - if ((spec.IsEip7709Enabled && currentHeader.Number > Eip2935Constants.RingBufferSize) || currentHeader.Number > 256) - Assert.That(result, Is.Null); - else - Assert.That(result, Is.EqualTo(genesisHash)); + Hash256? result = provider.GetBlockhash(currentHeader, 0, spec); + Assert.That(result, (spec.IsEip7709Enabled && currentHeader.Number > Eip2935Constants.RingBufferSize) || currentHeader.Number > 256 + ? Is.Null + : Is.EqualTo(genesisHash)); } [Test, MaxTime(Timeout.MaxTestTime)] @@ -333,10 +352,10 @@ public void Eip2935_poc_trimmed_hashes() Block current = Build.A.Block.WithParent(head!).WithStateRoot(stateRoot).TestObject; tree.SuggestHeader(current.Header); - var specProvider = new CustomSpecProvider( + ISpecProvider specProvider = new CustomSpecProvider( (new ForkActivation(0, genesis.Timestamp), Frontier.Instance), (new ForkActivation(0, current.Timestamp), Prague.Instance)); - BlockhashStore store = new(specProvider, worldState); + BlockhashStore store = new(worldState); using var _ = worldState.BeginScope(current.Header); @@ -346,9 +365,101 @@ public void Eip2935_poc_trimmed_hashes() current.Header.ParentHash = new Hash256("0x0011111111111111111111111111111111111111111111111111111111111111"); // 2. Store parent hash with leading zeros - store.ApplyBlockhashStateChanges(current.Header); + store.ApplyBlockhashStateChanges(current.Header, specProvider.GetSpec(current.Header)); // 3. Try to retrieve the parent hash from the state - var result = store.GetBlockHashFromState(current.Header, current.Header.Number - 1); + var result = store.GetBlockHashFromState(current.Header, current.Header.Number - 1, specProvider.GetSpec(current.Header)); Assert.That(result, Is.EqualTo(current.Header.ParentHash)); } + + [Test, MaxTime(Timeout.MaxTestTime)] + public void BlockhashStore_uses_custom_ring_buffer_size() + { + const int customRingBufferSize = 100; + const int chainLength = 150; + + Block genesis = Build.A.Block.Genesis.TestObject; + BlockTree tree = Build.A.BlockTree(genesis).OfHeadersOnly.OfChainLength(chainLength).TestObject; + BlockHeader? head = tree.FindHeader(chainLength - 1, BlockTreeLookupOptions.None); + + (IWorldState worldState, Hash256 stateRoot) = CreateWorldState(); + Block current = Build.A.Block.WithParent(head!).WithStateRoot(stateRoot).TestObject; + tree.SuggestHeader(current.Header); + + // Custom spec with non-standard ring buffer size + ReleaseSpec customSpec = new() + { + IsEip2935Enabled = true, + IsEip7709Enabled = true, + Eip2935RingBufferSize = customRingBufferSize + }; + + var specProvider = new CustomSpecProvider((new ForkActivation(0, genesis.Timestamp), customSpec)); + BlockhashStore store = new(worldState); + + using IDisposable _ = worldState.BeginScope(current.Header); + + // Insert code (account already created by CreateWorldState) + byte[] code = [1, 2, 3]; + worldState.InsertCode(Eip2935Constants.BlockHashHistoryAddress, ValueKeccak.Compute(code), code, customSpec); + + // Process current block (150) - stores block 149's hash + store.ApplyBlockhashStateChanges(current.Header, specProvider.GetSpec(current.Header)); + + // Simulate processing blocks 51-149 to fill the ring buffer + // At block 150 with buffer size 100, we need hashes for blocks 50-149 + // Block 150 stores block 149's hash (already done above) + // Now process blocks 51-149 from the tree to store blocks 50-148 + for (long blockNum = chainLength - customRingBufferSize + 1; blockNum < chainLength; blockNum++) + { + BlockHeader? header = tree.FindHeader(blockNum, BlockTreeLookupOptions.None); + Assert.That(header, Is.Not.Null, $"Block {blockNum} should exist in tree"); + + store.ApplyBlockhashStateChanges(header!, specProvider.GetSpec(header)); + } + + // Now verify all blocks behave correctly with custom ring buffer size + // At block 150 with buffer size 100, only blocks [50, 149] should be retrievable + for (long blockNum = 1; blockNum < chainLength - customRingBufferSize; blockNum++) + { + Hash256? result = store.GetBlockHashFromState(current.Header, blockNum, customSpec); + + Assert.That(result, Is.Null, + $"Block {blockNum} should be outside custom ring buffer of size {customRingBufferSize} (proves custom size is used, not default 8191)"); + } + + for (long blockNum = chainLength - customRingBufferSize; blockNum < chainLength; blockNum++) + { + BlockHeader? expectedHeader = tree.FindHeader(blockNum, BlockTreeLookupOptions.None); + Hash256? result = store.GetBlockHashFromState(current.Header, blockNum, customSpec); + + Assert.That(result, Is.EqualTo(expectedHeader!.Hash), + $"Block {blockNum} should be retrievable within custom ring buffer of size {customRingBufferSize}"); + } + } + + [Test, MaxTime(Timeout.MaxTestTime)] + [NonParallelizable] + public async Task Prefetches_come_in_wrong_order() + { + const int chainLength = 261; + + Block genesis = Build.A.Block.Genesis.TestObject; + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree(genesis) + .OfHeadersOnly + .OfChainLength(chainLength); + SlowHeaderStore slowHeaderStore = new(blockTreeBuilder.HeaderStore) { SlowBlockNumber = 2 }; + BlockTree tree = blockTreeBuilder.TestObject; + BlockHeader head = tree.FindHeader(chainLength - 1, BlockTreeLookupOptions.None)!; + long expectedBlockNumber = head.Number - 2; + BlockHeader expected = tree.FindHeader(expectedBlockNumber, BlockTreeLookupOptions.None)!; + BlockHeader previousHead = tree.FindHeader(chainLength - 4, BlockTreeLookupOptions.None)!; + BlockhashProvider provider = CreateBlockHashProvider(slowHeaderStore, Frontier.Instance); + CancellationTokenSource cts = new(TimeSpan.FromMilliseconds(5)); + Task previousHeadTask = provider.Prefetch(previousHead, cts.Token); + Task headTask = provider.Prefetch(head, CancellationToken.None); + await Task.WhenAll(previousHeadTask, headTask); + + Hash256? result = provider.GetBlockhash(head, expectedBlockNumber, Frontier.Instance); + Assert.That(result, Is.EqualTo(expected.Hash)); + } } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BadBlockStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BadBlockStoreTests.cs index dd149c475ccc..f8fefc3679d0 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BadBlockStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BadBlockStoreTests.cs @@ -12,6 +12,7 @@ namespace Nethermind.Blockchain.Test.Blocks; +[Parallelizable(ParallelScope.All)] public class BadBlockStoreTests { [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs index 525a99c5353c..861264efda96 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs @@ -15,6 +15,7 @@ namespace Nethermind.Blockchain.Test.Blocks; +[Parallelizable(ParallelScope.All)] public class BlockStoreTests { private readonly Func, EquivalencyAssertionOptions> _ignoreEncodedSize = options => options.Excluding(b => b.EncodedSize); @@ -37,7 +38,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/Blocks/HeaderStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/HeaderStoreTests.cs index bc1d967eb9ec..130bda2d4829 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/HeaderStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/HeaderStoreTests.cs @@ -11,9 +11,9 @@ namespace Nethermind.Blockchain.Test.Blocks; +[Parallelizable(ParallelScope.All)] public class HeaderStoreTests { - [Test] public void TestCanStoreAndGetHeader() { diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs b/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs index 86c7706fe3f5..4dbb1cc3e31a 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs @@ -11,14 +11,15 @@ namespace Nethermind.Blockchain.Test.Builders { public class FilterBuilder { - private static int _id; + private int _id; private BlockParameter _fromBlock = new(BlockParameterType.Latest); private BlockParameter _toBlock = new(BlockParameterType.Latest); private AddressFilter _address = AddressFilter.AnyAddress; private SequenceTopicsFilter _topicsFilter = new(); - private FilterBuilder() + private FilterBuilder(int id) { + _id = id; } public static FilterBuilder New() @@ -29,9 +30,9 @@ public static FilterBuilder New() public static FilterBuilder New(ref int currentFilterIndex) { - _id = currentFilterIndex; + int id = currentFilterIndex; currentFilterIndex++; - return new FilterBuilder(); + return new FilterBuilder(id); } public FilterBuilder WithId(int id) diff --git a/src/Nethermind/Nethermind.Blockchain.Test/CachedCodeInfoRepositoryTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/CachedCodeInfoRepositoryTests.cs new file mode 100644 index 000000000000..b933980d7e6b --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain.Test/CachedCodeInfoRepositoryTests.cs @@ -0,0 +1,434 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Concurrent; +using System.Collections.Frozen; +using System.Collections.Generic; +using FluentAssertions; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.Evm.CodeAnalysis; +using Nethermind.Evm.Precompiles; +using Nethermind.Specs.Forks; +using Nethermind.State; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class CachedCodeInfoRepositoryTests +{ + private static IReleaseSpec CreateSpecWithPrecompile(Address precompileAddress) + { + IReleaseSpec spec = Substitute.For(); + spec.Precompiles.Returns(new HashSet { precompileAddress }.ToFrozenSet()); + return spec; + } + + [Test] + public void Precompile_WithCachingEnabled_IsWrappedInCachedPrecompile() + { + // Arrange + TestPrecompile cachingPrecompile = new(supportsCaching: true); + Address precompileAddress = Address.FromNumber(100); + + FrozenDictionary precompiles = new Dictionary + { + [precompileAddress] = new(cachingPrecompile) + }.ToFrozenDictionary(); + + IPrecompileProvider precompileProvider = Substitute.For(); + precompileProvider.GetPrecompiles().Returns(precompiles); + + ICodeInfoRepository baseRepository = Substitute.For(); + ConcurrentDictionary> cache = new(); + + IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); + + // Act + CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + + // Assert + codeInfo.Should().NotBeNull(); + codeInfo.Precompile.Should().NotBeSameAs(cachingPrecompile); + codeInfo.Precompile!.GetType().Name.Should().Contain("CachedPrecompile"); + } + + [Test] + public void Precompile_WithCachingDisabled_IsNotWrapped() + { + // Arrange + TestPrecompile nonCachingPrecompile = new(supportsCaching: false); + Address precompileAddress = Address.FromNumber(100); + + FrozenDictionary precompiles = new Dictionary + { + [precompileAddress] = new(nonCachingPrecompile) + }.ToFrozenDictionary(); + + IPrecompileProvider precompileProvider = Substitute.For(); + precompileProvider.GetPrecompiles().Returns(precompiles); + + ICodeInfoRepository baseRepository = Substitute.For(); + ConcurrentDictionary> cache = new(); + + IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); + + // Act + CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + + // Assert + codeInfo.Should().NotBeNull(); + codeInfo.Precompile.Should().BeSameAs(nonCachingPrecompile); + } + + [Test] + public void IdentityPrecompile_IsNotWrapped_WhenCacheEnabled() + { + // Arrange + FrozenDictionary precompiles = new Dictionary + { + [IdentityPrecompile.Address] = new(IdentityPrecompile.Instance) + }.ToFrozenDictionary(); + + IPrecompileProvider precompileProvider = Substitute.For(); + precompileProvider.GetPrecompiles().Returns(precompiles); + + ICodeInfoRepository baseRepository = Substitute.For(); + ConcurrentDictionary> cache = new(); + + IReleaseSpec spec = CreateSpecWithPrecompile(IdentityPrecompile.Address); + + // Act + CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); + CodeInfo codeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); + + // Assert + codeInfo.Should().NotBeNull(); + codeInfo.Precompile.Should().BeSameAs(IdentityPrecompile.Instance); + } + + [Test] + public void CachedPrecompile_CachesResults_ForCachingEnabledPrecompile() + { + // Arrange + int runCount = 0; + TestPrecompile cachingPrecompile = new(supportsCaching: true, onRun: () => runCount++); + Address precompileAddress = Address.FromNumber(100); + + FrozenDictionary precompiles = new Dictionary + { + [precompileAddress] = new(cachingPrecompile) + }.ToFrozenDictionary(); + + IPrecompileProvider precompileProvider = Substitute.For(); + precompileProvider.GetPrecompiles().Returns(precompiles); + + ICodeInfoRepository baseRepository = Substitute.For(); + ConcurrentDictionary> cache = new(); + + IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); + + CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + + byte[] input = [1, 2, 3]; + + // Act - run twice with same input + codeInfo.Precompile!.Run(input, Prague.Instance); + codeInfo.Precompile!.Run(input, Prague.Instance); + + // Assert - should only run once due to caching + runCount.Should().Be(1); + cache.Count.Should().Be(1); + } + + [Test] + public void NonCachingPrecompile_DoesNotCacheResults() + { + // Arrange + int runCount = 0; + TestPrecompile nonCachingPrecompile = new(supportsCaching: false, onRun: () => runCount++); + Address precompileAddress = Address.FromNumber(100); + + FrozenDictionary precompiles = new Dictionary + { + [precompileAddress] = new(nonCachingPrecompile) + }.ToFrozenDictionary(); + + IPrecompileProvider precompileProvider = Substitute.For(); + precompileProvider.GetPrecompiles().Returns(precompiles); + + ICodeInfoRepository baseRepository = Substitute.For(); + ConcurrentDictionary> cache = new(); + + IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); + + CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + + byte[] input = [1, 2, 3]; + + // Act - run twice with same input + codeInfo.Precompile!.Run(input, Prague.Instance); + codeInfo.Precompile!.Run(input, Prague.Instance); + + // Assert - should run twice since caching is disabled + runCount.Should().Be(2); + cache.Count.Should().Be(0); + } + + [Test] + public void NullCache_DoesNotWrapAnyPrecompiles() + { + // Arrange + TestPrecompile cachingPrecompile = new(supportsCaching: true); + Address precompileAddress = Address.FromNumber(100); + + FrozenDictionary precompiles = new Dictionary + { + [precompileAddress] = new(cachingPrecompile) + }.ToFrozenDictionary(); + + IPrecompileProvider precompileProvider = Substitute.For(); + precompileProvider.GetPrecompiles().Returns(precompiles); + + ICodeInfoRepository baseRepository = Substitute.For(); + + IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); + + // Act - pass null cache + CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, null); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + + // Assert - precompile should not be wrapped + codeInfo.Should().NotBeNull(); + codeInfo.Precompile.Should().BeSameAs(cachingPrecompile); + } + + [Test] + public void Sha256Precompile_IsWrapped_WhenCacheEnabled() + { + // Arrange - Sha256Precompile has SupportsCaching = true (default) + FrozenDictionary precompiles = new Dictionary + { + [Sha256Precompile.Address] = new(Sha256Precompile.Instance) + }.ToFrozenDictionary(); + + IPrecompileProvider precompileProvider = Substitute.For(); + precompileProvider.GetPrecompiles().Returns(precompiles); + + ICodeInfoRepository baseRepository = Substitute.For(); + ConcurrentDictionary> cache = new(); + + IReleaseSpec spec = CreateSpecWithPrecompile(Sha256Precompile.Address); + + // Act + CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); + CodeInfo codeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); + + // Assert - Sha256Precompile should be wrapped (unlike IdentityPrecompile) + codeInfo.Should().NotBeNull(); + codeInfo.Precompile.Should().NotBeSameAs(Sha256Precompile.Instance); + codeInfo.Precompile!.GetType().Name.Should().Contain("CachedPrecompile"); + } + + [Test] + public void MixedPrecompiles_OnlyCachingEnabledAreWrapped() + { + // Arrange - mix of caching and non-caching precompiles + FrozenDictionary precompiles = new Dictionary + { + [Sha256Precompile.Address] = new(Sha256Precompile.Instance), // SupportsCaching = true + [IdentityPrecompile.Address] = new(IdentityPrecompile.Instance) // SupportsCaching = false + }.ToFrozenDictionary(); + + IPrecompileProvider precompileProvider = Substitute.For(); + precompileProvider.GetPrecompiles().Returns(precompiles); + + ICodeInfoRepository baseRepository = Substitute.For(); + ConcurrentDictionary> cache = new(); + + IReleaseSpec spec = Substitute.For(); + spec.Precompiles.Returns(new HashSet + { + Sha256Precompile.Address, + IdentityPrecompile.Address + }.ToFrozenSet()); + + // Act + CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); + CodeInfo sha256CodeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); + CodeInfo identityCodeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); + + // Assert - Sha256 wrapped, Identity not wrapped + sha256CodeInfo.Precompile.Should().NotBeSameAs(Sha256Precompile.Instance); + sha256CodeInfo.Precompile!.GetType().Name.Should().Contain("CachedPrecompile"); + + identityCodeInfo.Precompile.Should().BeSameAs(IdentityPrecompile.Instance); + } + + [Test] + public void CachedPrecompile_DifferentInputs_CreateSeparateCacheEntries() + { + // Arrange + int runCount = 0; + TestPrecompile cachingPrecompile = new(supportsCaching: true, onRun: () => runCount++); + Address precompileAddress = Address.FromNumber(100); + + FrozenDictionary precompiles = new Dictionary + { + [precompileAddress] = new(cachingPrecompile) + }.ToFrozenDictionary(); + + IPrecompileProvider precompileProvider = Substitute.For(); + precompileProvider.GetPrecompiles().Returns(precompiles); + + ICodeInfoRepository baseRepository = Substitute.For(); + ConcurrentDictionary> cache = new(); + + IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); + + CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + + byte[] input1 = [1, 2, 3]; + byte[] input2 = [4, 5, 6]; + + // Act - run with different inputs + codeInfo.Precompile!.Run(input1, Prague.Instance); + codeInfo.Precompile!.Run(input2, Prague.Instance); + codeInfo.Precompile!.Run(input1, Prague.Instance); // should hit cache + codeInfo.Precompile!.Run(input2, Prague.Instance); // should hit cache + + // Assert - should run twice (once per unique input), cache should have 2 entries + runCount.Should().Be(2); + cache.Count.Should().Be(2); + } + + [Test] + public void CachedPrecompile_ReturnsCachedResult_OnCacheHit() + { + // Arrange + int runCount = 0; + byte[] expectedOutput = [10, 20, 30]; + TestPrecompile cachingPrecompile = new(supportsCaching: true, onRun: () => runCount++, fixedOutput: expectedOutput); + Address precompileAddress = Address.FromNumber(100); + + FrozenDictionary precompiles = new Dictionary + { + [precompileAddress] = new(cachingPrecompile) + }.ToFrozenDictionary(); + + IPrecompileProvider precompileProvider = Substitute.For(); + precompileProvider.GetPrecompiles().Returns(precompiles); + + ICodeInfoRepository baseRepository = Substitute.For(); + ConcurrentDictionary> cache = new(); + + IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); + + CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + + byte[] input = [1, 2, 3]; + + // Act - run twice with same input + Result result1 = codeInfo.Precompile!.Run(input, Prague.Instance); + Result result2 = codeInfo.Precompile!.Run(input, Prague.Instance); + + // Assert - both results should be the same cached value + runCount.Should().Be(1); + ((bool)result1).Should().BeTrue(); + ((bool)result2).Should().BeTrue(); + result1.Data.Should().BeEquivalentTo(expectedOutput); + result2.Data.Should().BeEquivalentTo(expectedOutput); + } + + [Test] + public void Sha256Precompile_CachesResults_WithRealComputation() + { + // Arrange + FrozenDictionary precompiles = new Dictionary + { + [Sha256Precompile.Address] = new(Sha256Precompile.Instance) + }.ToFrozenDictionary(); + + IPrecompileProvider precompileProvider = Substitute.For(); + precompileProvider.GetPrecompiles().Returns(precompiles); + + ICodeInfoRepository baseRepository = Substitute.For(); + ConcurrentDictionary> cache = new(); + + IReleaseSpec spec = CreateSpecWithPrecompile(Sha256Precompile.Address); + + CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); + CodeInfo codeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); + + byte[] input = [1, 2, 3, 4, 5]; + + // Act - run twice with same input + Result result1 = codeInfo.Precompile!.Run(input, Prague.Instance); + Result result2 = codeInfo.Precompile!.Run(input, Prague.Instance); + + // Assert - results should match and cache should have entry + ((bool)result1).Should().BeTrue(); + ((bool)result2).Should().BeTrue(); + result1.Data.Should().BeEquivalentTo(result2.Data); + cache.Count.Should().Be(1); + } + + [Test] + public void IdentityPrecompile_DoesNotCache_WithRealComputation() + { + // Arrange + FrozenDictionary precompiles = new Dictionary + { + [IdentityPrecompile.Address] = new(IdentityPrecompile.Instance) + }.ToFrozenDictionary(); + + IPrecompileProvider precompileProvider = Substitute.For(); + precompileProvider.GetPrecompiles().Returns(precompiles); + + ICodeInfoRepository baseRepository = Substitute.For(); + ConcurrentDictionary> cache = new(); + + IReleaseSpec spec = CreateSpecWithPrecompile(IdentityPrecompile.Address); + + CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); + CodeInfo codeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); + + byte[] input = [1, 2, 3, 4, 5]; + + // Act - run twice with same input + Result result1 = codeInfo.Precompile!.Run(input, Prague.Instance); + Result result2 = codeInfo.Precompile!.Run(input, Prague.Instance); + + // Assert - results should match but cache should be empty (no caching for Identity) + ((bool)result1).Should().BeTrue(); + ((bool)result2).Should().BeTrue(); + result1.Data.Should().BeEquivalentTo(result2.Data); + cache.Count.Should().Be(0); // Key difference from Sha256 test + } + + private class TestPrecompile(bool supportsCaching, Action? onRun = null, byte[]? fixedOutput = null) : IPrecompile + { + public bool SupportsCaching => supportsCaching; + + public long BaseGasCost(IReleaseSpec releaseSpec) => 0; + + public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 0; + + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + { + onRun?.Invoke(); + return fixedOutput ?? inputData.ToArray(); + } + } +} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ChainHeadReadOnlyStateProviderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ChainHeadReadOnlyStateProviderTests.cs index 6711e1283518..092d3b8b14cb 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ChainHeadReadOnlyStateProviderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ChainHeadReadOnlyStateProviderTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class ChainHeadReadOnlyStateProviderTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/ClefSignerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/ClefSignerTests.cs index 54d83e4a3617..4ecb01eee74f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/ClefSignerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/ClefSignerTests.cs @@ -15,6 +15,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class ClefSignerTests { [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/CompositeTxSourceTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/CompositeTxSourceTests.cs index cc61b6a7fc4a..6a9b7270cfc5 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/CompositeTxSourceTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/CompositeTxSourceTests.cs @@ -14,6 +14,7 @@ namespace Nethermind.Blockchain.Test.Consensus; +[Parallelizable(ParallelScope.All)] public class CompositeTxSourceTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NethDevSealEngineTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NethDevSealEngineTests.cs index d9f0704c5167..e06a1eeaf3d6 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NethDevSealEngineTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NethDevSealEngineTests.cs @@ -12,6 +12,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class NethDevSealEngineTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSealEngineTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSealEngineTests.cs index dcf4bf713101..df8bed08b4f1 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSealEngineTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSealEngineTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class NullSealEngineTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs index 628ff7f9c60f..1de624592784 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs @@ -12,6 +12,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class NullSignerTests { [Test, MaxTime(Timeout.MaxTestTime)] @@ -19,7 +20,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/Consensus/OneByOneTxSourceTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/OneByOneTxSourceTests.cs index 354848108367..0c26037ba28e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/OneByOneTxSourceTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/OneByOneTxSourceTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class OneByOneTxSourceTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SealEngineExceptionTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SealEngineExceptionTests.cs index 5d5fcb9602c4..c6bef99ca9bc 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SealEngineExceptionTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SealEngineExceptionTests.cs @@ -8,6 +8,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class SealEngineExceptionTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SignerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SignerTests.cs index edd88c1a7224..fe897e8ff3e9 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SignerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SignerTests.cs @@ -16,6 +16,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class SignerTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SinglePendingTxSelectorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SinglePendingTxSelectorTests.cs index feb4d47964c0..ad529fda85ba 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SinglePendingTxSelectorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SinglePendingTxSelectorTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Consensus { + [Parallelizable(ParallelScope.All)] public class SinglePendingTxSelectorTests { private readonly BlockHeader _anyParent = Build.A.BlockHeader.TestObject; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/DaoDataTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/DaoDataTests.cs index b7483603e603..f2dbf4d95c20 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/DaoDataTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/DaoDataTests.cs @@ -7,6 +7,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class DaoDataTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Data/FileLocalDataSourceTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Data/FileLocalDataSourceTests.cs index 03e9008d489f..d13afa2d1143 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Data/FileLocalDataSourceTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Data/FileLocalDataSourceTests.cs @@ -16,6 +16,7 @@ namespace Nethermind.Blockchain.Test.Data { + [Parallelizable(ParallelScope.All)] public class FileLocalDataSourceTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ExitOnBlocknumberHandlerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ExitOnBlocknumberHandlerTests.cs index 72da5efe337e..34d04d45bf72 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ExitOnBlocknumberHandlerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ExitOnBlocknumberHandlerTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class ExitOnBlocknumberHandlerTests { [TestCase(10, false)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs index cbedcef98a5e..4dde1eadbff2 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Filters; [TestFixture] +[Parallelizable(ParallelScope.All)] public class AddressFilterTests { [Test] @@ -98,10 +99,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 +110,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 +207,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 +217,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 +230,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 +241,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 +255,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 40523468fe8c..a8dd1a2a6c9c 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; @@ -19,11 +21,11 @@ namespace Nethermind.Blockchain.Test.Filters; +[Parallelizable(ParallelScope.None)] public class FilterManagerTests { - private IFilterStore _filterStore = null!; - private IBranchProcessor _branchProcessor = null!; - private IMainProcessingContext _mainProcessingContext = null!; + private FilterStore _filterStore = null!; + private TestMainProcessingContext _mainProcessingContext = null!; private ITxPool _txPool = null!; private ILogManager _logManager = null!; private FilterManager _filterManager = null!; @@ -34,10 +36,8 @@ public class FilterManagerTests public void Setup() { _currentFilterId = 0; - _filterStore = Substitute.For(); - _branchProcessor = Substitute.For(); - _mainProcessingContext = Substitute.For(); - _mainProcessingContext.BranchProcessor.Returns(_branchProcessor); + _filterStore = new FilterStore(new TimerFactory(), 400, 100); + _mainProcessingContext = new TestMainProcessingContext(); _txPool = Substitute.For(); _logManager = LimboLogs.Instance; } @@ -49,11 +49,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(600); _filterManager.GetLogs(0).Should().BeEmpty(); } @@ -255,6 +255,7 @@ public void logs_should_be_empty_for_existing_block_and_addresses_and_non_existi [Test, MaxTime(Timeout.MaxTestTime)] [TestCase(1, 1)] [TestCase(5, 3)] + [NonParallelizable] public void logs_should_have_correct_log_indexes(int filtersCount, int logsPerTx) { const int txCount = 10; @@ -321,19 +322,19 @@ private void Assert(IEnumerable> filterBuilders, // adding always a simple block filter and test Block block = Build.A.Block.TestObject; - BlockFilter blockFilter = new(_currentFilterId++, 0); + 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, [])); + _mainProcessingContext.TestBranchProcessor.RaiseBlockProcessed(new BlockProcessedEventArgs(block, [])); int index = 1; foreach (TxReceipt receipt in receipts) { - _mainProcessingContext.TransactionProcessed += Raise.EventWith(_branchProcessor, + _mainProcessingContext.RaiseTransactionProcessed( new TxProcessedEventArgs(index, Build.A.Transaction.TestObject, block.Header, receipt)); index++; } @@ -367,3 +368,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 a1e60a9ea8ff..dfec4eac051e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs @@ -11,20 +11,22 @@ 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; using NUnit.Framework; namespace Nethermind.Blockchain.Test.Filters; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class FilterStoreTests { [Test, MaxTime(Timeout.MaxTestTime)] public void Can_save_and_load_block_filter() { FilterStore store = new(new TimerFactory()); - BlockFilter filter = store.CreateBlockFilter(1); + BlockFilter filter = store.CreateBlockFilter(); store.SaveFilter(filter); Assert.That(store.FilterExists(0), Is.True, "exists"); Assert.That(store.GetFilterType(filter.Id), Is.EqualTo(FilterType.BlockFilter), "type"); @@ -45,7 +47,7 @@ public void Cannot_overwrite_filters() { FilterStore store = new(new TimerFactory()); - BlockFilter externalFilter = new(100, 1); + BlockFilter externalFilter = new(100); store.SaveFilter(externalFilter); Assert.Throws(() => store.SaveFilter(externalFilter)); } @@ -55,7 +57,7 @@ public void Ids_are_incremented_when_storing_externally_created_filter() { FilterStore store = new(new TimerFactory()); - BlockFilter externalFilter = new(100, 1); + BlockFilter externalFilter = new(100); store.SaveFilter(externalFilter); LogFilter filter = store.CreateLogFilter(new BlockParameter(1), new BlockParameter(2)); store.SaveFilter(filter); @@ -69,13 +71,13 @@ public void Ids_are_incremented_when_storing_externally_created_filter() public void Remove_filter_removes_and_notifies() { FilterStore store = new(new TimerFactory()); - BlockFilter filter = store.CreateBlockFilter(1); + BlockFilter filter = store.CreateBlockFilter(); store.SaveFilter(filter); bool hasNotified = false; 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"); } @@ -83,7 +85,7 @@ public void Remove_filter_removes_and_notifies() public void Can_get_filters_by_type() { FilterStore store = new(new TimerFactory()); - BlockFilter filter1 = store.CreateBlockFilter(1); + BlockFilter filter1 = store.CreateBlockFilter(); store.SaveFilter(filter1); LogFilter filter2 = store.CreateLogFilter(new BlockParameter(1), new BlockParameter(2)); store.SaveFilter(filter2); @@ -102,14 +104,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 +126,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); @@ -149,24 +154,25 @@ public void Correctly_creates_topics_filter(IEnumerable topics, TopicsFi } [Test, MaxTime(Timeout.MaxTestTime)] + [Parallelizable(ParallelScope.None)] public async Task CleanUps_filters() { List removedFilterIds = new(); FilterStore store = new(new TimerFactory(), 50, 20); store.FilterRemoved += (_, e) => removedFilterIds.Add(e.FilterId); - store.SaveFilter(store.CreateBlockFilter(1)); - store.SaveFilter(store.CreateBlockFilter(2)); + store.SaveFilter(store.CreateBlockFilter()); + store.SaveFilter(store.CreateBlockFilter()); store.SaveFilter(store.CreateLogFilter(BlockParameter.Earliest, BlockParameter.Latest)); store.SaveFilter(store.CreatePendingTransactionFilter()); await Task.Delay(30); store.RefreshFilter(0); await Task.Delay(30); store.RefreshFilter(0); - await Task.Delay(30); - Assert.That(store.FilterExists(0), Is.True, "filter 0 exists"); - Assert.That(store.FilterExists(1), Is.False, "filter 1 doesn't exist"); - Assert.That(store.FilterExists(2), Is.False, "filter 2 doesn't exist"); - Assert.That(store.FilterExists(3), Is.False, "filter 3 doesn't exist"); - Assert.That(removedFilterIds, Is.EquivalentTo([1, 2, 3])); + Assert.That(() => store.FilterExists(0), Is.True.After(30, 5), "filter 0 exists"); + 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 27771f99a913..df6bde0db6b9 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogFilterTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogFilterTests.cs @@ -11,6 +11,8 @@ namespace Nethermind.Blockchain.Test.Filters; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class LogFilterTests { private int _filterCounter; @@ -121,7 +123,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 +135,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 +147,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 +159,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 +171,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 +183,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 +195,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/Filters/LogIndexFilterVisitorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogIndexFilterVisitorTests.cs new file mode 100644 index 000000000000..3a901bfaafdf --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogIndexFilterVisitorTests.cs @@ -0,0 +1,413 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Nethermind.Blockchain.Filters; +using Nethermind.Blockchain.Test.Builders; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Db.LogIndex; +using Nethermind.Facade.Filters; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Blockchain.Test.Filters; + +[Parallelizable(ParallelScope.All)] +public class LogIndexFilterVisitorTests +{ + private record Ranges(Dictionary> Address, Dictionary>[] Topic) + { + public List this[Address address] => Address[address]; + public List this[int topicIndex, Hash256 hash] => Topic[topicIndex][hash]; + } + + public class EnumeratorWrapper(int[] array) : IEnumerator + { + private readonly IEnumerator _enumerator = array.Cast().GetEnumerator(); + public bool MoveNext() => _enumerator.MoveNext(); + public void Reset() => _enumerator.Reset(); + public int Current => _enumerator.Current; + object IEnumerator.Current => Current; + public virtual void Dispose() => _enumerator.Dispose(); + } + + [TestCase( + new[] { 1, 3, 5, 7, 9, }, + new[] { 0, 2, 4, 6, 8 }, + TestName = "Non-intersecting, but similar ranges" + )] + [TestCase( + new[] { 1, 2, 3, 4, 5, }, + new[] { 5, 6, 7, 8, 9 }, + TestName = "Intersects on first/last" + )] + [TestCase( + new[] { 1, 2, 3, 4, 5, }, + new[] { 6, 7, 8, 9, 10 }, + TestName = "Non-intersecting ranges" + )] + public void IntersectEnumerator(int[] s1, int[] s2) + { + var expected = s1.Intersect(s2).Order().ToArray(); + + VerifyEnumerator(s1, s2, expected); + VerifyEnumerator(s2, s1, expected); + } + + [TestCase(1, 1)] + [TestCase(20, 20)] + [TestCase(20, 100)] + [TestCase(100, 100)] + [TestCase(1000, 1000)] + public void IntersectEnumerator_Random(int len1, int len2) + { + var random = new Random(42); + var s1 = RandomAscending(random, len1, Math.Max(1, len1 / 10)); + var s2 = RandomAscending(random, len2, Math.Max(1, len2 / 10)); + + var expected = s1.Intersect(s2).Order().ToArray(); + Assert.That(expected, Is.Not.Empty, "Unreliable test: Needs non-empty sequence to verify against."); + + VerifyEnumerator(s1, s2, expected); + VerifyEnumerator(s2, s1, expected); + } + + [TestCase(0, 0)] + [TestCase(0, 1)] + [TestCase(0, 10)] + public void IntersectEnumerator_SomeEmpty(int len1, int len2) + { + var s1 = Enumerable.Range(0, len1).ToArray(); + var s2 = Enumerable.Range(0, len2).ToArray(); + + VerifyEnumerator(s1, s2, []); + VerifyEnumerator(s2, s1, []); + } + + [TestCase( + new[] { 1, 2, 3, 4, 5, }, + new[] { 2, 3, 4 }, + TestName = "Contained" + )] + [TestCase( + new[] { 1, 2, 3, 4, 5, }, + new[] { 1, 2, 3, 4, 5, }, + TestName = "Identical" + )] + [TestCase( + new[] { 1, 3, 5, 7, 9, }, + new[] { 2, 4, 6, 8, 10 }, + TestName = "Complementary" + )] + public void UnionEnumerator(int[] s1, int[] s2) + { + var expected = s1.Union(s2).Distinct().Order().ToArray(); + + VerifyEnumerator(s1, s2, expected); + VerifyEnumerator(s2, s1, expected); + } + + [TestCase(1, 1)] + [TestCase(20, 20)] + [TestCase(20, 100)] + [TestCase(100, 100)] + [TestCase(1000, 1000)] + public void UnionEnumerator_Random(int len1, int len2) + { + var random = new Random(42); + var s1 = RandomAscending(random, len1, Math.Max(1, len1 / 10)); + var s2 = RandomAscending(random, len2, Math.Max(1, len2 / 10)); + + var expected = s1.Union(s2).Distinct().Order().ToArray(); + + VerifyEnumerator(s1, s2, expected); + VerifyEnumerator(s2, s1, expected); + } + + [TestCase(0, 0)] + [TestCase(0, 1)] + [TestCase(0, 10)] + public void UnionEnumerator_SomeEmpty(int len1, int len2) + { + var s1 = Enumerable.Range(0, len1).ToArray(); + var s2 = Enumerable.Range(0, len2).ToArray(); + + var expected = s1.Union(s2).Distinct().Order().ToArray(); + + VerifyEnumerator(s1, s2, expected); + VerifyEnumerator(s2, s1, expected); + } + + [TestCaseSource(nameof(FilterTestData))] + public void FilterEnumerator(string name, LogFilter filter, List expected) + { + Assert.That(expected, + Has.Count.InRange(from: 1, to: ToBlock - FromBlock - 1), + "Unreliable test: none or all blocks are selected." + ); + ILogIndexStorage storage = Substitute.For(); + + foreach ((Address address, List range) in LogIndexRanges.Address) + { + storage + .GetEnumerator(address, Arg.Any(), Arg.Any()) + .Returns(info => range.SkipWhile(x => x < info.ArgAt(1)).TakeWhile(x => x <= info.ArgAt(2)).GetEnumerator()); + } + + for (var i = 0; i < LogIndexRanges.Topic.Length; i++) + { + foreach ((Hash256 topic, List range) in LogIndexRanges.Topic[i]) + { + storage + .GetEnumerator(Arg.Is(i), topic, Arg.Any(), Arg.Any()) + .Returns(info => range.SkipWhile(x => x < info.ArgAt(2)).TakeWhile(x => x <= info.ArgAt(3)).GetEnumerator()); + } + } + + Assert.That(storage.EnumerateBlockNumbersFor(filter, FromBlock, ToBlock), Is.EquivalentTo(expected)); + } + + [TestCaseSource(nameof(FilterTestData))] + public void FilterEnumerator_Dispose(string name, LogFilter filter, List _) + { + int[] blockNumbers = [1, 2, 3, 4, 5]; + List> enumerators = []; + + ILogIndexStorage storage = Substitute.For(); + storage.GetEnumerator(Arg.Any
(), Arg.Any(), Arg.Any()).Returns(_ => MockEnumerator()); + storage.GetEnumerator(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(_ => MockEnumerator()); + + storage.EnumerateBlockNumbersFor(filter, FromBlock, ToBlock).ForEach(_ => { }); + + enumerators.ForEach(enumerator => enumerator.Received().Dispose()); + + IEnumerator MockEnumerator() + { + IEnumerator? enumerator = Substitute.ForPartsOf(blockNumbers); + enumerators.Add(enumerator); + return enumerator; + } + } + + public static IEnumerable FilterTestData + { + get + { + yield return new TestCaseData( + "AddressA", // name + + BuildFilter() // filter + .WithAddress(TestItem.AddressA) + .Build(), + + LogIndexRanges[TestItem.AddressA] // expected range + ); + + yield return new TestCaseData( + "AddressA or AddressA", + + BuildFilter() + .WithAddresses(TestItem.AddressA, TestItem.AddressA) + .Build(), + + LogIndexRanges[TestItem.AddressA] + ); + + yield return new TestCaseData( + "AddressA or AddressB", + + BuildFilter() + .WithAddresses(TestItem.AddressA, TestItem.AddressB) + .Build(), + + LogIndexRanges[TestItem.AddressA].Union(LogIndexRanges[TestItem.AddressB]) + .Distinct().Order().ToList() + ); + + yield return new TestCaseData( + "TopicA", + + BuildFilter() + .WithTopicExpressions( + TestTopicExpressions.Specific(TestItem.KeccakA) + ).Build(), + + LogIndexRanges[0, TestItem.KeccakA] + ); + + yield return new TestCaseData( + "TopicA or TopicA", + + BuildFilter() + .WithTopicExpressions( + TestTopicExpressions.Or(TestItem.KeccakA, TestItem.KeccakA) + ).Build(), + + LogIndexRanges[0, TestItem.KeccakA] + ); + + yield return new TestCaseData( + "TopicA or TopicB", + + BuildFilter() + .WithTopicExpressions( + TestTopicExpressions.Or(TestItem.KeccakA, TestItem.KeccakB) + ).Build(), + + LogIndexRanges[0, TestItem.KeccakA].Union(LogIndexRanges[0, TestItem.KeccakB]) + .Distinct().Order().ToList() + ); + + yield return new TestCaseData( + "TopicA, TopicB", + + BuildFilter() + .WithTopicExpressions( + TestTopicExpressions.Specific(TestItem.KeccakA), + TestTopicExpressions.Specific(TestItem.KeccakB) + ).Build(), + + LogIndexRanges[0, TestItem.KeccakA] + .Intersect(LogIndexRanges[1, TestItem.KeccakB]) + .Distinct().Order().ToList() + ); + + yield return new TestCaseData( + "TopicA, -, TopicA", + + BuildFilter() + .WithTopicExpressions( + TestTopicExpressions.Specific(TestItem.KeccakA), + TestTopicExpressions.Any, + TestTopicExpressions.Specific(TestItem.KeccakA) + ).Build(), + + LogIndexRanges[0, TestItem.KeccakA] + .Intersect(LogIndexRanges[2, TestItem.KeccakA]) + .Distinct().Order().ToList() + ); + + yield return new TestCaseData( + "TopicA, -, TopicB or TopicC", + + BuildFilter() + .WithTopicExpressions( + TestTopicExpressions.Specific(TestItem.KeccakA), + TestTopicExpressions.Any, + TestTopicExpressions.Or(TestItem.KeccakB, TestItem.KeccakC) + ).Build(), + LogIndexRanges[0, TestItem.KeccakA] + .Intersect(LogIndexRanges[2, TestItem.KeccakB].Union(LogIndexRanges[2, TestItem.KeccakC])) + .Distinct().Order().ToList() + ); + + yield return new TestCaseData( + "AddressA | TopicA or TopicB, TopicC", + + BuildFilter() + .WithAddress(TestItem.AddressA) + .WithTopicExpressions( + TestTopicExpressions.Or(TestItem.KeccakA, TestItem.KeccakB), + TestTopicExpressions.Specific(TestItem.KeccakC) + ).Build(), + + LogIndexRanges[0, TestItem.KeccakA].Union(LogIndexRanges[0, TestItem.KeccakB]) + .Intersect(LogIndexRanges[1, TestItem.KeccakC]) + .Intersect(LogIndexRanges[TestItem.AddressA]) + .Distinct().Order().ToList() + ); + + yield return new TestCaseData( + "AddressA or AddressB | TopicA or TopicB, -, TopicC, TopicD or TopicE", + + BuildFilter() + .WithAddresses(TestItem.AddressA, TestItem.AddressB) + .WithTopicExpressions( + TestTopicExpressions.Or(TestItem.KeccakA, TestItem.KeccakB), + TestTopicExpressions.Any, + TestTopicExpressions.Specific(TestItem.KeccakC), + TestTopicExpressions.Or(TestItem.KeccakD, TestItem.KeccakE) + ).Build(), + + LogIndexRanges[TestItem.AddressA].Union(LogIndexRanges[TestItem.AddressB]) + .Intersect(LogIndexRanges[0, TestItem.KeccakA].Union(LogIndexRanges[0, TestItem.KeccakB])) + .Intersect(LogIndexRanges[2, TestItem.KeccakC]) + .Intersect(LogIndexRanges[3, TestItem.KeccakD].Union(LogIndexRanges[3, TestItem.KeccakE])) + .Distinct().Order().ToList() + ); + } + } + + private static int[] RandomAscending(Random random, int count, int maxDelta) + { + var result = new int[count]; + + for (var i = 0; i < result.Length; i++) + { + var min = i > 0 ? result[i - 1] : -1; + result[i] = random.Next(min + 1, min + 1 + maxDelta); + } + + return result; + } + + private static void VerifyEnumerator(int[] s1, int[] s2, int[] ex) + where T : IEnumerator + { + using var enumerator = (T)Activator.CreateInstance( + typeof(T), + s1.Cast().GetEnumerator(), + s2.Cast().GetEnumerator() + )!; + + Assert.That(EnumerateOnce(enumerator), Is.EqualTo(ex)); + } + + private static IEnumerable EnumerateOnce(IEnumerator enumerator) + { + while (enumerator.MoveNext()) + yield return enumerator.Current; + } + + private const long FromBlock = 0; + private const long ToBlock = 99; + + private static readonly Ranges LogIndexRanges = GenerateLogIndexRanges(); + + private static Ranges GenerateLogIndexRanges() + { + var random = new Random(42); + + var addressRanges = new Dictionary>(); + foreach (Address address in new[] { TestItem.AddressA, TestItem.AddressB, TestItem.AddressC, TestItem.AddressD, TestItem.AddressE }) + { + var range = Enumerable.Range((int)FromBlock, (int)(ToBlock + 1)).Where(_ => random.NextDouble() < 0.3).ToList(); + addressRanges.Add(address, range); + } + + Dictionary>[] topicRanges = Enumerable + .Range(0, LogIndexStorage.MaxTopics) + .Select(_ => new Dictionary>()).ToArray(); + + foreach (Dictionary> ranges in topicRanges) + { + foreach (Hash256 topic in new[] { TestItem.KeccakA, TestItem.KeccakB, TestItem.KeccakC, TestItem.KeccakD, TestItem.KeccakE }) + { + var range = Enumerable.Range((int)FromBlock, (int)(ToBlock + 1)).Where(_ => random.NextDouble() < 0.2).ToList(); + ranges.Add(topic, range); + } + } + + return new(addressRanges, topicRanges); + } + + private static FilterBuilder BuildFilter() => FilterBuilder.New() + .FromBlock(FromBlock) + .ToBlock(ToBlock); +} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/TestProcessingContext.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/TestProcessingContext.cs new file mode 100644 index 000000000000..5b089c3f11c3 --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/TestProcessingContext.cs @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading; +using Nethermind.Consensus.Processing; +using Nethermind.Core; +using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; + +namespace Nethermind.Blockchain.Test.Filters; + +/// +/// Test implementation of IBranchProcessor that allows manual event raising. +/// +internal class TestBranchProcessor : IBranchProcessor +{ + public event EventHandler? BlockProcessed; + public event EventHandler? BlocksProcessing { add { } remove { } } + public event EventHandler? BlockProcessing { add { } remove { } } + + public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlocks, + ProcessingOptions processingOptions, IBlockTracer blockTracer, CancellationToken token = default) + => []; + + public void RaiseBlockProcessed(BlockProcessedEventArgs args) + => BlockProcessed?.Invoke(this, args); +} + +/// +/// Test implementation of IMainProcessingContext that allows manual event raising. +/// +internal class TestMainProcessingContext : IMainProcessingContext +{ + private readonly TestBranchProcessor _branchProcessor = new(); + + public ITransactionProcessor TransactionProcessor => null!; + public IBranchProcessor BranchProcessor => _branchProcessor; + public IBlockProcessor BlockProcessor => null!; + public IBlockchainProcessor BlockchainProcessor => null!; + public IBlockProcessingQueue BlockProcessingQueue => null!; + public IWorldState WorldState => null!; + public IGenesisLoader GenesisLoader => null!; + + public event EventHandler? TransactionProcessed; + + public TestBranchProcessor TestBranchProcessor => _branchProcessor; + + public void RaiseTransactionProcessed(TxProcessedEventArgs args) + => TransactionProcessed?.Invoke(this, args); +} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs index e8ae192041b0..ae11d8419b65 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs @@ -19,13 +19,17 @@ using Nethermind.Db; using Nethermind.Logging; using Nethermind.Db.Blooms; +using Nethermind.Db.LogIndex; using Nethermind.Facade.Filters; using Nethermind.Facade.Find; using NSubstitute; using NUnit.Framework; +using NUnit.Framework.Internal; namespace Nethermind.Blockchain.Test.Find; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class LogFinderTests { private IBlockTree _blockTree = null!; @@ -45,19 +49,19 @@ public void SetUp() [TearDown] public void TearDown() => _bloomStorage?.Dispose(); - private void SetUp(bool allowReceiptIterator) + private void SetUp(bool allowReceiptIterator, int chainLength = 5) { var specProvider = Substitute.For(); specProvider.GetSpec(Arg.Any()).IsEip155Enabled.Returns(true); _receiptStorage = new InMemoryReceiptStorage(allowReceiptIterator); _rawBlockTree = Build.A.BlockTree() .WithTransactions(_receiptStorage, LogsForBlockBuilder) - .OfChainLength(out _headTestBlock, 5) + .OfChainLength(out _headTestBlock, chainLength) .TestObject; _blockTree = _rawBlockTree; _bloomStorage = new BloomStorage(new BloomConfig(), new MemDb(), new InMemoryDictionaryFileStoreFactory()); _receiptsRecovery = Substitute.For(); - _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = CreateLogFinder(); } private void SetupHeadWithNoTransaction() @@ -129,15 +133,8 @@ public void filter_all_logs_iteratively([ValueSource(nameof(WithBloomValues))] b SetUp(allowReceiptIterator); LogFilter logFilter = AllBlockFilter().Build(); FilterLog[] logs = _logFinder.FindLogs(logFilter).ToArray(); - logs.Length.Should().Be(5); var indexes = logs.Select(static l => (int)l.LogIndex).ToArray(); - // indexes[0].Should().Be(0); - // indexes[1].Should().Be(1); - // indexes[2].Should().Be(0); - // indexes[3].Should().Be(1); - // indexes[4].Should().Be(2); - // BeEquivalentTo does not check the ordering!!! :O - indexes.Should().BeEquivalentTo(new[] { 0, 1, 0, 1, 2 }); + Assert.That(indexes, Is.EqualTo([0, 1, 0, 1, 2])); } [Test, MaxTime(Timeout.MaxTestTime)] @@ -145,7 +142,7 @@ public void throw_exception_when_receipts_are_missing([ValueSource(nameof(WithBl { StoreTreeBlooms(withBloomDb); _receiptStorage = NullReceiptStorage.Instance; - _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = CreateLogFinder(); var logFilter = AllBlockFilter().Build(); @@ -158,7 +155,7 @@ public void throw_exception_when_receipts_are_missing([ValueSource(nameof(WithBl public void when_receipts_are_missing_and_header_has_no_receipt_root_do_not_throw_exception_() { _receiptStorage = NullReceiptStorage.Instance; - _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = CreateLogFinder(); SetupHeadWithNoTransaction(); @@ -174,7 +171,7 @@ public void filter_all_logs_should_throw_when_to_block_is_not_found([ValueSource { StoreTreeBlooms(withBloomDb); var blockFinder = Substitute.For(); - _logFinder = new LogFinder(blockFinder, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = CreateLogFinder(blockFinder); var logFilter = AllBlockFilter().Build(); var action = new Func>(() => _logFinder.FindLogs(logFilter)); action.Should().Throw(); @@ -277,14 +274,13 @@ public void filter_by_blocks(LogFilter filter, int expectedCount, bool withBloom public void filter_by_blocks_with_limit([ValueSource(nameof(WithBloomValues))] bool withBloomDb) { StoreTreeBlooms(withBloomDb); - _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery, 2); + _logFinder = CreateLogFinder(); var filter = FilterBuilder.New().FromLatestBlock().ToLatestBlock().Build(); var logs = _logFinder.FindLogs(filter).ToArray(); logs.Length.Should().Be(3); } - public static IEnumerable ComplexFilterTestsData { get @@ -316,6 +312,7 @@ public void complex_filter(LogFilter filter, int expectedCount, bool withBloomDb } [Test, MaxTime(Timeout.MaxTestTime)] + [NonParallelizable] public async Task Throw_log_finder_operation_canceled_after_given_timeout([Values(2, 0.01)] double waitTime) { var timeout = TimeSpan.FromMilliseconds(Timeout.MaxWaitTime); @@ -323,24 +320,122 @@ public async Task Throw_log_finder_operation_canceled_after_given_timeout([Value CancellationToken cancellationToken = cancellationTokenSource.Token; StoreTreeBlooms(true); - _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = CreateLogFinder(); var logFilter = AllBlockFilter().Build(); var logs = _logFinder.FindLogs(logFilter, cancellationToken); await Task.Delay(timeout * waitTime); - Func action = () => logs.ToArray(); + TestDelegate action = () => _ = logs.ToArray(); if (waitTime > 1) { - action.Should().Throw().WithInnerException(); + Assert.That(action, Throws + .Exception.InstanceOf() + .Or.InnerException.InstanceOf() // PLINQ can wrap into AggregateException + ); } else { - action.Should().NotThrow(); + Assert.DoesNotThrow(action); } } + [TestCase("Empty index", + 1, 2, + null, null, + null, null + )] + [TestCase("No intersection, left", + 1, 2, + 4, 6, + null, null + )] + [TestCase("No intersection, adjacent left", + 1, 3, + 4, 6, + null, null + )] + [TestCase("1 block intersection, left", + 1, 4, + 4, 6, + 4, 4 + )] + [TestCase("Partial intersection, left", + 1, 5, + 4, 6, + 4, 5 + )] + [TestCase("Full containment, border right", + 1, 6, + 4, 6, + 4, 6 + )] + [TestCase("Full containment", + 1, 9, + 4, 6, + 4, 6 + )] + [TestCase("Full containment, border left", + 4, 9, + 4, 6, + 4, 6 + )] + [TestCase("Partial intersection, right", + 5, 9, + 4, 6, + 5, 6 + )] + [TestCase("1 block intersection, right", + 6, 9, + 4, 6, + 6, 6 + )] + [TestCase("No intersection, adjacent right", + 7, 9, + 4, 6, + null, null + )] + [TestCase("No intersection, right", + 8, 9, + 4, 6, + null, null + )] + public void query_intersected_range_from_log_index(string name, + int from, int to, + int? indexFrom, int? indexTo, + int? exFrom, int? exTo + ) + { + SetUp(true, chainLength: 10); + + var logIndexStorage = Substitute.For(); + logIndexStorage.Enabled.Returns(true); + logIndexStorage.MinBlockNumber.Returns(indexFrom); + logIndexStorage.MaxBlockNumber.Returns(indexTo); + logIndexStorage.GetEnumerator(Arg.Any
(), Arg.Any(), Arg.Any()) + .Returns(_ => Array.Empty().Cast().GetEnumerator()); + + Address address = TestItem.AddressA; + BlockHeader fromHeader = Build.A.BlockHeader.WithNumber(from).TestObject; + BlockHeader toHeader = Build.A.BlockHeader.WithNumber(to).TestObject; + LogFilter filter = FilterBuilder.New() + .FromBlock(from).ToBlock(to) + .WithAddress(address) + .Build(); + + var logFinder = new IndexedLogFinder( + _blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery, + logIndexStorage, minBlocksToUseIndex: 1 + ); + _ = logFinder.FindLogs(filter, fromHeader, toHeader).ToArray(); + + if (exTo is not null && exFrom is not null) + logIndexStorage.Received(1).GetEnumerator(address, exFrom.Value, exTo.Value); + else + logIndexStorage.DidNotReceiveWithAnyArgs().GetEnumerator(Arg.Any
(), Arg.Any(), Arg.Any()); + } + private static FilterBuilder AllBlockFilter() => FilterBuilder.New().FromEarliestBlock().ToPendingBlock(); private void StoreTreeBlooms(bool withBloomDb) @@ -354,4 +449,6 @@ private void StoreTreeBlooms(bool withBloomDb) } } + private LogFinder CreateLogFinder(IBlockFinder? blockFinder = null) => + new(blockFinder ?? _blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs index a20c74576774..406ae0c89e36 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs @@ -21,18 +21,11 @@ namespace Nethermind.Blockchain.Test.FullPruning; -[Parallelizable(ParallelScope.Self)] +[Parallelizable(ParallelScope.All)] [TestFixture(INodeStorage.KeyScheme.HalfPath)] [TestFixture(INodeStorage.KeyScheme.Hash)] -public class CopyTreeVisitorTests +public class CopyTreeVisitorTests(INodeStorage.KeyScheme scheme) { - private readonly INodeStorage.KeyScheme _keyScheme; - - public CopyTreeVisitorTests(INodeStorage.KeyScheme scheme) - { - _keyScheme = scheme; - } - [TestCase(0, 1)] [TestCase(0, 8)] [TestCase(1, 1)] @@ -83,7 +76,7 @@ public void cancel_coping_state_between_dbs() private IPruningContext CopyDb(IPruningContext pruningContext, CancellationToken cancellationToken, MemDb trieDb, VisitingOptions? visitingOptions = null, WriteFlags writeFlags = WriteFlags.None) { LimboLogs logManager = LimboLogs.Instance; - PatriciaTree trie = Build.A.Trie(new NodeStorage(trieDb, _keyScheme)).WithAccountsByIndex(0, 100).TestObject; + PatriciaTree trie = Build.A.Trie(new NodeStorage(trieDb, scheme)).WithAccountsByIndex(0, 100).TestObject; // Create a custom DbProvider that uses the trieDb from the test IDbProvider dbProvider = Substitute.For(); @@ -93,18 +86,19 @@ private IPruningContext CopyDb(IPruningContext pruningContext, CancellationToken // Use TestWorldStateFactory.CreateForTest() with the custom DbProvider (IWorldState worldState, IStateReader stateReader) = TestWorldStateFactory.CreateForTestWithStateReader(dbProvider, logManager); - if (_keyScheme == INodeStorage.KeyScheme.Hash) + BlockHeader? baseBlock = Build.A.BlockHeader.WithStateRoot(trie.RootHash).TestObject; + if (scheme == INodeStorage.KeyScheme.Hash) { - NodeStorage nodeStorage = new NodeStorage(pruningContext, _keyScheme); + NodeStorage nodeStorage = new NodeStorage(pruningContext, scheme); 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); + NodeStorage nodeStorage = new NodeStorage(pruningContext, scheme); 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/FullPrunerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPrunerTests.cs index c13e1e2482cc..d9b07e032cb3 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPrunerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPrunerTests.cs @@ -30,18 +30,10 @@ namespace Nethermind.Blockchain.Test.FullPruning; [TestFixture(0, 4)] [TestFixture(1, 1)] [TestFixture(1, 4)] -[Parallelizable(ParallelScope.Children)] -public class FullPrunerTests +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class FullPrunerTests(int fullPrunerMemoryBudgetMb, int degreeOfParallelism) { - private readonly int _fullPrunerMemoryBudgetMb; - private readonly int _degreeOfParallelism; - - public FullPrunerTests(int fullPrunerMemoryBudgetMb, int degreeOfParallelism) - { - _fullPrunerMemoryBudgetMb = fullPrunerMemoryBudgetMb; - _degreeOfParallelism = degreeOfParallelism; - } - [Test, MaxTime(Timeout.MaxTestTime)] public async Task can_prune() { @@ -61,8 +53,8 @@ public async Task can_prune_and_switch_key_scheme(INodeStorage.KeyScheme current true, false, FullPruningCompletionBehavior.None, - _fullPrunerMemoryBudgetMb, - _degreeOfParallelism, + fullPrunerMemoryBudgetMb, + degreeOfParallelism, currentKeyScheme: currentKeyScheme, preferredKeyScheme: newKeyScheme); @@ -192,8 +184,8 @@ private TestContext CreateTest( successfulPruning, clearPrunedDb, completionBehavior, - _fullPrunerMemoryBudgetMb, - _degreeOfParallelism); + fullPrunerMemoryBudgetMb, + degreeOfParallelism); private class TestContext { diff --git a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs index 5b1cec3e995f..98483a553cce 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs @@ -30,6 +30,7 @@ namespace Nethermind.Blockchain.Test.FullPruning; +[Parallelizable(ParallelScope.All)] public class FullPruningDiskTest { public class PruningTestBlockchain : TestBlockchain @@ -103,33 +104,30 @@ public static async Task Create(IPruningConfig? pruningCo PruningTestBlockchain chain = new() { PruningConfig = pruningConfig ?? new PruningConfig(), - TestTimout = testTimeoutMs, + TestTimeout = testTimeoutMs, }; await chain.Build(); return chain; } - public class FullTestPruner : FullPruner + public class FullTestPruner( + IFullPruningDb pruningDb, + INodeStorageFactory nodeStorageFactory, + INodeStorage mainNodeStorage, + IPruningTrigger pruningTrigger, + IPruningConfig pruningConfig, + IBlockTree blockTree, + IStateReader stateReader, + IProcessExitSource processExitSource, + IDriveInfo driveInfo, + IPruningTrieStore trieStore, + IChainEstimations chainEstimations, + ILogManager logManager) + : FullPruner(pruningDb, nodeStorageFactory, mainNodeStorage, pruningTrigger, pruningConfig, blockTree, + stateReader, processExitSource, chainEstimations, driveInfo, trieStore, logManager) { public EventWaitHandle WaitHandle { get; } = new ManualResetEvent(false); - public FullTestPruner( - IFullPruningDb pruningDb, - INodeStorageFactory nodeStorageFactory, - INodeStorage mainNodeStorage, - IPruningTrigger pruningTrigger, - IPruningConfig pruningConfig, - IBlockTree blockTree, - IStateReader stateReader, - IProcessExitSource processExitSource, - IDriveInfo driveInfo, - IPruningTrieStore trieStore, - IChainEstimations chainEstimations, - ILogManager logManager) - : base(pruningDb, nodeStorageFactory, mainNodeStorage, pruningTrigger, pruningConfig, blockTree, stateReader, processExitSource, chainEstimations, driveInfo, trieStore, logManager) - { - } - protected override async Task RunFullPruning(CancellationToken cancellationToken) { await base.RunFullPruning(cancellationToken); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/PruningTriggerPruningStrategyTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/PruningTriggerPruningStrategyTests.cs index 505f6227fb29..fc43e2dce333 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/PruningTriggerPruningStrategyTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/PruningTriggerPruningStrategyTests.cs @@ -12,7 +12,8 @@ namespace Nethermind.Blockchain.Test.FullPruning { [TestFixture] - [Parallelizable(ParallelScope.Self)] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class PruningTriggerPruningStrategyTests { private IFullPruningDb _fullPruningDb; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/GenesisBuilderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/GenesisBuilderTests.cs index 256318da9a57..f5f9e8c7c382 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/GenesisBuilderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/GenesisBuilderTests.cs @@ -2,16 +2,17 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.IO; +using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Core.Test; +using Nethermind.Core.Test.Container; using Nethermind.Evm.TransactionProcessing; using Nethermind.Logging; using Nethermind.Serialization.Json; using Nethermind.Specs.ChainSpecStyle; using Nethermind.Specs.Forks; using Nethermind.Evm.State; -using Nethermind.State; using NSubstitute; using NUnit.Framework; @@ -21,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"); } @@ -45,21 +46,36 @@ public void Can_load_withdrawals_with_empty_root() Assert.That(block.Hash!.ToString(), Is.EqualTo("0x1326aad1114b1f1c6a345b69ba4ba6f8ab6ce027d988aacd275ab596a047a547")); } + [Test, MaxTime(Timeout.MaxTestTime)] + public void Remove_ChainSpecAllocation_AfterPostProcessor() + { + string path = Path.Combine(TestContext.CurrentContext.WorkDirectory, "Specs/shanghai_from_genesis.json"); + ChainSpec chainSpec = LoadChainSpec(path); + + FunctionalGenesisPostProcessor genesisPostProcessor = new FunctionalGenesisPostProcessor((block) => + { + chainSpec.Allocations.Should().NotBeNull(); + }); + (GenesisBuilder genesisLoader, IWorldState stateProvider) = BuildGenesisBuilder(chainSpec, genesisPostProcessor); + + using var _ = stateProvider.BeginScope(IWorldState.PreGenesis); + genesisLoader.Build(); + chainSpec.Allocations.Should().BeNull(); + } + private void AssertBlockHash(string expectedHash, string chainspecFilePath) { Block block = GetGenesisBlock(chainspecFilePath); Assert.That(block.Hash!.ToString(), Is.EqualTo(expectedHash)); } - private Block GetGenesisBlock(string chainspecPath) + private (GenesisBuilder, IWorldState) BuildGenesisBuilder(ChainSpec chainSpec, IGenesisPostProcessor? genesisPostProcessor = null) { - string path = Path.Combine(TestContext.CurrentContext.WorkDirectory, chainspecPath); - ChainSpec chainSpec = LoadChainSpec(path); IWorldState stateProvider = TestWorldStateFactory.CreateForTest(); ISpecProvider specProvider = Substitute.For(); specProvider.GetSpec(Arg.Any()).Returns(Berlin.Instance); ITransactionProcessor transactionProcessor = Substitute.For(); - IGenesisPostProcessor genesisPostProcessor = Substitute.For(); + genesisPostProcessor ??= Substitute.For(); GenesisBuilder genesisLoader = new( chainSpec, specProvider, @@ -67,6 +83,15 @@ private Block GetGenesisBlock(string chainspecPath) transactionProcessor, genesisPostProcessor); + return (genesisLoader, stateProvider); + } + + private Block GetGenesisBlock(string chainspecPath) + { + string path = Path.Combine(TestContext.CurrentContext.WorkDirectory, chainspecPath); + ChainSpec chainSpec = LoadChainSpec(path); + (GenesisBuilder genesisLoader, IWorldState stateProvider) = BuildGenesisBuilder(chainSpec); + using var _ = stateProvider.BeginScope(IWorldState.PreGenesis); return genesisLoader.Build(); } @@ -74,7 +99,7 @@ private Block GetGenesisBlock(string chainspecPath) private static ChainSpec LoadChainSpec(string path) { - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance); var chainSpec = loader.LoadEmbeddedOrFromFile(path); return chainSpec; } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Nethermind.Blockchain.Test.csproj b/src/Nethermind/Nethermind.Blockchain.Test/Nethermind.Blockchain.Test.csproj index 3547c64beb22..3b852e313be4 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Nethermind.Blockchain.Test.csproj +++ b/src/Nethermind/Nethermind.Blockchain.Test/Nethermind.Blockchain.Test.csproj @@ -6,6 +6,7 @@ enable + diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.BaseFee.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.BaseFee.cs index b8449ad2baf1..376ae298fd12 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.BaseFee.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.BaseFee.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.IO; using System.Security; using System.Threading.Tasks; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.IsProducingBlocks.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.IsProducingBlocks.cs index 3b9582889cbb..4a5bce878374 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(), @@ -145,6 +146,33 @@ public async Task CliqueBlockProducer_IsProducingBlocks_returns_expected_results await AssertIsProducingBlocks(runner); } + [Test, MaxTime(Timeout.MaxTestTime)] + public async Task DevBlockProducer_OnGlobalWorldState_IsProducingAndSuggestingBlocks() + { + TestRpcBlockchain testRpc = await CreateTestRpc(); + DevBlockProducer blockProducer = new( + Substitute.For(), + testRpc.BlockchainProcessor, + new WorldState(testRpc.WorldStateManager.GlobalWorldState, testRpc.LogManager), + testRpc.BlockTree, + testRpc.Timestamper, + testRpc.SpecProvider, + new BlocksConfig(), + LimboLogs.Instance); + + BuildBlocksWhenRequested trigger = new(); + StandardBlockProducerRunner runner = new(trigger, testRpc.BlockTree, blockProducer); + long currentHead = testRpc.BlockTree.Head?.Number ?? 0; + + _ = new NonProcessingProducedBlockSuggester(testRpc.BlockTree, runner); + + runner.Start(); + + await trigger.BuildBlock(testRpc.BlockTree.Head?.Header); + + Assert.That(testRpc.BlockTree.BestSuggestedHeader?.Number, Is.EqualTo(currentHead + 1)); + } + private async Task CreateTestRpc() { Address address = TestItem.Addresses[0]; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.cs index 35538c1e1e8f..417cb72e8e4a 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.cs @@ -21,33 +21,28 @@ namespace Nethermind.Blockchain.Test.Producers; public partial class BlockProducerBaseTests { - private class ProducerUnderTest : BlockProducerBase + private class ProducerUnderTest( + ITxSource txSource, + IBlockchainProcessor processor, + ISealer sealer, + IBlockTree blockTree, + IWorldState stateProvider, + IGasLimitCalculator gasLimitCalculator, + ITimestamper timestamper, + ILogManager logManager, + IBlocksConfig blocksConfig) + : BlockProducerBase(txSource, + processor, + sealer, + blockTree, + stateProvider, + gasLimitCalculator, + timestamper, + MainnetSpecProvider.Instance, + logManager, + new TimestampDifficultyCalculator(), + blocksConfig) { - public ProducerUnderTest( - ITxSource txSource, - IBlockchainProcessor processor, - ISealer sealer, - IBlockTree blockTree, - IWorldState stateProvider, - IGasLimitCalculator gasLimitCalculator, - ITimestamper timestamper, - ILogManager logManager, - IBlocksConfig blocksConfig) - : base( - txSource, - processor, - sealer, - blockTree, - stateProvider, - gasLimitCalculator, - timestamper, - MainnetSpecProvider.Instance, - logManager, - new TimestampDifficultyCalculator(), - blocksConfig) - { - } - public Block Prepare() => PrepareBlock(Build.A.BlockHeader.TestObject); public Block Prepare(BlockHeader header) => PrepareBlock(header); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockOnEachPendingTxTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockOnEachPendingTxTests.cs index fb7dcde6abf1..f1d61f1a256e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockOnEachPendingTxTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockOnEachPendingTxTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.Blockchain.Test.Producers; +[Parallelizable(ParallelScope.All)] public class BuildBlockOnEachPendingTxTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockRegularlyTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockRegularlyTests.cs index 4aa9dac4cbba..c1279a679d17 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockRegularlyTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockRegularlyTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.Blockchain.Test.Producers; +[Parallelizable(ParallelScope.All)] public class BuildBlockRegularlyTests { [Test, MaxTime(Timeout.MaxTestTime), Retry(3)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksOnlyWhenNotProcessingTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksOnlyWhenNotProcessingTests.cs index fda6ddbe27a0..0199896bcebb 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksOnlyWhenNotProcessingTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksOnlyWhenNotProcessingTests.cs @@ -35,8 +35,7 @@ public async Task should_trigger_block_production_when_queue_empties() context.BlockProcessingQueue.IsEmpty.Returns(false); Task buildTask = context.MainBlockProductionTrigger.BuildBlock(); - await Task.Delay(BuildBlocksOnlyWhenNotProcessing.ChainNotYetProcessedMillisecondsDelay * 2); - buildTask.IsCanceled.Should().BeFalse(); + Assert.That(() => buildTask.IsCanceled, Is.False.After(BuildBlocksOnlyWhenNotProcessing.ChainNotYetProcessedMillisecondsDelay * 2, 10)); context.BlockProcessingQueue.IsEmpty.Returns(true); Block? block = await buildTask; @@ -52,8 +51,7 @@ public async Task should_cancel_triggering_block_production() using CancellationTokenSource cancellationTokenSource = new(); Task buildTask = context.MainBlockProductionTrigger.BuildBlock(cancellationToken: cancellationTokenSource.Token); - await Task.Delay(BuildBlocksOnlyWhenNotProcessing.ChainNotYetProcessedMillisecondsDelay * 2); - buildTask.IsCanceled.Should().BeFalse(); + Assert.That(() => buildTask.IsCanceled, Is.False.After(BuildBlocksOnlyWhenNotProcessing.ChainNotYetProcessedMillisecondsDelay * 2, 10)); cancellationTokenSource.Cancel(); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksWhenRequestedTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksWhenRequestedTests.cs index a244c84aacb9..d3c709b6ee11 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksWhenRequestedTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksWhenRequestedTests.cs @@ -7,6 +7,7 @@ namespace Nethermind.Blockchain.Test.Producers; +[Parallelizable(ParallelScope.All)] public class BuildBlocksWhenRequestedTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/CompositeBlockProductionTriggerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/CompositeBlockProductionTriggerTests.cs index 478c37bbebc4..d493795f1b3e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/CompositeBlockProductionTriggerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/CompositeBlockProductionTriggerTests.cs @@ -7,6 +7,7 @@ namespace Nethermind.Blockchain.Test.Producers; +[Parallelizable(ParallelScope.All)] public class CompositeBlockProductionTriggerTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs index 733b73a3c804..b8bc669d2a8c 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs @@ -17,6 +17,7 @@ namespace Nethermind.Blockchain.Test.Producers; +[Parallelizable(ParallelScope.All)] public class DevBlockProducerTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/IfPoolIsNotEmptyTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/IfPoolIsNotEmptyTests.cs index 3f2d97ac18be..14549530b899 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/IfPoolIsNotEmptyTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/IfPoolIsNotEmptyTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.Blockchain.Test.Producers; +[Parallelizable(ParallelScope.All)] public class IfPoolIsNotEmptyTests { [MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/ReceiptTrieTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/ReceiptTrieTests.cs index 0533fde1a350..82c883a83576 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/ReceiptTrieTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/ReceiptTrieTests.cs @@ -17,6 +17,7 @@ namespace Nethermind.Blockchain.Test.Proofs; +[Parallelizable(ParallelScope.All)] public class ReceiptTrieTests { private static readonly IRlpStreamDecoder _decoder = Rlp.GetStreamDecoder()!; @@ -57,6 +58,28 @@ public void Can_collect_proof_with_branch() VerifyProof(proof, trie.RootHash); } + [Test, MaxTime(Timeout.MaxTestTime)] + public void Parallel_and_non_parallel_root_hashing_produce_same_root() + { + const int receiptCount = 100; + IReleaseSpec spec = MainnetSpecProvider.Instance.GetSpec((MainnetSpecProvider.MuirGlacierBlockNumber, null)); + TxReceipt[] receipts = new TxReceipt[receiptCount]; + for (int i = 0; i < receiptCount; i++) + { + receipts[i] = Build.A.Receipt.WithAllFieldsFilled.WithGasUsedTotal(1000 + i).TestObject; + } + + using TrackingCappedArrayPool parallelPool = new(receiptCount * 4, canBeParallel: true); + ReceiptTrie parallelTrie = new(spec, receipts, _decoder, parallelPool, canBeParallel: true); + Hash256 parallelRoot = parallelTrie.RootHash; + + using TrackingCappedArrayPool sequentialPool = new(receiptCount * 4, canBeParallel: false); + ReceiptTrie sequentialTrie = new(spec, receipts, _decoder, sequentialPool, canBeParallel: false); + Hash256 sequentialRoot = sequentialTrie.RootHash; + + Assert.That(sequentialRoot, Is.EqualTo(parallelRoot)); + } + private void VerifyProof(byte[][] proof, Hash256 receiptRoot) { TrieNode node = new(NodeType.Unknown, proof.Last()); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/TxTrieTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/TxTrieTests.cs index 4f8e912e1887..02b86b29a6a3 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/TxTrieTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/TxTrieTests.cs @@ -7,6 +7,7 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; +using Nethermind.Int256; using Nethermind.Serialization.Rlp; using Nethermind.Specs.Forks; using Nethermind.State.Proofs; @@ -17,14 +18,11 @@ namespace Nethermind.Blockchain.Test.Proofs; [TestFixture(true)] [TestFixture(false)] -public class TxTrieTests +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class TxTrieTests(bool useEip2718) { - private readonly IReleaseSpec _releaseSpec; - - public TxTrieTests(bool useEip2718) - { - _releaseSpec = useEip2718 ? Berlin.Instance : MuirGlacier.Instance; - } + private readonly IReleaseSpec _releaseSpec = useEip2718 ? Berlin.Instance : MuirGlacier.Instance; [Test, MaxTime(Timeout.MaxTestTime)] public void Can_calculate_root() @@ -82,6 +80,46 @@ public void Can_collect_proof_with_trie_case_3_modified() } } + [Test, MaxTime(Timeout.MaxTestTime)] + public void Encoded_and_decoded_transaction_paths_have_same_root() + { + Transaction[] transactions = + [ + Build.A.Transaction.WithNonce(1).WithType(TxType.Legacy).Signed().TestObject, + Build.A.Transaction.WithNonce(2).WithType(useEip2718 ? TxType.EIP1559 : TxType.Legacy).Signed().TestObject, + Build.A.Transaction.WithNonce(3).WithType(useEip2718 ? TxType.AccessList : TxType.Legacy).Signed().TestObject, + ]; + + byte[][] encodedTransactions = transactions + .Select(static tx => Rlp.Encode(tx, RlpBehaviors.SkipTypedWrapping).Bytes) + .ToArray(); + + Hash256 decodedRoot = TxTrie.CalculateRoot(transactions); + Hash256 encodedRoot = TxTrie.CalculateRoot(encodedTransactions); + + Assert.That(encodedRoot, Is.EqualTo(decodedRoot)); + } + + [Test, MaxTime(Timeout.MaxTestTime)] + public void Parallel_and_non_parallel_root_hashing_produce_same_root() + { + const int txCount = 100; + Transaction[] transactions = new Transaction[txCount]; + for (int i = 0; i < txCount; i++) + { + transactions[i] = Build.A.Transaction.WithNonce((UInt256)(i + 1)).Signed().TestObject; + } + + using TrackingCappedArrayPool pool = new(); + TxTrie txTrie = new(transactions, canBuildProof: false, pool); + Hash256 parallelRoot = txTrie.RootHash; + + txTrie.UpdateRootHash(canBeParallel: false); + Hash256 nonParallelRoot = txTrie.RootHash; + + Assert.That(nonParallelRoot, Is.EqualTo(parallelRoot)); + } + private static void VerifyProof(byte[][] proof, Hash256 txRoot) { for (int i = proof.Length; i > 0; i--) diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs index 4dada95cceb2..02c002758efd 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Proofs; +[Parallelizable(ParallelScope.All)] public class WithdrawalTrieTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ReadOnlyBlockTreeTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ReadOnlyBlockTreeTests.cs index 2a85fb94a0e2..baa7e9d4af2e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ReadOnlyBlockTreeTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ReadOnlyBlockTreeTests.cs @@ -9,6 +9,8 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class ReadOnlyBlockTreeTests { private IBlockTree _innerBlockTree = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/KeccaksIteratorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/KeccaksIteratorTests.cs index 96be4973fcae..2c66c04dfaa1 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/KeccaksIteratorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/KeccaksIteratorTests.cs @@ -13,6 +13,7 @@ namespace Nethermind.Blockchain.Test.Receipts; +[Parallelizable(ParallelScope.All)] public class KeccaksIteratorTests { [TestCaseSource(nameof(TestKeccaks))] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs index 6f8d35d04b17..caa0794e7ac3 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs @@ -2,9 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using FluentAssertions; using FluentAssertions.Equivalency; @@ -21,7 +19,6 @@ using Nethermind.Specs; using Nethermind.Specs.Forks; using NSubstitute; -using NSubstitute.Core; using NUnit.Framework; namespace Nethermind.Blockchain.Test.Receipts; @@ -29,23 +26,19 @@ namespace Nethermind.Blockchain.Test.Receipts; [TestFixture(true)] [TestFixture(false)] -public class PersistentReceiptStorageTests +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class PersistentReceiptStorageTests(bool useCompactReceipts) { - private readonly TestSpecProvider _specProvider = new TestSpecProvider(Byzantium.Instance); + private readonly TestSpecProvider _specProvider = new(Byzantium.Instance); private TestMemColumnsDb _receiptsDb = null!; private ReceiptsRecovery _receiptsRecovery = null!; private IBlockTree _blockTree = null!; private IBlockStore _blockStore = null!; - private readonly bool _useCompactReceipts; private ReceiptConfig _receiptConfig = null!; private PersistentReceiptStorage _storage = null!; private ReceiptArrayStorageDecoder _decoder = null!; - public PersistentReceiptStorageTests(bool useCompactReceipts) - { - _useCompactReceipts = useCompactReceipts; - } - [SetUp] public void SetUp() { @@ -67,7 +60,7 @@ public void TearDown() private void CreateStorage() { - _decoder = new ReceiptArrayStorageDecoder(_useCompactReceipts); + _decoder = new ReceiptArrayStorageDecoder(useCompactReceipts); _storage = new PersistentReceiptStorage( _receiptsDb, _specProvider, @@ -88,14 +81,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); @@ -326,26 +319,21 @@ public void When_TxLookupLimitIs_NegativeOne_DoNotIndexTxHash() CreateStorage(); (Block block, TxReceipt[] receipts) = InsertBlock(isFinalized: true); _blockTree.BlockAddedToMain += Raise.EventWith(new BlockReplacementEventArgs(block)); - Thread.Sleep(100); - _receiptsDb.GetColumnDb(ReceiptsColumns.Transactions)[receipts[0].TxHash!.Bytes].Should().BeNull(); + Assert.That(() => _receiptsDb.GetColumnDb(ReceiptsColumns.Transactions)[receipts[0].TxHash!.Bytes], Is.Null.After(100, 10)); } [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(); _blockTree.BlockAddedToMain += Raise.EventWith(new BlockReplacementEventArgs(Build.A.Block.WithNumber(blockNumber).TestObject)); - Thread.Sleep(100); - IEnumerable calls = _blockTree.ReceivedCalls() - .Where(static call => call.GetMethodInfo().Name.EndsWith(nameof(_blockTree.FindBlock))); - if (WillPruneOldIndicies) - calls.Should().NotBeEmpty(); - else - calls.Should().BeEmpty(); + Assert.That(() => _blockTree.ReceivedCalls() + .Where(static call => call.GetMethodInfo().Name.EndsWith(nameof(_blockTree.FindBlock))), + willPruneOldIndices ? Is.Not.Empty.After(100, 10) : Is.Empty.After(100, 10)); } [Test] @@ -355,8 +343,7 @@ public void When_HeadBlockIsFarAhead_DoNotIndexTxHash() CreateStorage(); (Block block, TxReceipt[] receipts) = InsertBlock(isFinalized: true, headNumber: 1001); _blockTree.BlockAddedToMain += Raise.EventWith(new BlockReplacementEventArgs(block)); - Thread.Sleep(100); - _receiptsDb.GetColumnDb(ReceiptsColumns.Transactions)[receipts[0].TxHash!.Bytes].Should().BeNull(); + Assert.That(() => _receiptsDb.GetColumnDb(ReceiptsColumns.Transactions)[receipts[0].TxHash!.Bytes], Is.Null.After(100, 10)); } [Test] @@ -396,7 +383,7 @@ public void When_NewHeadBlock_DoNotRemove_TxIndex_WhenTxIsInOtherBlockNumber() [Test] public async Task When_NewHeadBlock_Remove_TxIndex_OfRemovedBlock_Unless_ItsAlsoInNewBlock() { - _receiptConfig.CompactTxIndex = _useCompactReceipts; + _receiptConfig.CompactTxIndex = useCompactReceipts; CreateStorage(); (Block block, _) = InsertBlock(); Block block2 = Build.A.Block diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs index 0e939e909430..14f6d357d52a 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs @@ -14,9 +14,10 @@ namespace Nethermind.Blockchain.Test.Receipts; +[Parallelizable(ParallelScope.All)] public class ReceiptsIteratorTests { - readonly ReceiptArrayStorageDecoder _decoder = ReceiptArrayStorageDecoder.Instance; + private readonly ReceiptArrayStorageDecoder _decoder = ReceiptArrayStorageDecoder.Instance; [Test] public void SmokeTestWithRecovery() @@ -25,12 +26,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 +85,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/Receipts/ReceiptsRecoveryTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRecoveryTests.cs index 7cd1b0ec1292..fd10cec441d6 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRecoveryTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRecoveryTests.cs @@ -11,6 +11,8 @@ namespace Nethermind.Blockchain.Test.Receipts; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class ReceiptsRecoveryTests { private IReceiptsRecovery _receiptsRecovery = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRootTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRootTests.cs index 1c3db195441b..e7b8009e2e92 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRootTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRootTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Receipts { + [Parallelizable(ParallelScope.All)] public class ReceiptsRootTests { public static IEnumerable ReceiptsRootTestCases diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ReorgDepthFinalizedStateProviderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ReorgDepthFinalizedStateProviderTests.cs new file mode 100644 index 000000000000..b8d4fd4f6c0b --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain.Test/ReorgDepthFinalizedStateProviderTests.cs @@ -0,0 +1,97 @@ +// 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.Builders; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Blockchain.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class ReorgDepthFinalizedStateProviderTests +{ + private IBlockTree _blockTree = null!; + private ReorgDepthFinalizedStateProvider _provider = null!; + + [SetUp] + public void Setup() + { + _blockTree = Substitute.For(); + _provider = new ReorgDepthFinalizedStateProvider(_blockTree); + } + + [Test] + public void FinalizedBlockNumber_ReturnsCorrectValue() + { + // Arrange + long bestKnownNumber = 1000; + _blockTree.BestKnownNumber.Returns(bestKnownNumber); + + // Act + long result = _provider.FinalizedBlockNumber; + + // Assert + result.Should().Be(bestKnownNumber - Reorganization.MaxDepth); + } + + [Test] + public void GetFinalizedStateRootAt_ReturnsNull_WhenBlockNumberExceedsFinalizedBlock() + { + // Arrange + long bestKnownNumber = 100; + long blockNumber = 100; + _blockTree.BestKnownNumber.Returns(bestKnownNumber); + + // Act + Hash256? result = _provider.GetFinalizedStateRootAt(blockNumber); + + // Assert + result.Should().BeNull(); + _blockTree.DidNotReceive().FindHeader(Arg.Any(), Arg.Any()); + } + + [Test] + public void GetFinalizedStateRootAt_ReturnsStateRoot_WhenBlockNumberIsFinalized() + { + // Arrange + long bestKnownNumber = 1000; + long blockNumber = 900; + Hash256 expectedStateRoot = TestItem.KeccakA; + BlockHeader header = Build.A.BlockHeader.WithStateRoot(expectedStateRoot).TestObject; + + _blockTree.BestKnownNumber.Returns(bestKnownNumber); + _blockTree.FindHeader(blockNumber, BlockTreeLookupOptions.RequireCanonical).Returns(header); + + // Act + Hash256? result = _provider.GetFinalizedStateRootAt(blockNumber); + + // Assert + result.Should().Be(expectedStateRoot); + _blockTree.Received(1).FindHeader(blockNumber, BlockTreeLookupOptions.RequireCanonical); + } + + [Test] + public void GetFinalizedStateRootAt_AtBoundary_ReturnsStateRoot() + { + // Arrange + long bestKnownNumber = 1000; + long blockNumber = bestKnownNumber - Reorganization.MaxDepth; // Exactly at the boundary + Hash256 expectedStateRoot = TestItem.KeccakD; + BlockHeader header = Build.A.BlockHeader.WithStateRoot(expectedStateRoot).TestObject; + + _blockTree.BestKnownNumber.Returns(bestKnownNumber); + _blockTree.FindHeader(blockNumber, BlockTreeLookupOptions.RequireCanonical).Returns(header); + + // Act + Hash256? result = _provider.GetFinalizedStateRootAt(blockNumber); + + // Assert + result.Should().Be(expectedStateRoot); + _blockTree.Received(1).FindHeader(blockNumber, BlockTreeLookupOptions.RequireCanonical); + } +} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs index 737093a449c6..8adf5fad4594 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; @@ -28,6 +29,7 @@ using Nethermind.Evm.State; using Nethermind.State; using Nethermind.TxPool; +using NSubstitute; using NUnit.Framework; namespace Nethermind.Blockchain.Test; @@ -49,7 +51,7 @@ public void Setup() IReleaseSpec finalSpec = specProvider.GetFinalSpec(); - using (var _ = stateProvider.BeginScope(IWorldState.PreGenesis)) + using (IDisposable _ = stateProvider.BeginScope(IWorldState.PreGenesis)) { if (finalSpec.WithdrawalsEnabled) { @@ -73,10 +75,11 @@ public void Setup() ITransactionComparerProvider transactionComparerProvider = new TransactionComparerProvider(specProvider, _blockTree); - _blockTree = Build.A.BlockTree() + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree() .WithoutSettingHead - .WithSpecProvider(specProvider) - .TestObject; + .WithSpecProvider(specProvider); + + _blockTree = blockTreeBuilder.TestObject; TxPool.TxPool txPool = new( ecdsa, @@ -87,12 +90,13 @@ public void Setup() new TxValidator(specProvider.ChainId), LimboLogs.Instance, transactionComparerProvider.GetDefaultComparer()); - BlockhashProvider blockhashProvider = new(_blockTree, specProvider, stateProvider, LimboLogs.Instance); - VirtualMachine virtualMachine = new( + BlockhashCache blockhashCache = new(blockTreeBuilder.HeaderStore, LimboLogs.Instance); + BlockhashProvider blockhashProvider = new(blockhashCache, stateProvider, LimboLogs.Instance); + EthereumVirtualMachine virtualMachine = new( blockhashProvider, specProvider, LimboLogs.Instance); - TransactionProcessor transactionProcessor = new( + EthereumTransactionProcessor transactionProcessor = new( BlobBaseFeeCalculator.Instance, specProvider, stateProvider, @@ -100,7 +104,7 @@ public void Setup() new EthereumCodeInfoRepository(stateProvider), LimboLogs.Instance); - BlockProcessor blockProcessor = new BlockProcessor( + BlockProcessor blockProcessor = new( MainnetSpecProvider.Instance, Always.Valid, new RewardCalculator(specProvider), @@ -108,15 +112,16 @@ public void Setup() stateProvider, NullReceiptStorage.Instance, new BeaconBlockRootHandler(transactionProcessor, stateProvider), - new BlockhashStore(MainnetSpecProvider.Instance, stateProvider), + new BlockhashStore(stateProvider), LimboLogs.Instance, new WithdrawalProcessor(stateProvider, LimboLogs.Instance), new ExecutionRequestsProcessor(transactionProcessor)); - BranchProcessor branchProcessor = new BranchProcessor( + BranchProcessor branchProcessor = new( blockProcessor, MainnetSpecProvider.Instance, stateProvider, new BeaconBlockRootHandler(transactionProcessor, stateProvider), + blockhashProvider, LimboLogs.Instance); _blockchainProcessor = new BlockchainProcessor( @@ -127,7 +132,9 @@ public void Setup() specProvider, LimboLogs.Instance), stateReader, - LimboLogs.Instance, BlockchainProcessor.Options.Default); + LimboLogs.Instance, + BlockchainProcessor.Options.Default, + Substitute.For()); } [OneTimeTearDown] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Rewards/NoBlockRewardsTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Rewards/NoBlockRewardsTests.cs index 1dc264011284..212a6448829f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Rewards/NoBlockRewardsTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Rewards/NoBlockRewardsTests.cs @@ -8,6 +8,7 @@ namespace Nethermind.Blockchain.Test.Rewards; +[Parallelizable(ParallelScope.All)] public class NoBlockRewardsTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Rewards/RewardCalculatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Rewards/RewardCalculatorTests.cs index d4225963be51..3553483a1f69 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Rewards/RewardCalculatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Rewards/RewardCalculatorTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.Blockchain.Test.Rewards; +[Parallelizable(ParallelScope.All)] public class RewardCalculatorTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Services/HealthHintServiceTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Services/HealthHintServiceTests.cs index aee9e3a4df56..fc89b9798e8f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Services/HealthHintServiceTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Services/HealthHintServiceTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.Blockchain.Test.Services; +[Parallelizable(ParallelScope.All)] public class HealthHintServiceTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/SlowHeaderStore.cs b/src/Nethermind/Nethermind.Blockchain.Test/SlowHeaderStore.cs new file mode 100644 index 000000000000..8e950a55212f --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain.Test/SlowHeaderStore.cs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Threading; +using Nethermind.Blockchain.Headers; +using Nethermind.Core; +using Nethermind.Core.Crypto; + +namespace Nethermind.Blockchain.Test; + +public class SlowHeaderStore(IHeaderStore headerStore) : IHeaderStore +{ + public long SlowBlockNumber { get; set; } = 100; + + public BlockHeader? Get(Hash256 blockHash, long? blockNumber = null) + { + if (blockNumber < SlowBlockNumber) Thread.Sleep(10); + return headerStore.Get(blockHash, blockNumber); + } + + public void Insert(BlockHeader header) => headerStore.Insert(header); + public void BulkInsert(IReadOnlyList headers) => headerStore.BulkInsert(headers); + public BlockHeader? Get(Hash256 blockHash, bool shouldCache, long? blockNumber = null) => headerStore.Get(blockHash, shouldCache, blockNumber); + public void Cache(BlockHeader header) => headerStore.Cache(header); + public void Delete(Hash256 blockHash) => headerStore.Delete(blockHash); + public void InsertBlockNumber(Hash256 blockHash, long blockNumber) => headerStore.InsertBlockNumber(blockHash, blockNumber); + public long? GetBlockNumber(Hash256 blockHash) => headerStore.GetBlockNumber(blockHash); +} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Timeout.cs b/src/Nethermind/Nethermind.Blockchain.Test/Timeout.cs index 394b42f25b34..240bb2dd0b01 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Timeout.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Timeout.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only namespace Nethermind.Blockchain.Test; + internal class Timeout { public const int LongTestTime = 60_000; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionGasPriceComparisonTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionGasPriceComparisonTests.cs index f247ffb1e153..089c1010237f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionGasPriceComparisonTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionGasPriceComparisonTests.cs @@ -14,6 +14,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class TransactionComparisonTests { [MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs index 5d782d07ce8a..73267a3f94a2 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs @@ -7,13 +7,11 @@ using Nethermind.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; -using Nethermind.Db; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; using Nethermind.Logging; using Nethermind.Specs.Forks; using Nethermind.Evm.State; -using Nethermind.Trie.Pruning; using NUnit.Framework; using System.Collections.Generic; using Nethermind.Core.Crypto; @@ -23,11 +21,12 @@ using Nethermind.Blockchain.Tracing; using Nethermind.Core.Test; using Nethermind.Int256; -using Nethermind.State; namespace Nethermind.Evm.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] internal class TransactionProcessorEip7702Tests { private ISpecProvider _specProvider; @@ -43,8 +42,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 +456,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 +577,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 +782,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 5584fc5bc0d7..b2b89a875891 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs @@ -11,7 +11,6 @@ using Nethermind.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; -using Nethermind.Db; using Nethermind.Int256; using Nethermind.Evm.Tracing; using Nethermind.Blockchain.Tracing.GethStyle; @@ -21,21 +20,20 @@ using Nethermind.Serialization.Json; using Nethermind.Specs.Forks; using Nethermind.Evm.State; -using Nethermind.Trie.Pruning; using NUnit.Framework; using Nethermind.Config; using System.Collections.Generic; using Nethermind.Blockchain; using Nethermind.Blockchain.Tracing; using Nethermind.Core.Test; -using Nethermind.State; namespace Nethermind.Evm.Test; [TestFixture(true)] [TestFixture(false)] [Todo(Improve.Refactor, "Check why fixture test cases did not work")] -[Parallelizable(ParallelScope.Self)] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class TransactionProcessorTests { private readonly bool _isEip155Enabled; @@ -65,8 +63,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); } @@ -235,18 +233,6 @@ public void Can_handle_quick_fail_on_above_block_gas_limit(bool withStateDiff, b Assert.That(result.TransactionExecuted, Is.False); } - [TestCase(true, true)] - [TestCase(true, false)] - [TestCase(false, true)] - [TestCase(false, false)] - public void Will_not_cause_quick_fail_above_block_gas_limit_during_calls(bool withStateDiff, bool withTrace) - { - Transaction tx = Build.A.Transaction.SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA, _isEip155Enabled).WithGasLimit(100000).TestObject; - Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).WithGasLimit(20000).TestObject; - TransactionResult result = CallAndRestore(tx, block); - Assert.That(result.TransactionExecuted, Is.True); - } - [Test] public void Balance_is_not_changed_on_call_and_restore() { @@ -301,14 +287,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); } } @@ -331,7 +317,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")); @@ -348,20 +334,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 }; } } @@ -418,7 +405,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)); @@ -461,7 +448,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); @@ -479,7 +466,7 @@ public void Can_estimate_with_destroy_refund_and_below_intrinsic_pre_berlin() tracer.CalculateAdditionalGasRequired(tx, releaseSpec).Should().Be(24080); tracer.GasSpent.Should().Be(35228L); long estimate = estimator.Estimate(tx, block.Header, tracer, out string? err, 0); - estimate.Should().Be(59307); + estimate.Should().Be(54225); Assert.That(err, Is.Null); ConfirmEnoughEstimate(tx, block, estimate); @@ -487,32 +474,19 @@ public void Can_estimate_with_destroy_refund_and_below_intrinsic_pre_berlin() private void ConfirmEnoughEstimate(Transaction tx, Block block, long estimate) { - CallOutputTracer outputTracer = new(); - tx.GasLimit = estimate; - TestContext.Out.WriteLine(tx.GasLimit); - - GethLikeTxMemoryTracer gethTracer = new(tx, GethTraceOptions.Default); var blkCtx = new BlockExecutionContext(block.Header, _specProvider.GetSpec(block.Header)); - _transactionProcessor.CallAndRestore(tx, blkCtx, gethTracer); - string traceEnoughGas = new EthereumJsonSerializer().Serialize(gethTracer.BuildResult(), true); + CallOutputTracer outputTracer = new(); + tx.GasLimit = estimate; _transactionProcessor.CallAndRestore(tx, blkCtx, outputTracer); - traceEnoughGas.Should().NotContain("OutOfGas"); + outputTracer.StatusCode.Should().Be(StatusCode.Success, + $"transaction should succeed at the estimate ({estimate})"); outputTracer = new CallOutputTracer(); tx.GasLimit = Math.Min(estimate - 1, estimate * 63 / 64); - TestContext.Out.WriteLine(tx.GasLimit); - - gethTracer = new GethLikeTxMemoryTracer(tx, GethTraceOptions.Default); - _transactionProcessor.CallAndRestore(tx, blkCtx, gethTracer); - - string traceOutOfGas = new EthereumJsonSerializer().Serialize(gethTracer.BuildResult(), true); - TestContext.Out.WriteLine(traceOutOfGas); - _transactionProcessor.CallAndRestore(tx, blkCtx, outputTracer); - - bool failed = traceEnoughGas.Contains("failed") || traceEnoughGas.Contains("OutOfGas"); - failed.Should().BeTrue(); + outputTracer.StatusCode.Should().Be(StatusCode.Failure, + $"transaction should fail below the estimate ({tx.GasLimit})"); } [TestCase] @@ -529,7 +503,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); @@ -573,7 +547,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); @@ -615,7 +589,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 66f5c5c427be..c31d811b18f6 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs @@ -25,10 +25,10 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Test; using Nethermind.Crypto; -using Nethermind.State; namespace Nethermind.Blockchain.Test { + [Parallelizable(ParallelScope.All)] public class TransactionSelectorTests { public static IEnumerable ProperTransactionsSelectedTestCases @@ -164,6 +164,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,26 +174,24 @@ 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( "Enough shard blob transactions and others selected"); ProperTransactionsSelectedTestCase higherPriorityTransactionsSelected = ProperTransactionsSelectedTestCase.Eip1559Default; - var accounts = higherPriorityTransactionsSelected.AccountStates; + IDictionary accounts = higherPriorityTransactionsSelected.AccountStates; accounts[TestItem.AddressA] = (1000, 0); accounts[TestItem.AddressB] = (1000, 0); accounts[TestItem.AddressC] = (1000, 0); @@ -248,15 +247,15 @@ public static IEnumerable BlobTransactionOrderingTestCases { get { - (Address address, PrivateKey key)[] Accounts = - { + (Address address, PrivateKey key)[] accounts = + [ (TestItem.AddressA, TestItem.PrivateKeyA), (TestItem.AddressB, TestItem.PrivateKeyB) - }; + ]; { - var blobTxs = CreateTestCase(); - var txs = new List(); + ProperTransactionsSelectedTestCase blobTxs = CreateTestCase(); + List txs = []; UInt256 nonce = 1; AddTxs(txCount: 5, blobsPerTx: 5, account: 0, txs, ref nonce); @@ -269,8 +268,8 @@ public static IEnumerable BlobTransactionOrderingTestCases yield return new TestCaseData(blobTxs).SetName("Blob Transaction Ordering, Single Account, Single Blob"); } { - var blobTxs = CreateTestCase(); - var txs = new List(); + ProperTransactionsSelectedTestCase blobTxs = CreateTestCase(); + List txs = []; UInt256 nonce = 1; AddTxs(txCount: 5, blobsPerTx: 5, account: 0, txs, ref nonce); @@ -284,7 +283,7 @@ public static IEnumerable BlobTransactionOrderingTestCases yield return new TestCaseData(blobTxs).SetName("Blob Transaction Ordering, Single Account, Dual Blob"); } { - var blobTxs = CreateTestCase(); + ProperTransactionsSelectedTestCase blobTxs = CreateTestCase(); var txs = new List(); UInt256 nonce = 1; @@ -300,8 +299,8 @@ public static IEnumerable BlobTransactionOrderingTestCases yield return new TestCaseData(blobTxs).SetName("Blob Transaction Ordering, Multiple Accounts"); } { - var blobTxs = CreateTestCase(); - var txs = new List(); + ProperTransactionsSelectedTestCase blobTxs = CreateTestCase(); + List txs = []; UInt256 nonce0 = 1; AddTxs(txCount: 5, blobsPerTx: 5, account: 0, txs, ref nonce0); @@ -316,8 +315,8 @@ public static IEnumerable BlobTransactionOrderingTestCases yield return new TestCaseData(blobTxs).SetName("Blob Transaction Ordering, Multiple Accounts, Nonce Order 1"); } { - var blobTxs = CreateTestCase(); - var txs = new List(); + ProperTransactionsSelectedTestCase blobTxs = CreateTestCase(); + List txs = []; UInt256 nonce0 = 1; AddTxs(txCount: 1, blobsPerTx: 5, account: 0, txs, ref nonce0); @@ -333,8 +332,8 @@ public static IEnumerable BlobTransactionOrderingTestCases yield return new TestCaseData(blobTxs).SetName("Blob Transaction Ordering, Multiple Accounts, Nonce Order 2"); } { - var blobTxs = CreateTestCase(); - var txs = new List(); + ProperTransactionsSelectedTestCase blobTxs = CreateTestCase(); + List txs = []; UInt256 nonce0 = 1; AddTxs(txCount: 1, blobsPerTx: 5, account: 0, txs, ref nonce0, priority: 1); @@ -349,8 +348,8 @@ public static IEnumerable BlobTransactionOrderingTestCases yield return new TestCaseData(blobTxs).SetName("Blob Transaction Ordering, Multiple Accounts, Nonce Order 3"); } { - var blobTxs = CreateTestCase(); - var txs = new List(); + ProperTransactionsSelectedTestCase blobTxs = CreateTestCase(); + List txs = []; UInt256 nonce0 = 1; AddTxs(txCount: 1, blobsPerTx: 5, account: 0, txs, ref nonce0, priority: 1); @@ -365,8 +364,8 @@ public static IEnumerable BlobTransactionOrderingTestCases yield return new TestCaseData(blobTxs).SetName("Blob Transaction Ordering, Multiple Accounts, Nonce Order 4"); } { - var blobTxs = CreateTestCase(); - var txs = new List(); + ProperTransactionsSelectedTestCase blobTxs = CreateTestCase(); + List txs = []; UInt256 nonce0 = 1; AddTxs(txCount: 1, blobsPerTx: 5, account: 0, txs, ref nonce0, priority: 1); @@ -381,8 +380,8 @@ public static IEnumerable BlobTransactionOrderingTestCases yield return new TestCaseData(blobTxs).SetName("Blob Transaction Ordering, Multiple Accounts, Nonce Order 5a"); } { - var blobTxs = CreateTestCase(); - var txs = new List(); + ProperTransactionsSelectedTestCase blobTxs = CreateTestCase(); + List txs = []; UInt256 nonce0 = 1; AddTxs(txCount: 2, blobsPerTx: 2, account: 0, txs, ref nonce0, priority: 1); @@ -397,8 +396,8 @@ public static IEnumerable BlobTransactionOrderingTestCases yield return new TestCaseData(blobTxs).SetName("Blob Transaction Ordering, Multiple Accounts, Nonce Order 5b"); } { - var blobTxs = CreateTestCase(); - var txs = new List(); + ProperTransactionsSelectedTestCase blobTxs = CreateTestCase(); + List txs = []; UInt256 nonce0 = 1; AddTxs(txCount: 1, blobsPerTx: 5, account: 0, txs, ref nonce0, priority: 1); @@ -414,8 +413,8 @@ public static IEnumerable BlobTransactionOrderingTestCases yield return new TestCaseData(blobTxs).SetName("Blob Transaction Ordering, Multiple Accounts, Nonce Order 6"); } { - var blobTxs = CreateTestCase(); - var txs = new List(); + ProperTransactionsSelectedTestCase blobTxs = CreateTestCase(); + List txs = []; UInt256 nonce0 = 1; AddTxs(txCount: 1, blobsPerTx: 5, account: 0, txs, ref nonce0, priority: 1); @@ -429,8 +428,8 @@ public static IEnumerable BlobTransactionOrderingTestCases yield return new TestCaseData(blobTxs).SetName("Blob Transaction Ordering, Multiple Accounts, Nonce Order 7a"); } { - var blobTxs = CreateTestCase(); - var txs = new List(); + ProperTransactionsSelectedTestCase blobTxs = CreateTestCase(); + List txs = []; UInt256 nonce1 = 1; AddTxs(txCount: 1, blobsPerTx: 5, account: 1, txs, ref nonce1, priority: 1); @@ -446,8 +445,8 @@ public static IEnumerable BlobTransactionOrderingTestCases static ProperTransactionsSelectedTestCase CreateTestCase() { - var higherPriorityTransactionsSelected = ProperTransactionsSelectedTestCase.Eip1559Default; - var accounts = higherPriorityTransactionsSelected.AccountStates; + ProperTransactionsSelectedTestCase higherPriorityTransactionsSelected = ProperTransactionsSelectedTestCase.Eip1559Default; + IDictionary accounts = higherPriorityTransactionsSelected.AccountStates; accounts[TestItem.AddressA] = (1000000, 0); accounts[TestItem.AddressB] = (1000000, 0); higherPriorityTransactionsSelected.ReleaseSpec = Cancun.Instance; @@ -457,7 +456,7 @@ static ProperTransactionsSelectedTestCase CreateTestCase() void AddTxs(int txCount, int blobsPerTx, int account, List txs, ref UInt256 nonce, int priority = -1) { - var eoa = Accounts[account]; + (Address address, PrivateKey key) eoa = accounts[account]; for (int i = 0; i < txCount; i++) { txs.Add(CreateBlobTransaction(eoa.address, eoa.key, maxFee: 1000, blobsPerTx, nonce, @@ -482,7 +481,7 @@ Hash256 SetAccountStates(IEnumerable
missingAddresses) { HashSet
missingAddressesSet = missingAddresses.ToHashSet(); - using var _ = stateProvider.BeginScope(IWorldState.PreGenesis); + using IDisposable _ = stateProvider.BeginScope(IWorldState.PreGenesis); foreach (KeyValuePair accountState in testCase.AccountStates .Where(v => !missingAddressesSet.Contains(v.Key))) diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs index f61ef9838310..1282d9038c21 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs @@ -20,7 +20,6 @@ using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.Evm.State; -using Nethermind.Trie.Pruning; using Nethermind.TxPool.Comparison; using NSubstitute; using NUnit.Framework; @@ -32,6 +31,7 @@ namespace Nethermind.Blockchain.Test { + [Parallelizable(ParallelScope.All)] public class TransactionsExecutorTests { public static IEnumerable ProperTransactionsSelectedTestCases @@ -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/TxPoolSourceTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TxPoolSourceTests.cs index 5a5f7a7c2f72..5d196ac0ce31 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TxPoolSourceTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TxPoolSourceTests.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.Consensus.Transactions; @@ -15,13 +15,20 @@ using Nethermind.Config; using NSubstitute; using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Int256; +using Nethermind.TxPool.Comparison; +using FluentAssertions; namespace Nethermind.Consensus.Producers.Test; +[Parallelizable(ParallelScope.All)] public class TxPoolSourceTests { - [TestCaseSource(nameof(BlobTransactionsWithBlobGasLimitPerBlock))] - public void GetTransactions_should_respect_customizable_blob_gas_limit(int[] blobCountPerTx, ulong customMaxBlobGasPerBlock) + [TestCaseSource(nameof(BlobTransactionsWithBlobGasLimitPerBlockCombinations))] + public void GetTransactions_should_respect_customizable_blob_gas_limit(int[] blobCountPerTx, ulong customMaxBlobGasPerBlock, int? customBlobLimit) { TestSingleReleaseSpecProvider specProvider = new(Cancun.Instance); TransactionComparerProvider transactionComparerProvider = new(specProvider, Build.A.BlockTree().TestObject); @@ -30,35 +37,117 @@ public void GetTransactions_should_respect_customizable_blob_gas_limit(int[] blo Dictionary transactionsWithBlobs = blobCountPerTx .Select((blobsCount, index) => (blobCount: blobsCount, index)) .ToDictionary( - pair => new AddressAsKey(new Address((new byte[19]).Concat(new byte[] { (byte)pair.index }).ToArray())), - pair => new Transaction[] { Build.A.Transaction.WithShardBlobTxTypeAndFields(pair.blobCount).TestObject }); + pair => new AddressAsKey(new Address(new byte[19].Concat(new[] { (byte)pair.index }).ToArray())), + pair => new[] { Build.A.Transaction.WithShardBlobTxTypeAndFields(pair.blobCount).TestObject }); txPool.GetPendingTransactions().Returns([]); txPool.GetPendingLightBlobTransactionsBySender().Returns(transactionsWithBlobs); ITxFilterPipeline txFilterPipeline = Substitute.For(); - txFilterPipeline.Execute(Arg.Any(), Arg.Any(), Arg.Any()).Returns(AcceptTxResult.Accepted); + txFilterPipeline.Execute(Arg.Any(), Arg.Any(), Arg.Any()).Returns(true); - TxPoolTxSource transactionSelector = new(txPool, specProvider, transactionComparerProvider, LimboLogs.Instance, txFilterPipeline, new BlocksConfig { SecondsPerSlot = 12 }); + TxPoolTxSource transactionSelector = new(txPool, specProvider, transactionComparerProvider, LimboLogs.Instance, txFilterPipeline, new BlocksConfig { SecondsPerSlot = 12, BlockProductionBlobLimit = customBlobLimit }); - IEnumerable txs = transactionSelector.GetTransactions(new BlockHeader { }, long.MaxValue); + IEnumerable txs = transactionSelector.GetTransactions(new BlockHeader(), long.MaxValue); int blobsCount = txs.Sum(tx => tx.GetBlobCount()); - Assert.Multiple(() => + Assert.That(blobsCount, Is.LessThanOrEqualTo(Cancun.Instance.MaxProductionBlobCount(customBlobLimit))); + } + + public static IEnumerable BlobTransactionsWithBlobGasLimitPerBlockCombinations() + { + int?[] customBlobLimits = [null, 0, 1, 2, 3, 5, 500]; + foreach ((int[] blobCountPerTx, ulong customMaxBlobGasPerBlock) in BlobTransactionsWithBlobGasLimitPerBlock()) { - Assert.That(blobsCount, Is.LessThanOrEqualTo(Cancun.Instance.MaxBlobCount)); - }); + foreach (int? customBlobLimit in customBlobLimits) + { + yield return new TestCaseData(blobCountPerTx, customMaxBlobGasPerBlock, customBlobLimit); + } + } + } + + public static IEnumerable<(int[], ulong)> BlobTransactionsWithBlobGasLimitPerBlock() + { + yield return ([1, 2, 4], Eip4844Constants.GasPerBlob * 6); + yield return ([1, 2, 6], Eip4844Constants.GasPerBlob * 6); + yield return ([1, 6], Eip4844Constants.GasPerBlob * 6); + yield return ([6, 1, 5], Eip4844Constants.GasPerBlob * 6); + yield return ([1, 2], Eip4844Constants.GasPerBlob * 2); + yield return ([1, 1], Eip4844Constants.GasPerBlob * 2); + yield return ([2, 1], Eip4844Constants.GasPerBlob * 2); + yield return ([2, 2], Eip4844Constants.GasPerBlob * 2); + yield return ([3], Eip4844Constants.GasPerBlob * 2); } - public static IEnumerable BlobTransactionsWithBlobGasLimitPerBlock() + [TestCaseSource(nameof(MaxProductionBlobCountTests))] + public int MaxProductionBlobCount_calculation(IReleaseSpec spec, int? customBlobLimit) => spec.MaxProductionBlobCount(customBlobLimit); + + public static IEnumerable MaxProductionBlobCountTests() + { + yield return new TestCaseData(Cancun.Instance, null).Returns(Cancun.Instance.MaxBlobCount); + yield return new TestCaseData(Prague.Instance, null).Returns(Prague.Instance.MaxBlobCount); + yield return new TestCaseData(BPO1.Instance, null).Returns(BPO1.Instance.MaxBlobCount); + yield return new TestCaseData(BPO2.Instance, null).Returns(BPO2.Instance.MaxBlobCount); + + yield return new TestCaseData(Prague.Instance, -1).Returns(Prague.Instance.MaxBlobCount); + yield return new TestCaseData(Prague.Instance, 0).Returns(0); + yield return new TestCaseData(BPO1.Instance, 5).Returns(5); + yield return new TestCaseData(BPO2.Instance, 500_000).Returns(BPO2.Instance.MaxBlobCount); + } + + [Test] + public void GetTransactions_should_order_blob_txs_before_regular_txs_when_blob_has_higher_priority() { - yield return new TestCaseData(new int[] { 1, 2, 4 }, Eip4844Constants.GasPerBlob * 6); - yield return new TestCaseData(new int[] { 1, 2, 6 }, Eip4844Constants.GasPerBlob * 6); - yield return new TestCaseData(new int[] { 1, 6 }, Eip4844Constants.GasPerBlob * 6); - yield return new TestCaseData(new int[] { 6, 1, 5 }, Eip4844Constants.GasPerBlob * 6); - yield return new TestCaseData(new int[] { 1, 2 }, Eip4844Constants.GasPerBlob * 2); - yield return new TestCaseData(new int[] { 1, 1 }, Eip4844Constants.GasPerBlob * 2); - yield return new TestCaseData(new int[] { 2, 1 }, Eip4844Constants.GasPerBlob * 2); - yield return new TestCaseData(new int[] { 2, 2 }, Eip4844Constants.GasPerBlob * 2); - yield return new TestCaseData(new int[] { 3 }, Eip4844Constants.GasPerBlob * 2); + TestSingleReleaseSpecProvider specProvider = new(Cancun.Instance); + TransactionComparerProvider transactionComparerProvider = new(specProvider, Build.A.BlockTree().TestObject); + + // Create a high-priority blob tx (high gas price) + Transaction highPriorityBlobTx = Build.A.Transaction + .WithShardBlobTxTypeAndFields() + .WithMaxFeePerGas(1000.GWei()) + .WithMaxPriorityFeePerGas(500.GWei()) + .SignedAndResolved(TestItem.PrivateKeyA) + .TestObject; + + // Create a lower-priority regular tx (lower gas price) + Transaction lowerPriorityRegularTx = Build.A.Transaction + .WithType(TxType.EIP1559) + .WithMaxFeePerGas(100.GWei()) + .WithMaxPriorityFeePerGas(50.GWei()) + .SignedAndResolved(TestItem.PrivateKeyB) + .TestObject; + + // Verify comparer semantics: higher priority tx should compare as "less than" (negative result) + IComparer comparer = transactionComparerProvider.GetDefaultProducerComparer( + new BlockPreparationContext(UInt256.Zero, 1)); + int compareResult = comparer.Compare(highPriorityBlobTx, lowerPriorityRegularTx); + compareResult.Should().Be(TxComparisonResult.XFirst, "Higher priority transaction should compare as XFirst (negative)"); + + // Setup mocks + ITxPool txPool = Substitute.For(); + txPool.GetPendingTransactionsBySender(Arg.Any(), Arg.Any()) + .Returns(new Dictionary { { TestItem.AddressB, [lowerPriorityRegularTx] } }); + txPool.GetPendingLightBlobTransactionsBySender() + .Returns(new Dictionary { { TestItem.AddressA, [highPriorityBlobTx] } }); + txPool.TryGetPendingBlobTransaction(Arg.Is(h => h == highPriorityBlobTx.Hash), out Arg.Any()) + .Returns(x => + { + x[1] = highPriorityBlobTx; + return true; + }); + txPool.SupportsBlobs.Returns(true); + + ITxFilterPipeline txFilterPipeline = Substitute.For(); + txFilterPipeline.Execute(Arg.Any(), Arg.Any(), Arg.Any()).Returns(true); + + TxPoolTxSource txSource = new(txPool, specProvider, transactionComparerProvider, LimboLogs.Instance, + txFilterPipeline, new BlocksConfig { SecondsPerSlot = 12 }); + + BlockHeader parent = Build.A.BlockHeader.WithNumber(0).WithExcessBlobGas(0).TestObject; + + // Act + Transaction[] result = txSource.GetTransactions(parent, long.MaxValue).ToArray(); + + // Assert: High priority blob tx should come BEFORE lower priority regular tx + result.Should().BeEquivalentTo([highPriorityBlobTx, lowerPriorityRegularTx], o => o.WithStrictOrdering()); } } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Utils/LastNStateRootTrackerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Utils/LastNStateRootTrackerTests.cs index 1af52fc05346..07c79a8a1ec5 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Utils/LastNStateRootTrackerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Utils/LastNStateRootTrackerTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Utils; +[Parallelizable(ParallelScope.All)] public class LastNStateRootTrackerTests { [Test] @@ -106,4 +107,31 @@ public void Test_OnReorg_RebuildSet() tracker.HasStateRoot(Keccak.Compute(100.ToBigEndianByteArray())).Should().BeTrue(); } + + [Test] + public void Test_TrackLastN_WithCustomDepth() + { + System.Collections.Generic.List blocks = new(); + Block currentBlock = Build.A.Block.Genesis.TestObject; + blocks.Add(currentBlock); + for (int i = 0; i < 300; i++) + { + currentBlock = Build.A.Block + .WithParent(currentBlock) + .WithStateRoot(Keccak.Compute(i.ToBigEndianByteArray())) + .TestObject; + blocks.Add(currentBlock); + } + + BlockTree tree = Build.A.BlockTree().WithBlocks(blocks.ToArray()).TestObject; + + // Test with a custom depth of 256 blocks (useful for networks with fast block times like Arbitrum) + LastNStateRootTracker tracker = new LastNStateRootTracker(tree, 256); + + for (int i = 0; i < 320; i++) + { + tracker.HasStateRoot(Keccak.Compute(i.ToBigEndianByteArray())) + .Should().Be(i is >= 44 and < 300); + } + } } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/BlockValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/BlockValidatorTests.cs index 2c5cb0302855..78a14474285d 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/BlockValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/BlockValidatorTests.cs @@ -14,9 +14,12 @@ using NSubstitute; using NUnit.Framework; using System.Collections.Generic; +using FluentAssertions; namespace Nethermind.Blockchain.Test.Validators; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class BlockValidatorTests { private static BlockValidator _blockValidator = null!; @@ -63,6 +66,23 @@ public void When_more_uncles_than_allowed_returns_false() Assert.That(result, Is.False); } + [Test, MaxTime(Timeout.MaxTestTime)] + public void When_do_not_check_uncle_when_orphaned() + { + TxValidator txValidator = new(TestBlockchainIds.ChainId); + ISpecProvider specProvider = new TestSpecProvider(Frontier.Instance); + + BlockValidator blockValidator = new(txValidator, Always.Valid, Always.Invalid, specProvider, LimboLogs.Instance); + + BlockHeader parent = Build.A.BlockHeader.TestObject; + Block block = Build.A.Block + .WithParent(parent) + .WithUncles(Build.A.BlockHeader.WithNumber(10).TestObject) + .TestObject; + blockValidator.ValidateSuggestedBlock(block, parent, out _).Should().Be(false); + blockValidator.ValidateOrphanedBlock(block, out _).Should().Be(true); + } + [Test] public void ValidateBodyAgainstHeader_BlockIsValid_ReturnsTrue() { @@ -184,6 +204,44 @@ public void ValidateProcessedBlock_StateRootIsWrong_ErrorIsSet() Assert.That(error, Does.StartWith("InvalidStateRoot")); } + [Test] + public void ValidateProcessedBlock_ReceiptCountMismatch_DoesNotThrow() + { + TxValidator txValidator = new(TestBlockchainIds.ChainId); + ISpecProvider specProvider = Substitute.For(); + BlockValidator sut = new(txValidator, Always.Valid, Always.Valid, specProvider, LimboLogs.Instance); + Block suggestedBlock = Build.A.Block.TestObject; + Block processedBlock = Build.A.Block + .WithStateRoot(Keccak.Zero) + .WithTransactions(2, specProvider) + .TestObject; + + Assert.DoesNotThrow(() => sut.ValidateProcessedBlock( + processedBlock, + [], + suggestedBlock)); + } + + [Test] + public void ValidateProcessedBlock_ReceiptCountMismatch_ReturnsFalse() + { + TxValidator txValidator = new(TestBlockchainIds.ChainId); + ISpecProvider specProvider = Substitute.For(); + BlockValidator sut = new(txValidator, Always.Valid, Always.Valid, specProvider, LimboLogs.Instance); + Block suggestedBlock = Build.A.Block.TestObject; + Block processedBlock = Build.A.Block + .WithStateRoot(Keccak.Zero) + .WithTransactions(3, specProvider) + .TestObject; + + bool result = sut.ValidateProcessedBlock( + processedBlock, + [Build.A.Receipt.TestObject], + suggestedBlock); + + Assert.That(result, Is.False); + } + private static IEnumerable BadSuggestedBlocks() { BlockHeader parent = Build.A.BlockHeader.TestObject; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/HeaderValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/HeaderValidatorTests.cs index 0eaea4fdee05..fb3a63499864 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/HeaderValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/HeaderValidatorTests.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Linq; using System.Numerics; using Nethermind.Consensus; using Nethermind.Consensus.Ethash; @@ -22,6 +23,8 @@ namespace Nethermind.Blockchain.Test.Validators; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class HeaderValidatorTests { private IHeaderValidator _validator = null!; @@ -58,7 +61,6 @@ public void Setup() [Test, MaxTime(Timeout.MaxTestTime)] public void Valid_when_valid() { - _block.Header.SealEngineType = SealEngineType.None; bool result = _validator.Validate(_block.Header, _parentBlock.Header); if (!result) { @@ -75,7 +77,6 @@ public void Valid_when_valid() public void When_gas_limit_too_high() { _block.Header.GasLimit = _parentBlock.Header.GasLimit + (long)BigInteger.Divide(_parentBlock.Header.GasLimit, 1024); - _block.Header.SealEngineType = SealEngineType.None; _block.Header.Hash = _block.CalculateHash(); bool result = _validator.Validate(_block.Header, _parentBlock.Header); @@ -86,7 +87,6 @@ public void When_gas_limit_too_high() public void When_gas_limit_just_correct_high() { _block.Header.GasLimit = _parentBlock.Header.GasLimit + (long)BigInteger.Divide(_parentBlock.Header.GasLimit, 1024) - 1; - _block.Header.SealEngineType = SealEngineType.None; _block.Header.Hash = _block.CalculateHash(); bool result = _validator.Validate(_block.Header, _parentBlock.Header); @@ -97,7 +97,6 @@ public void When_gas_limit_just_correct_high() public void When_gas_limit_just_correct_low() { _block.Header.GasLimit = _parentBlock.Header.GasLimit - (long)BigInteger.Divide(_parentBlock.Header.GasLimit, 1024) + 1; - _block.Header.SealEngineType = SealEngineType.None; _block.Header.Hash = _block.CalculateHash(); bool result = _validator.Validate(_block.Header, _parentBlock.Header); @@ -108,7 +107,6 @@ public void When_gas_limit_just_correct_low() public void When_gas_limit_is_just_too_low() { _block.Header.GasLimit = _parentBlock.Header.GasLimit - (long)BigInteger.Divide(_parentBlock.Header.GasLimit, 1024); - _block.Header.SealEngineType = SealEngineType.None; _block.Header.Hash = _block.CalculateHash(); bool result = _validator.Validate(_block.Header, _parentBlock.Header); @@ -119,7 +117,6 @@ public void When_gas_limit_is_just_too_low() public void When_gas_used_above_gas_limit() { _block.Header.GasUsed = _parentBlock.Header.GasLimit + 1; - _block.Header.SealEngineType = SealEngineType.None; _block.Header.Hash = _block.CalculateHash(); bool result = _validator.Validate(_block.Header, _parentBlock.Header); @@ -130,7 +127,6 @@ public void When_gas_used_above_gas_limit() public void When_no_parent_invalid() { _block.Header.ParentHash = Keccak.Zero; - _block.Header.SealEngineType = SealEngineType.None; _block.Header.Hash = _block.CalculateHash(); bool result = _validator.Validate(_block.Header, _parentBlock.Header); @@ -141,7 +137,6 @@ public void When_no_parent_invalid() public void When_timestamp_same_as_parent() { _block.Header.Timestamp = _parentBlock.Header.Timestamp; - _block.Header.SealEngineType = SealEngineType.None; _block.Header.Hash = _block.CalculateHash(); bool result = _validator.Validate(_block.Header, _parentBlock.Header); @@ -152,7 +147,6 @@ public void When_timestamp_same_as_parent() public void When_extra_data_too_long() { _block.Header.ExtraData = new byte[33]; - _block.Header.SealEngineType = SealEngineType.None; _block.Header.Hash = _block.CalculateHash(); bool result = _validator.Validate(_block.Header, _parentBlock.Header); @@ -163,7 +157,6 @@ public void When_extra_data_too_long() public void When_incorrect_difficulty_then_invalid() { _block.Header.Difficulty = 1; - _block.Header.SealEngineType = SealEngineType.None; _block.Header.Hash = _block.CalculateHash(); bool result = _validator.Validate(_block.Header, _parentBlock.Header); @@ -174,7 +167,6 @@ public void When_incorrect_difficulty_then_invalid() public void When_incorrect_number_then_invalid() { _block.Header.Number += 1; - _block.Header.SealEngineType = SealEngineType.None; _block.Header.Hash = _block.CalculateHash(); bool result = _validator.Validate(_block.Header, _parentBlock.Header); @@ -215,7 +207,6 @@ public void When_gaslimit_is_on_london_fork(long parentGasLimit, long blockNumbe .WithNumber(_parentBlock.Number + 1) .WithBaseFeePerGas(BaseFeeCalculator.Calculate(_parentBlock.Header, specProvider.GetSpec((ForkActivation)(_parentBlock.Number + 1)))) .WithNonce(0).TestObject; - _block.Header.SealEngineType = SealEngineType.None; _block.Header.Hash = _block.CalculateHash(); bool result = _validator.Validate(_block.Header, _parentBlock.Header); @@ -236,7 +227,6 @@ public void When_gas_limit_is_long_max_value() .WithGasLimit(long.MaxValue) .WithNumber(_parentBlock.Number + 1) .WithNonce(0).TestObject; - _block.Header.SealEngineType = SealEngineType.None; _block.Header.Hash = _block.CalculateHash(); bool result = _validator.Validate(_block.Header, _parentBlock.Header); @@ -269,7 +259,6 @@ public void When_total_difficulty_null_we_should_skip_total_difficulty_validatio { _block.Header.Difficulty = 1; _block.Header.TotalDifficulty = null; - _block.Header.SealEngineType = SealEngineType.None; _block.Header.Hash = _block.CalculateHash(); HeaderValidator validator = new HeaderValidator(_blockTree, Always.Valid, _specProvider, new OneLoggerLogManager(new(_testLogger))); @@ -289,7 +278,6 @@ public void When_total_difficulty_zero_we_should_skip_total_difficulty_validatio { _block.Header.Difficulty = 1; _block.Header.TotalDifficulty = 0; - _block.Header.SealEngineType = SealEngineType.None; _block.Header.Hash = _block.CalculateHash(); { @@ -340,4 +328,117 @@ public void When_given_parent_is_wrong() Assert.That(result, Is.False); Assert.That(error, Does.StartWith("Mismatched parent")); } + + [Test] + public void BaseFee_validation_must_not_be_bypassed_for_NoProof_seal_engine() + { + // Arrange: Create London fork with EIP-1559 + OverridableReleaseSpec spec = new(London.Instance) + { + Eip1559TransitionBlock = 0 + }; + TestSpecProvider specProvider = new(spec); + + // Create validator with no-op seal validator (simulates NoProof) + _validator = new HeaderValidator( + _blockTree, + Always.Valid, // No seal validation (NoProof behavior) + specProvider, + new OneLoggerLogManager(new(_testLogger)) + ); + + // Parent block: baseFee=10, gasUsed=0, gasLimit=300000000 + _parentBlock = Build.A.Block + .WithDifficulty(0) // Post-merge + .WithBaseFeePerGas(10) + .WithGasUsed(0) + .WithGasLimit(300000000) + .WithNumber(5) + .TestObject; + + // Calculate expected baseFee for child block + // parentGasTarget = 300000000 / 2 = 150000000 + // gasDelta = 150000000 - 0 = 150000000 + // feeDelta = 10 * 150000000 / 150000000 / 8 = 1 + // expectedBaseFee = 10 - 1 = 9 + UInt256 expectedBaseFee = BaseFeeCalculator.Calculate( + _parentBlock.Header, + specProvider.GetSpec((ForkActivation)(_parentBlock.Number + 1)) + ); + Assert.That(expectedBaseFee, Is.EqualTo((UInt256)9), "Test setup: expected baseFee should be 9"); + + // Create block with INCORRECT baseFee (10 instead of 9) + _block = Build.A.Block + .WithParent(_parentBlock) + .WithDifficulty(0) + .WithBaseFeePerGas(10) // WRONG! Should be 9 + .WithGasUsed(0) + .WithGasLimit(300000000) + .WithNumber(_parentBlock.Number + 1) + .TestObject; + + _block.Header.Hash = _block.CalculateHash(); + + // Act: Validate the block + bool result = _validator.Validate(_block.Header, _parentBlock.Header); + + // Assert: Block MUST be rejected due to invalid baseFee + // Even though seal engine is NoProof, consensus rules must be enforced + Assert.That(result, Is.False, + "Block with invalid baseFee must be rejected even with NoProof seal engine. " + + $"Expected baseFee=9, actual baseFee=10"); + + // Verify the error message mentions baseFee + bool baseFeeErrorLogged = _testLogger.LogList.Any(log => + log.Contains("base fee", StringComparison.OrdinalIgnoreCase) || + log.Contains("baseFee", StringComparison.OrdinalIgnoreCase)); + Assert.That(baseFeeErrorLogged, Is.True, + "Validation should log baseFee mismatch error"); + } + + [Test] + public void Valid_baseFee_with_NoProof_seal_engine_should_pass() + { + // Arrange: Same setup as above + OverridableReleaseSpec spec = new(London.Instance) + { + Eip1559TransitionBlock = 0 + }; + TestSpecProvider specProvider = new(spec); + _validator = new HeaderValidator(_blockTree, Always.Valid, specProvider, + new OneLoggerLogManager(new(_testLogger))); + + _parentBlock = Build.A.Block + .WithDifficulty(0) + .WithBaseFeePerGas(10) + .WithGasUsed(0) + .WithGasLimit(300000000) + .WithNumber(5) + .TestObject; + + // Calculate CORRECT baseFee + UInt256 correctBaseFee = BaseFeeCalculator.Calculate( + _parentBlock.Header, + specProvider.GetSpec((ForkActivation)(_parentBlock.Number + 1)) + ); + + // Create block with CORRECT baseFee (9) + _block = Build.A.Block + .WithParent(_parentBlock) + .WithDifficulty(0) + .WithBaseFeePerGas(correctBaseFee) // CORRECT: 9 + .WithGasUsed(0) + .WithGasLimit(300000000) + .WithNumber(_parentBlock.Number + 1) + .TestObject; + + _block.Header.Hash = _block.CalculateHash(); + + // Act + bool result = _validator.Validate(_block.Header, _parentBlock.Header); + + // Assert: Valid block should pass + Assert.That(result, Is.True, + "Block with correct baseFee should be accepted with NoProof seal engine"); + } } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs index 0d386ee72497..28e7fe635cbe 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs @@ -15,6 +15,7 @@ namespace Nethermind.Blockchain.Test.Validators; +[Parallelizable(ParallelScope.All)] public class ShardBlobBlockValidatorTests { [TestCaseSource(nameof(BlobGasFieldsPerForkTestCases))] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs index 9cb84ee9ce48..ac04461b93fc 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs @@ -26,12 +26,9 @@ namespace Nethermind.Blockchain.Test.Validators; +[Parallelizable(ParallelScope.All)] public class TxValidatorTests { - [SetUp] - public void Setup() - { - } [Test, MaxTime(Timeout.MaxTestTime)] public void Curve_is_correct() @@ -851,6 +848,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 cd97053ed709..0ecbe42eb588 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/UnclesValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/UnclesValidatorTests.cs @@ -12,9 +12,11 @@ namespace Nethermind.Blockchain.Test.Validators; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class UnclesValidatorTests { - private Block _grandgrandparent; + private Block _greatGrandparent; private Block _grandparent; private Block _parent; private Block _block; @@ -27,9 +29,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 +144,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.Test/Validators/WithdrawalValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs index 757493dcbf71..604c64e2d497 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs @@ -1,14 +1,12 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Microsoft.AspNetCore.Mvc.ApplicationModels; using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Logging; -using Nethermind.Serialization.Rlp; using Nethermind.Specs.Forks; using Nethermind.Specs.Test; using Nethermind.State.Proofs; @@ -16,6 +14,7 @@ namespace Nethermind.Blockchain.Test.Validators; +[Parallelizable(ParallelScope.All)] public class WithdrawalValidatorTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Visitors/DbBlocksLoaderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Visitors/DbBlocksLoaderTests.cs index b468c23eed9c..420a86305e72 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Visitors/DbBlocksLoaderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Visitors/DbBlocksLoaderTests.cs @@ -17,6 +17,7 @@ namespace Nethermind.Blockchain.Test.Visitors; +[Parallelizable(ParallelScope.All)] public class DbBlocksLoaderTests { private readonly int _dbLoadTimeout = 5000; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Visitors/StartupTreeFixerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Visitors/StartupTreeFixerTests.cs index 8e787f1a5067..1191fe481b7a 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Visitors/StartupTreeFixerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Visitors/StartupTreeFixerTests.cs @@ -17,6 +17,7 @@ namespace Nethermind.Blockchain.Test.Visitors; +[Parallelizable(ParallelScope.All)] public class StartupTreeFixerTests { [Test, MaxTime(Timeout.MaxTestTime), Ignore("Not implemented")] diff --git a/src/Nethermind/Nethermind.Blockchain/BeaconBlockRoot/BeaconBlockRootHandler.cs b/src/Nethermind/Nethermind.Blockchain/BeaconBlockRoot/BeaconBlockRootHandler.cs index a8abe08c1340..c25d4339e2f3 100644 --- a/src/Nethermind/Nethermind.Blockchain/BeaconBlockRoot/BeaconBlockRootHandler.cs +++ b/src/Nethermind/Nethermind.Blockchain/BeaconBlockRoot/BeaconBlockRootHandler.cs @@ -11,6 +11,7 @@ using Nethermind.Int256; namespace Nethermind.Blockchain.BeaconBlockRoot; + public class BeaconBlockRootHandler(ITransactionProcessor processor, IWorldState stateProvider) : IBeaconBlockRootHandler { private const long GasLimit = 30_000_000L; diff --git a/src/Nethermind/Nethermind.Blockchain/BeaconBlockRoot/IBeaconBlockRootHandler.cs b/src/Nethermind/Nethermind.Blockchain/BeaconBlockRoot/IBeaconBlockRootHandler.cs index 3b1a204a9b33..5cfa5fafc6d9 100644 --- a/src/Nethermind/Nethermind.Blockchain/BeaconBlockRoot/IBeaconBlockRootHandler.cs +++ b/src/Nethermind/Nethermind.Blockchain/BeaconBlockRoot/IBeaconBlockRootHandler.cs @@ -4,10 +4,10 @@ using Nethermind.Core; using Nethermind.Core.Eip2930; using Nethermind.Core.Specs; -using Nethermind.Evm; using Nethermind.Evm.Tracing; namespace Nethermind.Blockchain.BeaconBlockRoot; + public interface IBeaconBlockRootHandler : IHasAccessList { (Address? toAddress, AccessList? accessList) BeaconRootsAccessList(Block block, IReleaseSpec spec, bool includeStorageCells = true); diff --git a/src/Nethermind/Nethermind.Blockchain/BlockTree.AcceptVisitor.cs b/src/Nethermind/Nethermind.Blockchain/BlockTree.AcceptVisitor.cs index 77363c4f2cab..1102c83812b1 100644 --- a/src/Nethermind/Nethermind.Blockchain/BlockTree.AcceptVisitor.cs +++ b/src/Nethermind/Nethermind.Blockchain/BlockTree.AcceptVisitor.cs @@ -65,9 +65,9 @@ public async Task Accept(IBlockTreeVisitor visitor, CancellationToken cancellati { if (visitor.CalculateTotalDifficultyIfMissing && (block.TotalDifficulty is null || block.TotalDifficulty == 0)) { - if (_logger.IsTrace) _logger.Trace($"Setting TD for block {block.Number}. Old TD: {block.TotalDifficulty}."); + if (Logger.IsTrace) Logger.Trace($"Setting TD for block {block.Number}. Old TD: {block.TotalDifficulty}."); SetTotalDifficulty(block.Header); - if (_logger.IsTrace) _logger.Trace($"Setting TD for block {block.Number}. New TD: {block.TotalDifficulty}."); + if (Logger.IsTrace) Logger.Trace($"Setting TD for block {block.Number}. New TD: {block.TotalDifficulty}."); } if (await VisitBlock(visitor, block, cancellationToken)) break; } @@ -86,7 +86,7 @@ public async Task Accept(IBlockTreeVisitor visitor, CancellationToken cancellati string resultWord = cancellationToken.IsCancellationRequested ? "Canceled" : "Completed"; - if (_logger.IsDebug) _logger.Debug($"{resultWord} visiting blocks in DB at level {levelNumber} - best known {BestKnownNumber}"); + if (Logger.IsDebug) Logger.Debug($"{resultWord} visiting blocks in DB at level {levelNumber} - best known {BestKnownNumber}"); } finally { diff --git a/src/Nethermind/Nethermind.Blockchain/BlockTree.Initializer.cs b/src/Nethermind/Nethermind.Blockchain/BlockTree.Initializer.cs index 35e40be0235a..7425bb3b7896 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; @@ -85,7 +84,7 @@ private void AttemptToFixCorruptionByMovingHeadBackwards() } else { - _logger.Error("Failed attempt to fix 'header < body' corruption caused by an unexpected shutdown."); + Logger.Error("Failed attempt to fix 'header < body' corruption caused by an unexpected shutdown."); } } } @@ -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) @@ -158,7 +157,7 @@ private bool BodyExists(long blockNumber, bool findBeacon = false) } private void LoadForkChoiceInfo() { - _logger.Info("Loading fork choice info"); + Logger.Info("Loading fork choice info"); FinalizedHash ??= _metadataDb.Get(MetadataDbKeys.FinalizedBlockHash)?.AsRlpStream().DecodeKeccak(); SafeHash ??= _metadataDb.Get(MetadataDbKeys.SafeBlockHash)?.AsRlpStream().DecodeKeccak(); } @@ -190,7 +189,7 @@ private void LoadLowestInsertedHeader() LowestInsertedHeader = BinarySearchBlockHeader(left, right, LevelExists, BinarySearchDirection.Down); } - if (_logger.IsDebug) _logger.Debug($"Lowest inserted header set to {LowestInsertedHeader?.Number.ToString() ?? "null"}"); + if (Logger.IsDebug) Logger.Debug($"Lowest inserted header set to {LowestInsertedHeader?.Number.ToString() ?? "null"}"); } private void LoadBestKnown() @@ -205,8 +204,8 @@ private void LoadBestKnown() long bestSuggestedHeaderNumber = BinarySearchBlockNumber(left, right, HeaderExists) ?? 0; long bestSuggestedBodyNumber = BinarySearchBlockNumber(left, right, BodyExists) ?? 0; - if (_logger.IsInfo) - _logger.Info("Numbers resolved, " + + if (Logger.IsInfo) + Logger.Info("Numbers resolved, " + $"level = {bestKnownNumberFound}, " + $"header = {bestSuggestedHeaderNumber}, " + $"body = {bestSuggestedBodyNumber}"); @@ -216,8 +215,8 @@ private void LoadBestKnown() bestSuggestedBodyNumber < 0 || bestSuggestedHeaderNumber < bestSuggestedBodyNumber) { - if (_logger.IsWarn) - _logger.Warn( + if (Logger.IsWarn) + Logger.Warn( $"Detected corrupted block tree data ({bestSuggestedHeaderNumber} < {bestSuggestedBodyNumber}) (possibly due to an unexpected shutdown). Attempting to fix by moving head backwards. This may fail and you may need to resync the node."); if (bestSuggestedHeaderNumber < bestSuggestedBodyNumber) { @@ -263,8 +262,8 @@ private void LoadBeaconBestKnown() right = Math.Max(0, left) + BestKnownSearchLimit; long bestBeaconBodyNumber = BinarySearchBlockNumber(left, right, BodyExists, findBeacon: true) ?? 0; - if (_logger.IsInfo) - _logger.Info("Beacon Numbers resolved, " + + if (Logger.IsInfo) + Logger.Info("Beacon Numbers resolved, " + $"level = {bestKnownNumberFound}, " + $"header = {bestBeaconHeaderNumber}, " + $"body = {bestBeaconBodyNumber}"); @@ -274,8 +273,8 @@ private void LoadBeaconBestKnown() bestBeaconBodyNumber < 0 || bestBeaconHeaderNumber < bestBeaconBodyNumber) { - if (_logger.IsWarn) - _logger.Warn( + if (Logger.IsWarn) + Logger.Warn( $"Detected corrupted block tree data ({bestBeaconHeaderNumber} < {bestBeaconBodyNumber}) (possibly due to an unexpected shutdown). Attempting to fix by moving head backwards. This may fail and you may need to resync the node."); if (bestBeaconHeaderNumber < bestBeaconBodyNumber) { @@ -329,7 +328,7 @@ private void LoadStartBlock() if (persistedNumber is not null) { startBlock = FindBlock(persistedNumber.Value, BlockTreeLookupOptions.None); - if (_logger.IsInfo) _logger.Info( + if (Logger.IsInfo) Logger.Info( $"Start block loaded from reorg boundary - {persistedNumber} - {startBlock?.ToString(Block.Format.Short)}"); } else @@ -338,7 +337,7 @@ private void LoadStartBlock() if (data is not null) { startBlock = FindBlock(new Hash256(data), BlockTreeLookupOptions.None); - if (_logger.IsInfo) _logger.Info($"Start block loaded from HEAD - {startBlock?.ToString(Block.Format.Short)}"); + if (Logger.IsInfo) Logger.Info($"Start block loaded from HEAD - {startBlock?.ToString(Block.Format.Short)}"); } } @@ -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,13 +382,13 @@ 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}"); + 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 dcb517846fe7..a9ccc310da6d 100644 --- a/src/Nethermind/Nethermind.Blockchain/BlockTree.cs +++ b/src/Nethermind/Nethermind.Blockchain/BlockTree.cs @@ -30,7 +30,6 @@ namespace Nethermind.Blockchain { - [Todo(Improve.Refactor, "After the fast sync work there are some duplicated code parts for the 'by header' and 'by block' approaches.")] public partial class BlockTree : IBlockTree { // there is not much logic in the addressing here @@ -49,8 +48,8 @@ public partial class BlockTree : IBlockTree private readonly LruCache _invalidBlocks = new(128, 128, "invalid blocks"); - private readonly ILogger _logger; - private readonly ISpecProvider _specProvider; + protected readonly ILogger Logger; + protected readonly ISpecProvider SpecProvider; private readonly IBloomStorage _bloomStorage; private readonly ISyncConfig _syncConfig; private readonly IChainLevelInfoRepository _chainLevelInfoRepository; @@ -95,9 +94,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; @@ -106,6 +105,8 @@ public BlockHeader? LowestInsertedBeaconHeader private TaskCompletionSource? _taskCompletionSource; + private readonly long _genesisBlockNumber; + public BlockTree( IBlockStore? blockStore, IHeaderStore? headerDb, @@ -116,20 +117,23 @@ public BlockTree( ISpecProvider? specProvider, IBloomStorage? bloomStorage, ISyncConfig? syncConfig, - ILogManager? logManager) + ILogManager? logManager, + long genesisBlockNumber = 0) { - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + Logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); _blockStore = blockStore ?? throw new ArgumentNullException(nameof(blockStore)); _headerStore = headerDb ?? throw new ArgumentNullException(nameof(headerDb)); _blockInfoDb = blockInfoDb ?? throw new ArgumentNullException(nameof(blockInfoDb)); _metadataDb = metadataDb ?? throw new ArgumentNullException(nameof(metadataDb)); _badBlockStore = badBlockStore ?? throw new ArgumentNullException(nameof(badBlockStore)); - _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 ?? throw new ArgumentNullException(nameof(chainLevelInfoRepository)); - _oldestBlock = long.Min(syncConfig.AncientBodiesBarrierCalc, syncConfig.AncientReceiptsBarrierCalc); + _oldestBlock = syncConfig.AncientBodiesBarrierCalc; + + _genesisBlockNumber = genesisBlockNumber; LoadSyncPivot(); @@ -142,7 +146,7 @@ public BlockTree( // Need to be here because it still need to run even if there are no genesis to store the null entry. LoadLowestInsertedHeader(); - ChainLevelInfo? genesisLevel = LoadLevel(0); + ChainLevelInfo? genesisLevel = LoadLevel(_genesisBlockNumber); if (genesisLevel is not null) { BlockInfo genesisBlockInfo = genesisLevel.BlockInfos[0]; @@ -167,9 +171,9 @@ public BlockTree( AttemptToFixCorruptionByMovingHeadBackwards(); } - if (_logger.IsInfo) + if (Logger.IsInfo) { - _logger.Info($"Block tree initialized, " + + Logger.Info($"Block tree initialized, " + $"last processed is {Head?.Header.ToString(BlockHeader.Format.Short) ?? "0"}, " + $"best queued is {BestSuggestedHeader?.Number.ToString() ?? "0"}, " + $"best known is {BestKnownNumber}, " + @@ -203,7 +207,7 @@ public AddBlockResult Insert(BlockHeader header, BlockTreeInsertHeaderOptions he throw new InvalidOperationException("An attempt to insert a block header without a known bloom."); } - if (header.Number == 0) + if (header.Number == _genesisBlockNumber) { throw new InvalidOperationException("Genesis block should not be inserted."); } @@ -249,8 +253,8 @@ public AddBlockResult Insert(BlockHeader header, BlockTreeInsertHeaderOptions he if (header.Number < (LowestInsertedBeaconHeader?.Number ?? long.MaxValue)) { - if (_logger.IsTrace) - _logger.Trace( + if (Logger.IsTrace) + Logger.Trace( $"LowestInsertedBeaconHeader changed, old: {LowestInsertedBeaconHeader?.Number}, new: {header?.Number}"); LowestInsertedBeaconHeader = header; } @@ -288,8 +292,8 @@ public void BulkInsertHeader(IReadOnlyList headers, throw new InvalidOperationException("Cannot accept new blocks at the moment."); } - using ArrayPoolList<(long, Bloom)> bloomToStore = new ArrayPoolList<(long, Bloom)>(headers.Count); - foreach (var header in headers) + using ArrayPoolList<(long, Bloom)> bloomToStore = new(headers.Count); + foreach (BlockHeader? header in headers) { if (header.Hash is null) { @@ -301,7 +305,7 @@ public void BulkInsertHeader(IReadOnlyList headers, throw new InvalidOperationException("An attempt to insert a block header without a known bloom."); } - if (header.Number == 0) + if (header.Number == _genesisBlockNumber) { throw new InvalidOperationException("Genesis block should not be inserted."); } @@ -325,9 +329,9 @@ public void BulkInsertHeader(IReadOnlyList headers, bool isOnMainChain = (headerOptions & BlockTreeInsertHeaderOptions.NotOnMainChain) == 0; bool beaconInsert = (headerOptions & BlockTreeInsertHeaderOptions.BeaconHeaderMetadata) != 0; - using ArrayPoolList<(long, BlockInfo)> blockInfos = new ArrayPoolList<(long, BlockInfo)>(headers.Count); + using ArrayPoolListRef<(long, BlockInfo)> blockInfos = new(headers.Count); - foreach (var header in headers) + foreach (BlockHeader? header in headers) { BlockInfo blockInfo = new(header.Hash, header.TotalDifficulty ?? 0); if (!beaconInsert) @@ -357,8 +361,8 @@ public void BulkInsertHeader(IReadOnlyList headers, if (header.Number < (LowestInsertedBeaconHeader?.Number ?? long.MaxValue)) { - if (_logger.IsTrace) - _logger.Trace( + if (Logger.IsTrace) + Logger.Trace( $"LowestInsertedBeaconHeader changed, old: {LowestInsertedBeaconHeader?.Number}, new: {header?.Number}"); LowestInsertedBeaconHeader = header; } @@ -397,7 +401,7 @@ public AddBlockResult Insert(Block block, BlockTreeInsertBlockOptions insertBloc bool skipCanAcceptNewBlocks = (insertBlockOptions & BlockTreeInsertBlockOptions.SkipCanAcceptNewBlocks) != 0; if (!CanAcceptNewBlocks) { - if (_logger.IsTrace) _logger.Trace($"Block tree in cannot accept new blocks mode. SkipCanAcceptNewBlocks: {skipCanAcceptNewBlocks}, Block {block}"); + if (Logger.IsTrace) Logger.Trace($"Block tree in cannot accept new blocks mode. SkipCanAcceptNewBlocks: {skipCanAcceptNewBlocks}, Block {block}"); } if (!CanAcceptNewBlocks && !skipCanAcceptNewBlocks) @@ -405,7 +409,7 @@ public AddBlockResult Insert(Block block, BlockTreeInsertBlockOptions insertBloc return AddBlockResult.CannotAccept; } - if (block.Number == 0) + if (block.Number == _genesisBlockNumber) { throw new InvalidOperationException("Genesis block should not be inserted."); } @@ -422,14 +426,14 @@ public AddBlockResult Insert(Block block, BlockTreeInsertBlockOptions insertBloc return AddBlockResult.Added; } - private AddBlockResult Suggest(Block? block, BlockHeader header, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) + protected virtual AddBlockResult Suggest(Block? block, BlockHeader header, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) { bool shouldProcess = options.ContainsFlag(BlockTreeSuggestOptions.ShouldProcess); bool fillBeaconBlock = options.ContainsFlag(BlockTreeSuggestOptions.FillBeaconBlock); bool setAsMain = options.ContainsFlag(BlockTreeSuggestOptions.ForceSetAsMain) || !options.ContainsFlag(BlockTreeSuggestOptions.ForceDontSetAsMain) && !shouldProcess; - if (_logger.IsTrace) _logger.Trace($"Suggesting a new block. BestSuggestedBlock {BestSuggestedBody}, BestSuggestedBlock TD {BestSuggestedBody?.TotalDifficulty}, Block TD {block?.TotalDifficulty}, Head: {Head}, Head TD: {Head?.TotalDifficulty}, Block {block?.ToString(Block.Format.FullHashAndNumber)}. ShouldProcess: {shouldProcess}, TryProcessKnownBlock: {fillBeaconBlock}, SetAsMain {setAsMain}"); + if (Logger.IsTrace) Logger.Trace($"Suggesting a new block. BestSuggestedBlock {BestSuggestedBody}, BestSuggestedBlock TD {BestSuggestedBody?.TotalDifficulty}, Block TD {block?.TotalDifficulty}, Head: {Head}, Head TD: {Head?.TotalDifficulty}, Block {block?.ToString(Block.Format.FullHashAndNumber)}. ShouldProcess: {shouldProcess}, TryProcessKnownBlock: {fillBeaconBlock}, SetAsMain {setAsMain}"); #if DEBUG /* this is just to make sure that we do not fall into this trap when creating tests */ @@ -457,7 +461,7 @@ private AddBlockResult Suggest(Block? block, BlockHeader header, BlockTreeSugges bool isKnown = IsKnownBlock(header.Number, header.Hash); if (isKnown && (BestSuggestedHeader?.Number ?? 0) >= header.Number) { - if (_logger.IsTrace) _logger.Trace($"Block {header.ToString(BlockHeader.Format.FullHashAndNumber)} already known."); + if (Logger.IsTrace) Logger.Trace($"Block {header.ToString(BlockHeader.Format.FullHashAndNumber)} already known."); return AddBlockResult.AlreadyKnown; } @@ -465,7 +469,7 @@ private AddBlockResult Suggest(Block? block, BlockHeader header, BlockTreeSugges IsKnownBeaconBlock(header.Number - 1, header.ParentHash!); if (!header.IsGenesis && !parentExists) { - if (_logger.IsTrace) _logger.Trace($"Could not find parent ({header.ParentHash}) of block {header.Hash}"); + if (Logger.IsTrace) Logger.Trace($"Could not find parent ({header.ParentHash}) of block {header.Hash}"); return AddBlockResult.UnknownParent; } @@ -504,7 +508,7 @@ private AddBlockResult Suggest(Block? block, BlockHeader header, BlockTreeSugges bool bestSuggestedImprovementSatisfied = BestSuggestedImprovementRequirementsSatisfied(header); if (bestSuggestedImprovementSatisfied) { - if (_logger.IsTrace) _logger.Trace($"New best suggested block. PreviousBestSuggestedBlock {BestSuggestedBody}, BestSuggestedBlock TD {BestSuggestedBody?.TotalDifficulty}, Block TD {block?.TotalDifficulty}, Head: {Head}, Head: {Head?.TotalDifficulty}, Block {block?.ToString(Block.Format.FullHashAndNumber)}"); + if (Logger.IsTrace) Logger.Trace($"New best suggested block. PreviousBestSuggestedBlock {BestSuggestedBody}, BestSuggestedBlock TD {BestSuggestedBody?.TotalDifficulty}, Block TD {block?.TotalDifficulty}, Head: {Head}, Head: {Head?.TotalDifficulty}, Block {block?.ToString(Block.Format.FullHashAndNumber)}"); BestSuggestedHeader = block.Header; if (block.IsPostMerge) @@ -594,11 +598,11 @@ public AddBlockResult SuggestBlock(Block block, BlockTreeSuggestOptions options bool isSearchingForBeaconBlock = (BestKnownBeaconNumber > BestKnownNumber && header.Number > BestKnownNumber); // if we're searching for beacon block we don't want to create level. We're creating it in different place with beacon metadata if (createLevelIfMissing == false || isSearchingForBeaconBlock) { - if (_logger.IsInfo) _logger.Info($"Missing block info - ignoring creation of the level in {nameof(FindHeader)} scope when head is {Head?.ToString(Block.Format.Short)}. BlockHeader {header.ToString(BlockHeader.Format.FullHashAndNumber)}, CreateLevelIfMissing: {createLevelIfMissing}. BestKnownBeaconNumber: {BestKnownBeaconNumber}, BestKnownNumber: {BestKnownNumber}"); + if (Logger.IsInfo) Logger.Info($"Missing block info - ignoring creation of the level in {nameof(FindHeader)} scope when head is {Head?.ToString(Block.Format.Short)}. BlockHeader {header.ToString(BlockHeader.Format.FullHashAndNumber)}, CreateLevelIfMissing: {createLevelIfMissing}. BestKnownBeaconNumber: {BestKnownBeaconNumber}, BestKnownNumber: {BestKnownNumber}"); } else { - if (_logger.IsInfo) _logger.Info($"Missing block info - creating level in {nameof(FindHeader)} scope when head is {Head?.ToString(Block.Format.Short)}. BlockHeader {header.ToString(BlockHeader.Format.FullHashAndNumber)}, CreateLevelIfMissing: {createLevelIfMissing}. BestKnownBeaconNumber: {BestKnownBeaconNumber}, BestKnownNumber: {BestKnownNumber}"); + if (Logger.IsInfo) Logger.Info($"Missing block info - creating level in {nameof(FindHeader)} scope when head is {Head?.ToString(Block.Format.Short)}. BlockHeader {header.ToString(BlockHeader.Format.FullHashAndNumber)}, CreateLevelIfMissing: {createLevelIfMissing}. BestKnownBeaconNumber: {BestKnownBeaconNumber}, BestKnownNumber: {BestKnownNumber}"); SetTotalDifficulty(header); blockInfo = new BlockInfo(header.Hash, header.TotalDifficulty ?? UInt256.Zero); level = UpdateOrCreateLevel(header.Number, blockInfo); @@ -683,7 +687,7 @@ static ArrayPoolList FindHeadersReversedFast(BlockTree tree, BlockH return new ArrayPoolList(1) { startHeader }; } - ArrayPoolList result = new ArrayPoolList(numberOfBlocks, numberOfBlocks); + ArrayPoolList result = new(numberOfBlocks, numberOfBlocks); BlockHeader current = startHeader; int responseIndex = reverse ? 0 : numberOfBlocks - 1; @@ -720,7 +724,7 @@ as it does not require the step of resolving number -> hash */ } } - ArrayPoolList result = new ArrayPoolList(numberOfBlocks, numberOfBlocks); + ArrayPoolList result = new(numberOfBlocks, numberOfBlocks); BlockHeader current = startHeader; int directionMultiplier = reverse ? -1 : 1; int responseIndex = 0; @@ -763,7 +767,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; @@ -783,11 +787,11 @@ public void DeleteInvalidBlock(Block invalidBlock) { if (invalidBlock.Hash is null) { - if (_logger.IsWarn) _logger.Warn($"{nameof(DeleteInvalidBlock)} call has been made for a block without a null hash."); + if (Logger.IsWarn) Logger.Warn($"{nameof(DeleteInvalidBlock)} call has been made for a block without a null hash."); return; } - if (_logger.IsDebug) _logger.Debug($"Deleting invalid block {invalidBlock.ToString(Block.Format.FullHashAndNumber)}"); + if (Logger.IsDebug) Logger.Debug($"Deleting invalid block {invalidBlock.ToString(Block.Format.FullHashAndNumber)}"); _invalidBlocks.Set(invalidBlock.Hash, invalidBlock); _badBlockStore.Insert(invalidBlock); @@ -815,13 +819,13 @@ private void DeleteBlocks(Hash256 deletePointer) BlockHeader? deleteHeader = FindHeader(deletePointer, BlockTreeLookupOptions.TotalDifficultyNotNeeded); if (deleteHeader is null) { - if (_logger.IsWarn) _logger.Warn($"Cannot delete invalid block {deletePointer} - block has not been added to the database or has already been deleted."); + if (Logger.IsWarn) Logger.Warn($"Cannot delete invalid block {deletePointer} - block has not been added to the database or has already been deleted."); return; } if (deleteHeader.Hash is null) { - if (_logger.IsWarn) _logger.Warn($"Cannot delete invalid block {deletePointer} - black has a null hash."); + if (Logger.IsWarn) Logger.Warn($"Cannot delete invalid block {deletePointer} - black has a null hash."); return; } @@ -867,7 +871,7 @@ private void DeleteBlocks(Hash256 deletePointer) _chainLevelInfoRepository.PersistLevel(currentNumber, currentLevel, batch); } - if (_logger.IsInfo) _logger.Info($"Deleting invalid block {currentHash} at level {currentNumber}"); + if (Logger.IsInfo) Logger.Info($"Deleting invalid block {currentHash} at level {currentNumber}"); _blockStore.Delete(currentNumber, currentHash); _headerStore.Delete(currentHash); @@ -891,7 +895,7 @@ private void DeleteBlocks(Hash256 deletePointer) BlockHeader? potentialChild = FindHeader(potentialChildHash, BlockTreeLookupOptions.TotalDifficultyNotNeeded); if (potentialChild is null) { - if (_logger.IsWarn) _logger.Warn($"Block with hash {potentialChildHash} has been found on chain level but its header is missing from the DB."); + if (Logger.IsWarn) Logger.Warn($"Block with hash {potentialChildHash} has been found on chain level but its header is missing from the DB."); return null; } @@ -1040,20 +1044,20 @@ private void TryUpdateSyncPivot() if (newPivotHeader is null) { - if (_logger.IsTrace) _logger.Trace("Did not update sync pivot because unable to find finalized header"); + if (Logger.IsTrace) Logger.Trace("Did not update sync pivot because unable to find finalized header"); return; } long? bestPersisted = BestPersistedState; if (bestPersisted is null) { - if (_logger.IsTrace) _logger.Trace("Did not update sync pivot because no best persisted state"); + if (Logger.IsTrace) Logger.Trace("Did not update sync pivot because no best persisted state"); return; } 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; @@ -1114,7 +1118,7 @@ public void UpdateBeaconMainChain(BlockInfo[]? blockInfos, long clearBeaconMainC get => _syncPivot; set { - if (_logger.IsTrace) _logger.Trace($"Sync pivot updated from {SyncPivot} to {value}"); + if (Logger.IsTrace) Logger.Trace($"Sync pivot updated from {SyncPivot} to {value}"); RlpStream pivotData = new(38); //1 byte (prefix) + 4 bytes (long) + 1 byte (prefix) + 32 bytes (Keccak) pivotData.Encode(value.BlockNumber); @@ -1128,7 +1132,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 @@ -1146,7 +1150,7 @@ header is not null // null is never better [Todo(Improve.MissingFunctionality, "Recalculate bloom storage on reorg.")] private void MoveToMain(Block block, BatchWrite batch, bool wasProcessed, bool forceUpdateHeadBlock) { - if (_logger.IsTrace) _logger.Trace($"Moving {block.ToString(Block.Format.Short)} to main"); + if (Logger.IsTrace) Logger.Trace($"Moving {block.ToString(Block.Format.Short)} to main"); if (block.Hash is null) { throw new InvalidOperationException("An attempt to move to main a block with hash not set."); @@ -1178,7 +1182,7 @@ private void MoveToMain(Block block, BatchWrite batch, bool wasProcessed, bool f if (forceUpdateHeadBlock || block.IsGenesis || HeadImprovementRequirementsSatisfied(block.Header)) { - if (block.Number == 0) + if (block.Number == _genesisBlockNumber) { Genesis = block.Header; } @@ -1194,27 +1198,27 @@ private void MoveToMain(Block block, BatchWrite batch, bool wasProcessed, bool f } } - if (_logger.IsTrace) _logger.Trace($"Block added to main {block}, block TD {block.TotalDifficulty}"); + if (Logger.IsTrace) Logger.Trace($"Block added to main {block}, block TD {block.TotalDifficulty}"); BlockAddedToMain?.Invoke(this, new BlockReplacementEventArgs(block, previous)); - if (_logger.IsTrace) _logger.Trace($"Block {block.ToString(Block.Format.Short)}, TD: {block.TotalDifficulty} added to main chain"); + if (Logger.IsTrace) Logger.Trace($"Block {block.ToString(Block.Format.Short)}, TD: {block.TotalDifficulty} added to main chain"); } - private bool HeadImprovementRequirementsSatisfied(BlockHeader header) + 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; } @@ -1222,11 +1226,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; @@ -1271,25 +1275,25 @@ private void UpdateDeletePointer(Hash256? hash) } else { - if (_logger.IsDebug) _logger.Debug($"Deleting an invalid block or its descendant {hash}"); + if (Logger.IsDebug) Logger.Debug($"Deleting an invalid block or its descendant {hash}"); _blockInfoDb.Set(DeletePointerAddressInDb, hash.Bytes); } } public void UpdateHeadBlock(Hash256 blockHash) { - if (_logger.IsError) _logger.Error($"Block tree override detected - updating head block to {blockHash}."); + if (Logger.IsError) Logger.Error($"Block tree override detected - updating head block to {blockHash}."); _blockInfoDb.Set(HeadAddressInDb, blockHash.Bytes); BlockHeader? header = FindHeader(blockHash, BlockTreeLookupOptions.None); if (header is not null) { - if (_logger.IsError) _logger.Error($"Block tree override detected - updating head block to {blockHash}."); + if (Logger.IsError) Logger.Error($"Block tree override detected - updating head block to {blockHash}."); _blockInfoDb.Set(HeadAddressInDb, blockHash.Bytes); BestPersistedState = header.Number; } else { - if (_logger.IsError) _logger.Error($"Block tree override detected - cannot find block: {blockHash}."); + if (Logger.IsError) Logger.Error($"Block tree override detected - cannot find block: {blockHash}."); } } @@ -1340,11 +1344,11 @@ private ChainLevelInfo UpdateOrCreateLevel(long number, BlockInfo blockInfo, boo return level; } - private void UpdateOrCreateLevel(IReadOnlyList<(long number, BlockInfo blockInfo)> blockInfos, bool setAsMain = false) + private void UpdateOrCreateLevel(in ArrayPoolListRef<(long number, BlockInfo blockInfo)> blockInfos, bool setAsMain = false) { using BatchWrite? batch = _chainLevelInfoRepository.StartBatch(); - using ArrayPoolList blockNumbers = blockInfos.Select(b => b.number).ToPooledList(blockInfos.Count); + using ArrayPoolListRef blockNumbers = blockInfos.Select(b => b.number); // Yes, this is measurably faster using IOwnedReadOnlyList levels = _chainLevelInfoRepository.MultiLoadLevel(blockNumbers); @@ -1383,23 +1387,13 @@ private void UpdateOrCreateLevel(IReadOnlyList<(long number, BlockInfo blockInfo private (BlockInfo? Info, ChainLevelInfo? Level) LoadInfo(long number, Hash256 blockHash, bool forceLoad) { ChainLevelInfo chainLevelInfo = LoadLevel(number, forceLoad); - if (chainLevelInfo is null) - { - return (null, null); - } - - return (chainLevelInfo.FindBlockInfo(blockHash), chainLevelInfo); + return chainLevelInfo is null ? (null, null) : (chainLevelInfo.FindBlockInfo(blockHash), chainLevelInfo); } - private ChainLevelInfo? LoadLevel(long number, bool forceLoad = true) - { - if (number > Math.Max(BestKnownNumber, BestKnownBeaconNumber) && !forceLoad) - { - return null; - } - - return _chainLevelInfoRepository.LoadLevel(number); - } + private ChainLevelInfo? LoadLevel(long number, bool forceLoad = true) => + number > Math.Max(BestKnownNumber, BestKnownBeaconNumber) && !forceLoad + ? null + : _chainLevelInfoRepository.LoadLevel(number); /// /// To make cache useful even when we handle sync requests @@ -1408,7 +1402,7 @@ private void UpdateOrCreateLevel(IReadOnlyList<(long number, BlockInfo blockInfo /// private bool ShouldCache(long number) { - return number == 0L || Head is null || number >= Head.Number - BlockStore.CacheSize; + return number == _genesisBlockNumber || Head is null || number >= Head.Number - BlockStore.CacheSize; } public ChainLevelInfo? FindLevel(long number) @@ -1469,11 +1463,11 @@ private bool ShouldCache(long number) bool isSearchingForBeaconBlock = BestKnownBeaconNumber > BestKnownNumber && block.Number > BestKnownNumber; // if we're searching for beacon block we don't want to create level. We're creating it in different place with beacon metadata if (createLevelIfMissing == false || isSearchingForBeaconBlock) { - if (_logger.IsInfo) _logger.Info($"Missing block info - ignoring creation of the level in {nameof(FindBlock)} scope when head is {Head?.ToString(Block.Format.Short)}. BlockHeader {block.ToString(Block.Format.FullHashAndNumber)}, CreateLevelIfMissing: {createLevelIfMissing}. BestKnownBeaconNumber: {BestKnownBeaconNumber}, BestKnownNumber: {BestKnownNumber}"); + if (Logger.IsInfo) Logger.Info($"Missing block info - ignoring creation of the level in {nameof(FindBlock)} scope when head is {Head?.ToString(Block.Format.Short)}. BlockHeader {block.ToString(Block.Format.FullHashAndNumber)}, CreateLevelIfMissing: {createLevelIfMissing}. BestKnownBeaconNumber: {BestKnownBeaconNumber}, BestKnownNumber: {BestKnownNumber}"); } else { - if (_logger.IsInfo) _logger.Info($"Missing block info - creating level in {nameof(FindBlock)} scope when head is {Head?.ToString(Block.Format.Short)}. BlockHeader {block.ToString(Block.Format.FullHashAndNumber)}, CreateLevelIfMissing: {createLevelIfMissing}. BestKnownBeaconNumber: {BestKnownBeaconNumber}, BestKnownNumber: {BestKnownNumber}"); + if (Logger.IsInfo) Logger.Info($"Missing block info - creating level in {nameof(FindBlock)} scope when head is {Head?.ToString(Block.Format.Short)}. BlockHeader {block.ToString(Block.Format.FullHashAndNumber)}, CreateLevelIfMissing: {createLevelIfMissing}. BestKnownBeaconNumber: {BestKnownBeaconNumber}, BestKnownNumber: {BestKnownNumber}"); SetTotalDifficulty(block.Header); blockInfo = new BlockInfo(block.Hash, block.TotalDifficulty ?? UInt256.Zero); level = UpdateOrCreateLevel(block.Number, blockInfo); @@ -1504,7 +1498,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) @@ -1532,14 +1526,14 @@ private void SetTotalDifficulty(BlockHeader header) if (header.IsGenesis) { header.TotalDifficulty = header.Difficulty; - if (_logger.IsTrace) _logger.Trace($"Genesis total difficulty is {header.TotalDifficulty}"); + if (Logger.IsTrace) Logger.Trace($"Genesis total difficulty is {header.TotalDifficulty}"); return; } if (IsTotalDifficultyAlwaysZero()) { header.TotalDifficulty = 0; - if (_logger.IsTrace) _logger.Trace($"Block {header} has zero total difficulty"); + if (Logger.IsTrace) Logger.Trace($"Block {header} has zero total difficulty"); return; } BlockHeader GetParentHeader(BlockHeader current) => @@ -1559,8 +1553,8 @@ void SetTotalDifficultyDeep(BlockHeader current) if (level is null || blockInfo is null || blockInfo.TotalDifficulty == 0) { stack.Push(current); - if (_logger.IsTrace) - _logger.Trace( + if (Logger.IsTrace) + Logger.Trace( $"Calculating total difficulty for {current.ToString(BlockHeader.Format.Short)}"); current = GetParentHeader(current); } @@ -1583,8 +1577,8 @@ void SetTotalDifficultyDeep(BlockHeader current) child.TotalDifficulty = current.TotalDifficulty + child.Difficulty; BlockInfo blockInfo = new(child.Hash, child.TotalDifficulty.Value); UpdateOrCreateLevel(child.Number, blockInfo); - if (_logger.IsTrace) - _logger.Trace($"Calculated total difficulty for {child} is {child.TotalDifficulty}"); + if (Logger.IsTrace) + Logger.Trace($"Calculated total difficulty for {child} is {child.TotalDifficulty}"); current = child; } } @@ -1594,8 +1588,8 @@ void SetTotalDifficultyDeep(BlockHeader current) return; } - if (_logger.IsTrace) - _logger.Trace($"Calculating total difficulty for {header.ToString(BlockHeader.Format.Short)}"); + if (Logger.IsTrace) + Logger.Trace($"Calculating total difficulty for {header.ToString(BlockHeader.Format.Short)}"); BlockHeader parentHeader = GetParentHeader(header); @@ -1607,7 +1601,7 @@ void SetTotalDifficultyDeep(BlockHeader current) header.TotalDifficulty = parentHeader.TotalDifficulty + header.Difficulty; - if (_logger.IsTrace) _logger.Trace($"Calculated total difficulty for {header} is {header.TotalDifficulty}"); + if (Logger.IsTrace) Logger.Trace($"Calculated total difficulty for {header} is {header.TotalDifficulty}"); } public event EventHandler? BlockAddedToMain; diff --git a/src/Nethermind/Nethermind.Blockchain/BlockTreeOverlay.cs b/src/Nethermind/Nethermind.Blockchain/BlockTreeOverlay.cs index 4e2a519c14f5..d776bc7895fa 100644 --- a/src/Nethermind/Nethermind.Blockchain/BlockTreeOverlay.cs +++ b/src/Nethermind/Nethermind.Blockchain/BlockTreeOverlay.cs @@ -9,7 +9,6 @@ using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; -using Nethermind.State.Repositories; namespace Nethermind.Blockchain; diff --git a/src/Nethermind/Nethermind.Blockchain/BlockhashCache.cs b/src/Nethermind/Nethermind.Blockchain/BlockhashCache.cs new file mode 100644 index 000000000000..2147e771fd9f --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain/BlockhashCache.cs @@ -0,0 +1,271 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Blockchain.Headers; +using Nethermind.Core; +using Nethermind.Core.Caching; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Core.Threading; +using Nethermind.Logging; + +namespace Nethermind.Blockchain; + +public class BlockhashCache(IHeaderFinder headerFinder, ILogManager logManager) : IDisposable, IBlockhashCache +{ + private readonly ILogger _logger = logManager.GetClassLogger(); + private readonly ConcurrentDictionary _blocks = new(); + private readonly LruCache _flatCache = new(32, nameof(BlockhashCache)); + private readonly Lock _lock = new(); + public const int MaxDepth = BlockhashProvider.MaxDepth; + private long _minBlock = int.MaxValue; + private Task _pruningTask = Task.CompletedTask; + + public Hash256? GetHash(BlockHeader headBlock, int depth) => + depth == 0 ? headBlock.Hash + : depth == 1 ? headBlock.ParentHash + : depth > MaxDepth ? null + : _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) + { + hashes = null; + if (depth > MaxDepth) return null; + bool alwaysAdd = depth == MaxDepth; + using ArrayPoolListRef<(CacheNode Node, bool NeedToAdd)> blocks = new(depth + 1); + Hash256 currentHash = blockHeader.Hash!; + CacheNode currentNode = null; + bool needToAddAny = false; + int skipped = 0; + for (int i = 0; i <= depth && !cancellationToken.IsCancellationRequested; i++) + { + bool needToAdd = false; + if (currentNode is null) + { + if (!_blocks.TryGetValue(currentHash, out currentNode)) + { + BlockHeader? currentHeader = i == 0 ? blockHeader : headerFinder.Get(currentHash, blockHeader.Number - i); + if (currentHeader is null) + { + break; + } + + currentNode = new CacheNode(currentHeader); + needToAddAny |= needToAdd = currentHeader.Hash is not null; + } + } + + if (alwaysAdd || blocks.Count != 0 || needToAdd || currentNode.Parent is null) + { + blocks.Add((currentNode, needToAdd)); + } + else + { + skipped++; + } + + if (i != depth) + { + currentHash = currentNode.ParentHash; + currentNode = currentNode.Parent; + } + } + + if (needToAddAny && !cancellationToken.IsCancellationRequested) + { + (CacheNode Node, bool NeedToAdd) parentNode = blocks[^1]; + InterlockedEx.Min(ref _minBlock, parentNode.Node.Number); + for (int i = blocks.Count - 2; i >= 0 && !cancellationToken.IsCancellationRequested; i--) + { + if (parentNode.NeedToAdd) + { + parentNode.Node = _blocks.GetOrAdd(parentNode.Node.Hash, parentNode.Node); + } + + (CacheNode Node, bool NeedToAdd) current = blocks[i]; + current.Node.Parent = parentNode.Node; + parentNode = current; + } + + if (parentNode.NeedToAdd) + { + _blocks.TryAdd(parentNode.Node.Hash, parentNode.Node); + } + } + + int ancestorCount = blocks.Count - 1; + if (ancestorCount == FlatCacheLength(blockHeader)) + { + hashes = new Hash256[ancestorCount]; + for (int i = 1; i < blocks.Count; i++) + { + hashes[i - 1] = blocks[i].Node.Hash; + } + + if (blockHeader.Hash is not null) + { + _flatCache.Set(blockHeader.Hash, hashes); + } + } + + int index = depth - skipped; + return index < 0 ? currentNode // if index <0 then we skipped everything and got it from cache + : blocks.Count > index + ? blocks[index].Node + : null; + } + + private static int FlatCacheLength(BlockHeader blockHeader) => (int)Math.Min(MaxDepth, blockHeader.Number); + + public Task Prefetch(BlockHeader blockHeader, CancellationToken cancellationToken = default) + { + return Task.Run(() => + { + Hash256[]? hashes = null; + try + { + if (!cancellationToken.IsCancellationRequested) + { + 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.ParentHash; + Array.Copy(parentHashes, 0, hashes, 1, length - 1); + if (!emptyHash) + { + _flatCache.Set(blockHeader.Hash, hashes); + } + } + else + { + Load(blockHeader, MaxDepth, out hashes, cancellationToken); + } + } + } + + PruneInBackground(blockHeader); + } + catch (Exception e) + { + if (_logger.IsWarn) _logger.Warn($"Background fetch failed for block {blockHeader.Number}: {e.Message}"); + } + + return hashes; + }); + } + + private void PruneInBackground(BlockHeader blockHeader) + { + if (ShouldPrune()) + { + lock (_lock) + { + if (ShouldPrune()) + { + _pruningTask = Task.Run(() => + { + try + { + PruneBefore(blockHeader.Number - MaxDepth * 2); + } + catch (Exception e) + { + if (_logger.IsWarn) _logger.Warn($"Background pruning failed for block {blockHeader.Number}: {e.Message}"); + } + }); + } + } + } + + bool ShouldPrune() => _minBlock + MaxDepth * 4 < blockHeader.Number && _pruningTask.IsCompleted; + } + + public int PruneBefore(long blockNumber) + { + int removed = 0; + long minBlockNumber = long.MaxValue; + + Interlocked.Exchange(ref _minBlock, blockNumber); + + foreach (KeyValuePair kvp in _blocks) + { + if (kvp.Value.Parent?.Number < blockNumber) + { + kvp.Value.Parent = null; + } + + if (kvp.Value.Number < blockNumber) + { + if (_blocks.TryRemove(kvp.Key, out CacheNode node)) + { + _flatCache.Delete(node.Hash); + removed++; + + } + } + else + { + minBlockNumber = Math.Min(minBlockNumber, kvp.Value.Number); + } + } + + InterlockedEx.Min(ref _minBlock, minBlockNumber); + + return removed; + } + + public bool Contains(Hash256 blockHash) => _blocks.ContainsKey(blockHash); + + public void Clear() + { + _blocks.Clear(); + } + + public void Dispose() + { + Clear(); + } + + public Stats GetStats() + { + Dictionary parents = new(); + int nodes = 0; + foreach (CacheNode node in _blocks.Values) + { + parents.GetOrAdd(node, static _ => 0); + if (node.Parent is not null) + { + parents.GetOrAdd(node.Parent, static _ => 0)++; + } + + nodes++; + } + + return new Stats(nodes, parents.Values.Count(p => p == 0), _flatCache.Count); + } + + /// + /// Represents a cached block node in a linked-list structure + /// + private class CacheNode(BlockHeader blockHeader, CacheNode? parent = null) + { + public Hash256 Hash { get; } = blockHeader.Hash!; + public long Number { get; } = blockHeader.Number; + public Hash256 ParentHash { get; } = blockHeader.ParentHash!; + public CacheNode? Parent { get; set; } = parent; + } + + public record struct Stats(int Nodes, int Roots, int FlatCache); +} diff --git a/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs b/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs index 52bee65baf33..200341f05cdb 100644 --- a/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs +++ b/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs @@ -3,8 +3,9 @@ using System; using System.IO; +using System.Threading; +using System.Threading.Tasks; using Nethermind.Blockchain.Blocks; -using Nethermind.Blockchain.Find; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; @@ -14,58 +15,67 @@ namespace Nethermind.Blockchain { - public class BlockhashProvider : IBlockhashProvider + public class BlockhashProvider( + IBlockhashCache blockhashCache, + IWorldState worldState, + ILogManager? logManager) + : IBlockhashProvider { - private static readonly int _maxDepth = 256; - private readonly IBlockFinder _blockTree; - private readonly ISpecProvider _specProvider; - private readonly IBlockhashStore _blockhashStore; - private readonly ILogger _logger; + public const int MaxDepth = 256; + private readonly IBlockhashStore _blockhashStore = new BlockhashStore(worldState); + private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + private Hash256[]? _hashes; + private long _prefetchVersion; - public BlockhashProvider(IBlockFinder blockTree, ISpecProvider specProvider, IWorldState worldState, ILogManager? logManager) + public Hash256? GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec spec) { - _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); - _specProvider = specProvider; - _blockhashStore = new BlockhashStore(specProvider, worldState); - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - } - - public Hash256? GetBlockhash(BlockHeader currentBlock, long number) - => GetBlockhash(currentBlock, number, _specProvider.GetSpec(currentBlock)); - - public Hash256? GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec? spec) - { - if (spec.IsBlockHashInStateAvailable) + if (number < 0) { - return _blockhashStore.GetBlockHashFromState(currentBlock, number); + return ReturnOutOfBounds(currentBlock, number); } - long current = currentBlock.Number; - if (number >= current || number < current - Math.Min(current, _maxDepth)) + if (spec.IsBlockHashInStateAvailable) { - return null; + return _blockhashStore.GetBlockHashFromState(currentBlock, number, spec); } - BlockHeader header = _blockTree.FindParentHeader(currentBlock, BlockTreeLookupOptions.TotalDifficultyNotNeeded) ?? - throw new InvalidDataException("Parent header cannot be found when executing BLOCKHASH operation"); + long depth = currentBlock.Number - number; + Hash256[]? hashes = Volatile.Read(ref _hashes); - for (var i = 0; i < _maxDepth; i++) + return depth switch { - if (number == header.Number) - { - if (_logger.IsTrace) _logger.Trace($"BLOCKHASH opcode returning {header.Number},{header.Hash} for {currentBlock.Number} -> {number}"); - return header.Hash; - } + <= 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) + { + long prefetchVersion = Interlocked.Increment(ref _prefetchVersion); + Volatile.Write(ref _hashes, null); + Hash256[]? hashes = await blockhashCache.Prefetch(currentBlock, token); - header = _blockTree.FindParentHeader(header, BlockTreeLookupOptions.TotalDifficultyNotNeeded); - if (header is null) + // This leverages that branch processing is single threaded + // If the cancellation was requested it means block processing finished before prefetching is done + // This means we don't want to set hashes, as next block might already be prefetching + // This allows us to avoid await on Prefetch in BranchProcessor + lock (_blockhashStore) + { + if (!token.IsCancellationRequested && prefetchVersion == Interlocked.Read(ref _prefetchVersion)) { - throw new InvalidDataException("Parent header cannot be found when executing BLOCKHASH operation"); + Volatile.Write(ref _hashes, hashes); } } - - if (_logger.IsTrace) _logger.Trace($"BLOCKHASH opcode returning null for {currentBlock.Number} -> {number}"); - return null; } } } diff --git a/src/Nethermind/Nethermind.Blockchain/Blocks/BlockStore.cs b/src/Nethermind/Nethermind.Blockchain/Blocks/BlockStore.cs index 3cb168ba391f..00bd9f65da8e 100644 --- a/src/Nethermind/Nethermind.Blockchain/Blocks/BlockStore.cs +++ b/src/Nethermind/Nethermind.Blockchain/Blocks/BlockStore.cs @@ -7,15 +7,14 @@ using Nethermind.Core; using Nethermind.Core.Caching; using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; using Nethermind.Db; using Nethermind.Serialization.Rlp; namespace Nethermind.Blockchain.Blocks; -public class BlockStore([KeyFilter(DbNames.Blocks)] IDb blockDb) : IBlockStore +public class BlockStore([KeyFilter(DbNames.Blocks)] IDb blockDb, IHeaderDecoder headerDecoder = null) : IBlockStore { - private readonly BlockDecoder _blockDecoder = new(); + private readonly BlockDecoder _blockDecoder = new(headerDecoder ?? new HeaderDecoder()); public const int CacheSize = 128 + 32; private readonly ClockCache @@ -52,12 +51,6 @@ public void Insert(Block block, WriteFlags writeFlags = WriteFlags.None) blockDb.Set(block.Number, block.Hash, newRlp.AsSpan(), writeFlags); } - private static void GetBlockNumPrefixedKey(long blockNumber, Hash256 blockHash, Span output) - { - blockNumber.WriteBigEndian(output); - blockHash!.Bytes.CopyTo(output[8..]); - } - public void Delete(long blockNumber, Hash256 blockHash) { _blockCache.Delete(blockHash); @@ -84,12 +77,12 @@ public void Delete(long blockNumber, Hash256 blockHash) public ReceiptRecoveryBlock? GetReceiptRecoveryBlock(long blockNumber, Hash256 blockHash) { Span keyWithBlockNumber = stackalloc byte[40]; - GetBlockNumPrefixedKey(blockNumber, blockHash, keyWithBlockNumber); + KeyValueStoreExtensions.GetBlockNumPrefixedKey(blockNumber, blockHash, keyWithBlockNumber); MemoryManager? memoryOwner = blockDb.GetOwnedMemory(keyWithBlockNumber); memoryOwner ??= blockDb.GetOwnedMemory(blockHash.Bytes); - return BlockDecoder.DecodeToReceiptRecoveryBlock(memoryOwner, memoryOwner?.Memory ?? Memory.Empty, RlpBehaviors.None); + return _blockDecoder.DecodeToReceiptRecoveryBlock(memoryOwner, memoryOwner?.Memory ?? Memory.Empty, RlpBehaviors.None); } public void Cache(Block block) diff --git a/src/Nethermind/Nethermind.Blockchain/Blocks/BlockhashStore.cs b/src/Nethermind/Nethermind.Blockchain/Blocks/BlockhashStore.cs index 00d7a44cda82..4db2ff986576 100644 --- a/src/Nethermind/Nethermind.Blockchain/Blocks/BlockhashStore.cs +++ b/src/Nethermind/Nethermind.Blockchain/Blocks/BlockhashStore.cs @@ -14,14 +14,10 @@ [assembly: InternalsVisibleTo("Nethermind.Merge.Plugin.Test")] namespace Nethermind.Blockchain.Blocks; -public class BlockhashStore(ISpecProvider specProvider, IWorldState worldState) - : IBlockhashStore +public class BlockhashStore(IWorldState worldState) : IBlockhashStore { private static readonly byte[] EmptyBytes = [0]; - public void ApplyBlockhashStateChanges(BlockHeader blockHeader) - => ApplyBlockhashStateChanges(blockHeader, specProvider.GetSpec(blockHeader)); - public void ApplyBlockhashStateChanges(BlockHeader blockHeader, IReleaseSpec spec) { if (!spec.IsEip2935Enabled || blockHeader.IsGenesis || blockHeader.ParentHash is null) return; @@ -30,22 +26,19 @@ public void ApplyBlockhashStateChanges(BlockHeader blockHeader, IReleaseSpec spe if (!worldState.IsContract(eip2935Account)) return; Hash256 parentBlockHash = blockHeader.ParentHash; - var parentBlockIndex = new UInt256((ulong)((blockHeader.Number - 1) % Eip2935Constants.RingBufferSize)); + UInt256 parentBlockIndex = new UInt256((ulong)((blockHeader.Number - 1) % spec.Eip2935RingBufferSize)); StorageCell blockHashStoreCell = new(eip2935Account, parentBlockIndex); worldState.Set(blockHashStoreCell, parentBlockHash!.Bytes.WithoutLeadingZeros().ToArray()); } - public Hash256? GetBlockHashFromState(BlockHeader currentHeader, long requiredBlockNumber) - => GetBlockHashFromState(currentHeader, requiredBlockNumber, specProvider.GetSpec(currentHeader)); - - public Hash256? GetBlockHashFromState(BlockHeader currentHeader, long requiredBlockNumber, IReleaseSpec? spec) + public Hash256? GetBlockHashFromState(BlockHeader currentHeader, long requiredBlockNumber, IReleaseSpec spec) { if (requiredBlockNumber >= currentHeader.Number || - requiredBlockNumber + Eip2935Constants.RingBufferSize < currentHeader.Number) + requiredBlockNumber + spec.Eip2935RingBufferSize < currentHeader.Number) { return null; } - var blockIndex = new UInt256((ulong)(requiredBlockNumber % Eip2935Constants.RingBufferSize)); + UInt256 blockIndex = new UInt256((ulong)(requiredBlockNumber % spec.Eip2935RingBufferSize)); Address? eip2935Account = spec.Eip2935ContractAddress ?? Eip2935Constants.BlockHashHistoryAddress; StorageCell blockHashStoreCell = new(eip2935Account, blockIndex); ReadOnlySpan data = worldState.Get(blockHashStoreCell); diff --git a/src/Nethermind/Nethermind.Blockchain/Blocks/IBlockhashStore.cs b/src/Nethermind/Nethermind.Blockchain/Blocks/IBlockhashStore.cs index 77cc6b214f5a..c26ad9b438e6 100644 --- a/src/Nethermind/Nethermind.Blockchain/Blocks/IBlockhashStore.cs +++ b/src/Nethermind/Nethermind.Blockchain/Blocks/IBlockhashStore.cs @@ -9,8 +9,6 @@ namespace Nethermind.Blockchain.Blocks; public interface IBlockhashStore { - public void ApplyBlockhashStateChanges(BlockHeader blockHeader); public void ApplyBlockhashStateChanges(BlockHeader blockHeader, IReleaseSpec spec); - public Hash256? GetBlockHashFromState(BlockHeader currentBlockHeader, long requiredBlockNumber); - public Hash256? GetBlockHashFromState(BlockHeader currentBlockHeader, long requiredBlockNumber, IReleaseSpec? spec); + public Hash256? GetBlockHashFromState(BlockHeader currentBlockHeader, long requiredBlockNumber, IReleaseSpec spec); } diff --git a/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs b/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs index 003b9046c6cb..cdbd1857d4fb 100644 --- a/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs @@ -12,7 +12,6 @@ using Nethermind.Evm; using Nethermind.Evm.CodeAnalysis; using Nethermind.Evm.Precompiles; -using Nethermind.Evm.State; using Nethermind.State; namespace Nethermind.Blockchain; @@ -20,13 +19,13 @@ namespace Nethermind.Blockchain; public class CachedCodeInfoRepository( IPrecompileProvider precompileProvider, ICodeInfoRepository baseCodeInfoRepository, - ConcurrentDictionary? precompileCache) : ICodeInfoRepository + ConcurrentDictionary>? precompileCache) : ICodeInfoRepository { - private readonly FrozenDictionary _cachedPrecompile = precompileCache is null + private readonly FrozenDictionary _cachedPrecompile = precompileCache is null ? precompileProvider.GetPrecompiles() : precompileProvider.GetPrecompiles().ToFrozenDictionary(kvp => kvp.Key, kvp => CreateCachedPrecompile(kvp, precompileCache)); - public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, + public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) { if (vmSpec.IsPrecompile(codeSource) && _cachedPrecompile.TryGetValue(codeSource, out var cachedCodeInfo)) @@ -58,28 +57,30 @@ public bool TryGetDelegation(Address address, IReleaseSpec spec, return baseCodeInfoRepository.TryGetDelegation(address, spec, out delegatedAddress); } - private static PrecompileInfo CreateCachedPrecompile( - in KeyValuePair originalPrecompile, - ConcurrentDictionary cache) => - new PrecompileInfo(new CachedPrecompile(originalPrecompile.Key.Value, originalPrecompile.Value.Precompile!, cache)); + private static CodeInfo CreateCachedPrecompile( + in KeyValuePair originalPrecompile, + ConcurrentDictionary> cache) + { + IPrecompile precompile = originalPrecompile.Value.Precompile!; + + return !precompile.SupportsCaching + ? originalPrecompile.Value + : new CodeInfo(new CachedPrecompile(originalPrecompile.Key.Value, precompile, cache)); + } private class CachedPrecompile( Address address, IPrecompile precompile, - ConcurrentDictionary cache) : IPrecompile + 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); - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { PreBlockCaches.PrecompileCacheKey key = new(address, inputData); - if (!cache.TryGetValue(key, out (byte[], bool) result)) + if (!cache.TryGetValue(key, out Result result)) { result = precompile.Run(inputData, releaseSpec); // we need to rebuild the key with data copy as the data can be changed by VM processing diff --git a/src/Nethermind/Nethermind.Blockchain/ChainHeadReadOnlyStateProvider.cs b/src/Nethermind/Nethermind.Blockchain/ChainHeadReadOnlyStateProvider.cs index fdd1f7aa309e..460e20d68b9a 100644 --- a/src/Nethermind/Nethermind.Blockchain/ChainHeadReadOnlyStateProvider.cs +++ b/src/Nethermind/Nethermind.Blockchain/ChainHeadReadOnlyStateProvider.cs @@ -1,10 +1,8 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Nethermind.Blockchain.Find; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.State; namespace Nethermind.Blockchain diff --git a/src/Nethermind/Nethermind.Blockchain/Contracts/Contract.ConstantContract.cs b/src/Nethermind/Nethermind.Blockchain/Contracts/Contract.ConstantContract.cs index 79f83bd127b9..2441780301f0 100644 --- a/src/Nethermind/Nethermind.Blockchain/Contracts/Contract.ConstantContract.cs +++ b/src/Nethermind/Nethermind.Blockchain/Contracts/Contract.ConstantContract.cs @@ -4,7 +4,6 @@ using System; using Nethermind.Abi; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Evm.TransactionProcessing; namespace Nethermind.Blockchain.Contracts diff --git a/src/Nethermind/Nethermind.Blockchain/Contracts/LogEntryAddressAndTopicsMatchTemplateEqualityComparer.cs b/src/Nethermind/Nethermind.Blockchain/Contracts/LogEntryAddressAndTopicsMatchTemplateEqualityComparer.cs index 5d38f19412b6..11529d9e5c95 100644 --- a/src/Nethermind/Nethermind.Blockchain/Contracts/LogEntryAddressAndTopicsMatchTemplateEqualityComparer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Contracts/LogEntryAddressAndTopicsMatchTemplateEqualityComparer.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using System.Linq; using Nethermind.Core; diff --git a/src/Nethermind/Nethermind.Blockchain/Data/JSTracers/prestateTracer_legacy.js b/src/Nethermind/Nethermind.Blockchain/Data/JSTracers/prestateTracer_legacy.js index 3d293fd14ddc..24a92d6ab9ec 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/EthereumPrecompileProvider.cs b/src/Nethermind/Nethermind.Blockchain/EthereumPrecompileProvider.cs index d793c9a65ba3..03fa5cff876d 100644 --- a/src/Nethermind/Nethermind.Blockchain/EthereumPrecompileProvider.cs +++ b/src/Nethermind/Nethermind.Blockchain/EthereumPrecompileProvider.cs @@ -13,9 +13,9 @@ namespace Nethermind.Blockchain; public class EthereumPrecompileProvider() : IPrecompileProvider { - private static FrozenDictionary Precompiles + private static FrozenDictionary Precompiles { - get => new Dictionary + get => new Dictionary { [EcRecoverPrecompile.Address] = new(EcRecoverPrecompile.Instance), [Sha256Precompile.Address] = new(Sha256Precompile.Instance), @@ -45,7 +45,7 @@ private static FrozenDictionary Precompiles }.ToFrozenDictionary(); } - public FrozenDictionary GetPrecompiles() + public FrozenDictionary GetPrecompiles() { return Precompiles; } diff --git a/src/Nethermind/Nethermind.Blockchain/FinalizeEventArgs.cs b/src/Nethermind/Nethermind.Blockchain/FinalizeEventArgs.cs index 6eda9aca738c..8003ccd63164 100644 --- a/src/Nethermind/Nethermind.Blockchain/FinalizeEventArgs.cs +++ b/src/Nethermind/Nethermind.Blockchain/FinalizeEventArgs.cs @@ -7,19 +7,13 @@ namespace Nethermind.Blockchain { - public class FinalizeEventArgs : EventArgs + public class FinalizeEventArgs(BlockHeader finalizingBlock, IReadOnlyList finalizedBlocks) + : EventArgs { public FinalizeEventArgs(BlockHeader finalizingBlock, params BlockHeader[] finalizedBlocks) : this(finalizingBlock, (IReadOnlyList)finalizedBlocks) { } - public FinalizeEventArgs(BlockHeader finalizingBlock, IReadOnlyList finalizedBlocks) - { - FinalizingBlock = finalizingBlock; - FinalizedBlocks = finalizedBlocks; - } - - public BlockHeader FinalizingBlock { get; } - public IReadOnlyList FinalizedBlocks { get; } - + public BlockHeader FinalizingBlock { get; } = finalizingBlock; + public IReadOnlyList FinalizedBlocks { get; } = finalizedBlocks; } } diff --git a/src/Nethermind/Nethermind.Blockchain/Find/BlockParameter.cs b/src/Nethermind/Nethermind.Blockchain/Find/BlockParameter.cs index cf6136e7d67b..6b675581d1bd 100644 --- a/src/Nethermind/Nethermind.Blockchain/Find/BlockParameter.cs +++ b/src/Nethermind/Nethermind.Blockchain/Find/BlockParameter.cs @@ -4,11 +4,13 @@ using System; using System.Buffers.Binary; using System.Buffers.Text; +using System.Diagnostics; +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; @@ -43,6 +45,7 @@ public BlockParameter(BlockParameterType type) public BlockParameter(long number) { + RequireCanonical = true; Type = BlockParameterType.BlockNumber; BlockNumber = number; } @@ -56,7 +59,12 @@ public BlockParameter(Hash256 blockHash, bool requireCanonical = false) RequireCanonical = requireCanonical; } - public override string ToString() => $"{Type}, {BlockNumber?.ToString() ?? BlockHash?.ToString()}"; + public override string ToString() => Type switch + { + BlockParameterType.BlockNumber => BlockNumber?.ToString() ?? "unknown", + BlockParameterType.BlockHash => BlockHash?.ToString() ?? "unknown", + _ => Type.ToString().ToLowerInvariant() + }; public bool Equals(BlockParameter? other) { @@ -116,205 +124,190 @@ public override void Write(Utf8JsonWriter writer, BlockParameter value, JsonSeri return; } - switch (value.Type) + writer.WriteStringValue(value.Type switch { - case BlockParameterType.Earliest: - writer.WriteStringValue("earliest"u8); - break; - case BlockParameterType.Latest: - writer.WriteStringValue("latest"u8); - break; - case BlockParameterType.Pending: - writer.WriteStringValue("pending"u8); - break; - case BlockParameterType.Finalized: - writer.WriteStringValue("finalized"u8); - break; - case BlockParameterType.Safe: - writer.WriteStringValue("safe"u8); - break; - case BlockParameterType.BlockNumber: - throw new InvalidOperationException("block number should be handled separately"); - case BlockParameterType.BlockHash: - throw new InvalidOperationException("block hash should be handled separately"); - default: - throw new InvalidOperationException("unknown block parameter type"); - } + BlockParameterType.Earliest => "earliest"u8, + BlockParameterType.Latest => "latest"u8, + BlockParameterType.Pending => "pending"u8, + BlockParameterType.Finalized => "finalized"u8, + BlockParameterType.Safe => "safe"u8, + BlockParameterType.BlockNumber => throw new InvalidOperationException("block number should be handled separately"), + BlockParameterType.BlockHash => throw new InvalidOperationException("block hash should be handled separately"), + _ => throw new InvalidOperationException("unknown block parameter type") + }); } [SkipLocalsInit] public override BlockParameter? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - JsonTokenType tokenType = reader.TokenType; - if (tokenType == JsonTokenType.String && (reader.HasValueSequence ? reader.ValueSequence.Length : reader.ValueSpan.Length) > 66) + return reader.TokenType switch { - return JsonSerializer.Deserialize(reader.GetString(), options); - } - if (tokenType == JsonTokenType.StartObject) + 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()), + _ => throw new FormatException("unknown block parameter type") + }; + } + + private BlockParameter ReadObjectFormat(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + bool requireCanonical = false; + Hash256? blockHash = null; + BlockParameter? blockNumberParam = null; + + while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) { - bool requireCanonical = false; - bool readEndObject = false; - Hash256 blockHash = null; - for (int i = 0; i < 2; i++) + if (reader.TokenType == JsonTokenType.PropertyName) { - reader.Read(); - if (reader.TokenType == JsonTokenType.EndObject) + switch (reader) { - readEndObject = true; - break; - } - - if (reader.ValueTextEquals("requireCanonical"u8)) - { - reader.Read(); - requireCanonical = reader.GetBoolean(); - } - else if (reader.ValueTextEquals("blockHash"u8)) - { - blockHash = JsonSerializer.Deserialize(ref reader, options); + case var _ when reader.ValueTextEquals("requireCanonical"u8): + reader.Read(); + requireCanonical = reader.GetBoolean(); + break; + case var _ when reader.ValueTextEquals("blockHash"u8): + reader.Read(); + blockHash = JsonSerializer.Deserialize(ref reader, options); + break; + case var _ when reader.ValueTextEquals("blockNumber"u8): + reader.Read(); + blockNumberParam = Read(ref reader, typeToConvert, options); + break; } } - - BlockParameter parameter = new(blockHash, requireCanonical); - - if ((!readEndObject && !reader.Read()) || reader.TokenType != JsonTokenType.EndObject) - { - ThrowInvalidFormatting(); - } - - return parameter; } - if (tokenType == JsonTokenType.Null) + return (blockHash, blockNumberParam) switch { - return BlockParameter.Latest; - } - if (tokenType == JsonTokenType.Number) - { - return new BlockParameter(reader.GetInt64()); - } + (blockHash: not null, blockNumberParam: _) => new BlockParameter(blockHash, requireCanonical), + (blockHash: null, blockNumberParam: not null) => blockNumberParam, + _ => throw new FormatException("unknown block parameter type") + }; + } - if (tokenType != JsonTokenType.String) + private static BlockParameter ReadStringFormat(ReadOnlySpan span) + { + int length = span.Length; + // Creates a jmp table based on length + switch (length) { - ThrowInvalidFormatting(); + 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; } - if (reader.ValueTextEquals(ReadOnlySpan.Empty) || reader.ValueTextEquals("latest"u8)) - { - return BlockParameter.Latest; - } - else if (reader.ValueTextEquals("earliest"u8)) - { - return BlockParameter.Earliest; - } - else if (reader.ValueTextEquals("pending"u8)) - { - return BlockParameter.Pending; - } - else if (reader.ValueTextEquals("finalized"u8)) - { - return BlockParameter.Finalized; - } - else if (reader.ValueTextEquals("safe"u8)) + // Unknown tag or 0x quantity etc + return ReadStringFormatOther(span); + } + + [SkipLocalsInit] + private static BlockParameter ReadStringFormatValueSequence(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + if (reader.ValueSequence.Length > 66) { - return BlockParameter.Safe; + return ReadStringComplex(ref reader, options); } Span span = stackalloc byte[66]; int hexLength = reader.CopyString(span); span = span[..hexLength]; - long value = 0; + 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)) { span = span[2..]; + + // 64 hex chars = 32 bytes = Hash256 if (span.Length == 64) { byte[] bytes = Bytes.FromUtf8HexString(span); return new BlockParameter(new Hash256(bytes)); } - int oddMod = span.Length % 2; - int length = (span.Length >> 1) + oddMod; - - Span output = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref value, 1)); - - Bytes.FromUtf8HexString(span, output[(sizeof(long) - length)..]); - - if (BitConverter.IsLittleEndian) - { - value = BinaryPrimitives.ReverseEndianness(value); - } - + // Parse as block number + long value = ParseHexNumber(span); return new BlockParameter(value); } - if (Utf8Parser.TryParse(span, out value, out _)) + // Try decimal format (if not strict) + if (!EthereumJsonSerializer.StrictHexFormat && Utf8Parser.TryParse(span, out long decimalValue, out _)) { - return new BlockParameter(value); - } - - // Lower case the span string - for (int i = 0; i < span.Length; i++) - { - int ch = span[i]; - if (ch >= 'A' && ch <= 'Z') - { - span[i] = (byte)(ch + 'a' - 'A'); - } - } - - if (span.SequenceEqual("latest"u8)) - { - return BlockParameter.Latest; - } - else if (span.SequenceEqual("earliest"u8)) - { - return BlockParameter.Earliest; - } - else if (span.SequenceEqual("pending"u8)) - { - return BlockParameter.Pending; - } - else if (span.SequenceEqual("finalized"u8)) - { - return BlockParameter.Finalized; - } - else if (span.SequenceEqual("safe"u8)) - { - return BlockParameter.Safe; + return new BlockParameter(decimalValue); } ThrowInvalidFormatting(); return null; } - private static void ThrowInvalidFormatting() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static long ParseHexNumber(ReadOnlySpan span) { - throw new InvalidOperationException("unknown block parameter type"); + int oddMod = span.Length % 2; + int length = (span.Length >> 1) + oddMod; + long value = 0; + + Span output = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref value, 1)); + Bytes.FromUtf8HexString(span, output[(sizeof(long) - length)..]); + + return BitConverter.IsLittleEndian switch + { + true => BinaryPrimitives.ReverseEndianness(value), + _ => value + }; } + [DoesNotReturn, StackTraceHidden] + private static void ThrowInvalidFormatting() + => throw new FormatException("unknown block parameter type"); + public static BlockParameter GetBlockParameter(string? value) { - switch (value) + return value switch { - case null: - case { } empty when string.IsNullOrWhiteSpace(empty): - case { } latest when latest.Equals("latest", StringComparison.OrdinalIgnoreCase): - return BlockParameter.Latest; - case { } earliest when earliest.Equals("earliest", StringComparison.OrdinalIgnoreCase): - return BlockParameter.Earliest; - case { } pending when pending.Equals("pending", StringComparison.OrdinalIgnoreCase): - return BlockParameter.Pending; - case { } finalized when finalized.Equals("finalized", StringComparison.OrdinalIgnoreCase): - return BlockParameter.Finalized; - case { } safe when safe.Equals("safe", StringComparison.OrdinalIgnoreCase): - return BlockParameter.Safe; - case { Length: 66 } hash when hash.StartsWith("0x"): - return new BlockParameter(new Hash256(hash)); - default: - return new BlockParameter(LongConverter.FromString(value)); - } + null => BlockParameter.Latest, + not null when string.IsNullOrWhiteSpace(value) => BlockParameter.Latest, + not null when value.Equals("latest", StringComparison.OrdinalIgnoreCase) => BlockParameter.Latest, + not null when value.Equals("earliest", StringComparison.OrdinalIgnoreCase) => BlockParameter.Earliest, + not null when value.Equals("pending", StringComparison.OrdinalIgnoreCase) => BlockParameter.Pending, + not null when value.Equals("finalized", StringComparison.OrdinalIgnoreCase) => BlockParameter.Finalized, + not null when value.Equals("safe", StringComparison.OrdinalIgnoreCase) => BlockParameter.Safe, + { Length: 66 } when value.StartsWith("0x") => new BlockParameter(new Hash256(value)), + _ => new BlockParameter(LongConverter.FromString(value)) + }; } } } diff --git a/src/Nethermind/Nethermind.Blockchain/FullPruning/CompositePruningTrigger.cs b/src/Nethermind/Nethermind.Blockchain/FullPruning/CompositePruningTrigger.cs index 49f20bb7e2e3..8ae718f97eec 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 94a9d7454dbb..82cb1e9b06b6 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.ToArray(), _writeFlags); - Interlocked.Increment(ref _persistedNodes); - - // log message every 1 mln nodes - if (_persistedNodes % Million == 0) - { - LogProgress("In Progress"); - } + _concurrentWriteBatcher.Set(storage, path, node.Keccak, node.FullRlp.Span, _writeFlags); + _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 01ee4f9cde93..50ef9b111967 100644 --- a/src/Nethermind/Nethermind.Blockchain/FullPruning/FullPruner.cs +++ b/src/Nethermind/Nethermind.Blockchain/FullPruning/FullPruner.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using Nethermind.Config; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Core.Events; using Nethermind.Core.Extensions; using Nethermind.Db; @@ -180,7 +179,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 +219,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 +264,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 +295,7 @@ private Task CopyTrie(IPruningContext pruning, Hash256 stateRoot, CancellationTo } private ICopyTreeVisitor CopyTree( - Hash256 stateRoot, + BlockHeader? baseBlock, INodeStorage targetNodeStorage, WriteFlags writeFlags, VisitingOptions visitingOptions, @@ -304,7 +303,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/GenesisBuilder.cs b/src/Nethermind/Nethermind.Blockchain/GenesisBuilder.cs index b401c73a22f8..07b3716f98de 100644 --- a/src/Nethermind/Nethermind.Blockchain/GenesisBuilder.cs +++ b/src/Nethermind/Nethermind.Blockchain/GenesisBuilder.cs @@ -31,9 +31,6 @@ public Block Build() Block genesis = chainSpec.Genesis; Preallocate(genesis); - // we no longer need the allocations - 0.5MB RAM, 9000 objects for mainnet - chainSpec.Allocations = null; - foreach (IGenesisPostProcessor postProcessor in postProcessors) { postProcessor.PostProcess(genesis); @@ -49,6 +46,9 @@ public Block Build() genesis.Header.Hash = genesis.Header.CalculateHash(); + // we no longer need the allocations - 0.5MB RAM, 9000 objects for mainnet + chainSpec.Allocations = null; + return genesis; } diff --git a/src/Nethermind/Nethermind.Blockchain/Headers/HeaderStore.cs b/src/Nethermind/Nethermind.Blockchain/Headers/HeaderStore.cs index 425d56d19559..3acf9b0ae735 100644 --- a/src/Nethermind/Nethermind.Blockchain/Headers/HeaderStore.cs +++ b/src/Nethermind/Nethermind.Blockchain/Headers/HeaderStore.cs @@ -15,40 +15,35 @@ namespace Nethermind.Blockchain.Headers; -public class HeaderStore : IHeaderStore +public class HeaderStore( + [KeyFilter(DbNames.Headers)] IDb headerDb, + [KeyFilter(DbNames.BlockNumbers)] IDb blockNumberDb, + IHeaderDecoder? decoder = null) + : IHeaderStore { // SyncProgressResolver MaxLookupBack is 256, add 16 wiggle room public const int CacheSize = 256 + 16; - private readonly IDb _headerDb; - private readonly IDb _blockNumberDb; - private readonly HeaderDecoder _headerDecoder = new(); - private readonly ClockCache _headerCache = - new(CacheSize); - - public HeaderStore([KeyFilter(DbNames.Headers)] IDb headerDb, [KeyFilter(DbNames.BlockNumbers)] IDb blockNumberDb) - { - _headerDb = headerDb; - _blockNumberDb = blockNumberDb; - } + private readonly IHeaderDecoder _headerDecoder = decoder ?? new HeaderDecoder(); + private readonly ClockCache _headerCache = new(CacheSize); public void Insert(BlockHeader header) { using NettyRlpStream newRlp = _headerDecoder.EncodeToNewNettyStream(header); - _headerDb.Set(header.Number, header.Hash, newRlp.AsSpan()); + headerDb.Set(header.Number, header.Hash!, newRlp.AsSpan()); InsertBlockNumber(header.Hash, header.Number); } public void BulkInsert(IReadOnlyList headers) { - using IWriteBatch headerWriteBatch = _headerDb.StartWriteBatch(); - using IWriteBatch blockNumberWriteBatch = _blockNumberDb.StartWriteBatch(); + using IWriteBatch headerWriteBatch = headerDb.StartWriteBatch(); + using IWriteBatch blockNumberWriteBatch = blockNumberDb.StartWriteBatch(); Span blockNumberSpan = stackalloc byte[8]; - foreach (var header in headers) + foreach (BlockHeader header in headers) { using NettyRlpStream newRlp = _headerDecoder.EncodeToNewNettyStream(header); - headerWriteBatch.Set(header.Number, header.Hash, newRlp.AsSpan()); + headerWriteBatch.Set(header.Number, header.Hash!, newRlp.AsSpan()); header.Number.WriteBigEndian(blockNumberSpan); blockNumberWriteBatch.Set(header.Hash, blockNumberSpan); @@ -62,9 +57,9 @@ public void BulkInsert(IReadOnlyList headers) BlockHeader? header = null; if (blockNumber is not null) { - header = _headerDb.Get(blockNumber.Value, blockHash, _headerDecoder, _headerCache, shouldCache: shouldCache); + header = headerDb.Get(blockNumber.Value, blockHash, _headerDecoder, _headerCache, shouldCache: shouldCache); } - return header ?? _headerDb.Get(blockHash, _headerDecoder, _headerCache, shouldCache: shouldCache); + return header ?? headerDb.Get(blockHash, _headerDecoder, _headerCache, shouldCache: shouldCache); } public void Cache(BlockHeader header) @@ -75,9 +70,9 @@ public void Cache(BlockHeader header) public void Delete(Hash256 blockHash) { long? blockNumber = GetBlockNumberFromBlockNumberDb(blockHash); - if (blockNumber is not null) _headerDb.Delete(blockNumber.Value, blockHash); - _blockNumberDb.Delete(blockHash); - _headerDb.Delete(blockHash); + if (blockNumber is not null) headerDb.Delete(blockNumber.Value, blockHash); + blockNumberDb.Delete(blockHash); + headerDb.Delete(blockHash); _headerCache.Delete(blockHash); } @@ -85,7 +80,7 @@ public void InsertBlockNumber(Hash256 blockHash, long blockNumber) { Span blockNumberSpan = stackalloc byte[8]; blockNumber.WriteBigEndian(blockNumberSpan); - _blockNumberDb.Set(blockHash, blockNumberSpan); + blockNumberDb.Set(blockHash, blockNumberSpan); } public long? GetBlockNumber(Hash256 blockHash) @@ -99,7 +94,7 @@ public void InsertBlockNumber(Hash256 blockHash, long blockNumber) private long? GetBlockNumberFromBlockNumberDb(Hash256 blockHash) { - Span numberSpan = _blockNumberDb.GetSpan(blockHash); + Span numberSpan = blockNumberDb.GetSpan(blockHash); if (numberSpan.IsNullOrEmpty()) return null; try { @@ -112,7 +107,9 @@ public void InsertBlockNumber(Hash256 blockHash, long blockNumber) } finally { - _blockNumberDb.DangerousReleaseMemory(numberSpan); + blockNumberDb.DangerousReleaseMemory(numberSpan); } } + + BlockHeader? IHeaderFinder.Get(Hash256 blockHash, long? blockNumber) => Get(blockHash, true, blockNumber); } diff --git a/src/Nethermind/Nethermind.Blockchain/Headers/IHeaderFinder.cs b/src/Nethermind/Nethermind.Blockchain/Headers/IHeaderFinder.cs new file mode 100644 index 000000000000..cb964c6ebdc3 --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain/Headers/IHeaderFinder.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Crypto; + +namespace Nethermind.Blockchain.Headers; + +public interface IHeaderFinder +{ + BlockHeader? Get(Hash256 blockHash, long? blockNumber = null); +} diff --git a/src/Nethermind/Nethermind.Blockchain/Headers/IHeaderStore.cs b/src/Nethermind/Nethermind.Blockchain/Headers/IHeaderStore.cs index dc56e2d91167..4af92175e845 100644 --- a/src/Nethermind/Nethermind.Blockchain/Headers/IHeaderStore.cs +++ b/src/Nethermind/Nethermind.Blockchain/Headers/IHeaderStore.cs @@ -1,14 +1,13 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using Nethermind.Core; using Nethermind.Core.Crypto; namespace Nethermind.Blockchain.Headers; -public interface IHeaderStore +public interface IHeaderStore : IHeaderFinder { void Insert(BlockHeader header); void BulkInsert(IReadOnlyList headers); diff --git a/src/Nethermind/Nethermind.Blockchain/IBlockTree.cs b/src/Nethermind/Nethermind.Blockchain/IBlockTree.cs index 22bd63cbdedd..f3531b6f8f91 100644 --- a/src/Nethermind/Nethermind.Blockchain/IBlockTree.cs +++ b/src/Nethermind/Nethermind.Blockchain/IBlockTree.cs @@ -10,7 +10,6 @@ using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; -using Nethermind.State.Repositories; namespace Nethermind.Blockchain { @@ -203,9 +202,9 @@ AddBlockResult Insert(Block block, BlockTreeInsertBlockOptions insertBlockOption public readonly struct ForkChoiceUpdateEventArgs(Block? head, long safe, long finalized) { - public readonly Block? Head => head; - public readonly long Safe => safe; - public readonly long Finalized => finalized; + public Block? Head => head; + public long Safe => safe; + public long Finalized => finalized; } bool IsProcessingBlock { get; set; } } diff --git a/src/Nethermind/Nethermind.Blockchain/IBlockhashCache.cs b/src/Nethermind/Nethermind.Blockchain/IBlockhashCache.cs new file mode 100644 index 000000000000..5019c07908d8 --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain/IBlockhashCache.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Crypto; + +namespace Nethermind.Blockchain; + +public interface IBlockhashCache +{ + Hash256? GetHash(BlockHeader headBlock, int depth); + Task Prefetch(BlockHeader blockHeader, CancellationToken cancellationToken); +} diff --git a/src/Nethermind/Nethermind.Blockchain/IReadOnlyTxProcessingScope.cs b/src/Nethermind/Nethermind.Blockchain/IReadOnlyTxProcessingScope.cs index 5bd5bec4ae8b..210f7a3ca01b 100644 --- a/src/Nethermind/Nethermind.Blockchain/IReadOnlyTxProcessingScope.cs +++ b/src/Nethermind/Nethermind.Blockchain/IReadOnlyTxProcessingScope.cs @@ -4,7 +4,6 @@ using System; using Nethermind.Evm.State; using Nethermind.Evm.TransactionProcessing; -using Nethermind.State; namespace Nethermind.Blockchain; diff --git a/src/Nethermind/Nethermind.Blockchain/InvalidBlockException.cs b/src/Nethermind/Nethermind.Blockchain/InvalidBlockException.cs index 7468d5448a56..80cd68a1b280 100644 --- a/src/Nethermind/Nethermind.Blockchain/InvalidBlockException.cs +++ b/src/Nethermind/Nethermind.Blockchain/InvalidBlockException.cs @@ -6,13 +6,11 @@ namespace Nethermind.Blockchain; -public class InvalidBlockException : BlockchainException +public class InvalidBlockException(BlockHeader block, string message, Exception? innerException = null) + : BlockchainException(message, innerException) { public InvalidBlockException(Block block, string message, Exception? innerException = null) - : base(message, innerException) => InvalidBlock = block.Header; + : this(block.Header, message, innerException) { } - public InvalidBlockException(BlockHeader block, string message, Exception? innerException = null) - : base(message, innerException) => InvalidBlock = block; - - public BlockHeader InvalidBlock { get; } + public BlockHeader InvalidBlock { get; } = block; } diff --git a/src/Nethermind/Nethermind.Blockchain/InvalidTransactionException.cs b/src/Nethermind/Nethermind.Blockchain/InvalidTransactionException.cs new file mode 100644 index 000000000000..afa267ad792d --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain/InvalidTransactionException.cs @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; +using Nethermind.Evm.TransactionProcessing; + +namespace Nethermind.Blockchain; + +public class InvalidTransactionException : InvalidBlockException +{ + public InvalidTransactionException(BlockHeader header, string message, TransactionResult result, Exception? innerException = null) + : base(header, message, innerException) => Reason = result; + + public TransactionResult Reason; +} diff --git a/src/Nethermind/Nethermind.Blockchain/LogTraceDumper.cs b/src/Nethermind/Nethermind.Blockchain/LogTraceDumper.cs index 5fbc96ea7fa6..61b3d14cd417 100644 --- a/src/Nethermind/Nethermind.Blockchain/LogTraceDumper.cs +++ b/src/Nethermind/Nethermind.Blockchain/LogTraceDumper.cs @@ -61,7 +61,7 @@ public static void LogDiagnosticTrace( { fileName = $"receipts_{blockHash}_{state}.txt"; using FileStream diagnosticFile = GetFileStream(fileName); - IReadOnlyList receipts = receiptsTracer.TxReceipts; + TxReceipt[] receipts = receiptsTracer.TxReceipts.ToArray(); EthereumJsonSerializer.SerializeToStream(diagnosticFile, receipts, true); if (logger.IsInfo) logger.Info($"Created a Receipts trace of {logCondition} block {blockHash} in file {diagnosticFile.Name}"); diff --git a/src/Nethermind/Nethermind.Blockchain/Metrics.cs b/src/Nethermind/Nethermind.Blockchain/Metrics.cs index 91864c136fb3..648eac7402c8 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/ReadOnlyBlockTree.cs b/src/Nethermind/Nethermind.Blockchain/ReadOnlyBlockTree.cs index b6635141370e..867c44dc267b 100644 --- a/src/Nethermind/Nethermind.Blockchain/ReadOnlyBlockTree.cs +++ b/src/Nethermind/Nethermind.Blockchain/ReadOnlyBlockTree.cs @@ -10,7 +10,6 @@ using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; -using Nethermind.State.Repositories; namespace Nethermind.Blockchain { diff --git a/src/Nethermind/Nethermind.Blockchain/ReceiptCanonicalityMonitor.cs b/src/Nethermind/Nethermind.Blockchain/ReceiptCanonicalityMonitor.cs index 4d6b55283d95..eea17931dabe 100644 --- a/src/Nethermind/Nethermind.Blockchain/ReceiptCanonicalityMonitor.cs +++ b/src/Nethermind/Nethermind.Blockchain/ReceiptCanonicalityMonitor.cs @@ -25,7 +25,7 @@ public ReceiptCanonicalityMonitor(IReceiptStorage? receiptStorage, ILogManager? { _receiptStorage = receiptStorage ?? throw new ArgumentNullException(nameof(receiptStorage)); _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - _receiptStorage.ReceiptsInserted += OnBlockAddedToMain; + _receiptStorage.NewCanonicalReceipts += OnBlockAddedToMain; } private void OnBlockAddedToMain(object sender, BlockReplacementEventArgs e) @@ -55,7 +55,7 @@ private void TriggerReceiptInsertedEvent(Block newBlock, Block? previousBlock) public void Dispose() { - _receiptStorage.ReceiptsInserted -= OnBlockAddedToMain; + _receiptStorage.NewCanonicalReceipts -= OnBlockAddedToMain; } } } diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/IReceiptStorage.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/IReceiptStorage.cs index 65633a1fa7ad..dd7caa7366b7 100644 --- a/src/Nethermind/Nethermind.Blockchain/Receipts/IReceiptStorage.cs +++ b/src/Nethermind/Nethermind.Blockchain/Receipts/IReceiptStorage.cs @@ -19,8 +19,16 @@ public interface IReceiptStorage : IReceiptFinder void RemoveReceipts(Block block); /// - /// Receipts for a block are inserted + /// Receipts for canonical chain changed. /// - event EventHandler ReceiptsInserted; + event EventHandler? NewCanonicalReceipts; + + /// + /// Receipts for any block are inserted. + /// + /// + /// This is invoked for both canonical and non-canonical blocks. + /// + event EventHandler? ReceiptsInserted; } } diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/InMemoryReceiptStorage.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/InMemoryReceiptStorage.cs index 1e3eec074a49..a8824cb66a4c 100644 --- a/src/Nethermind/Nethermind.Blockchain/Receipts/InMemoryReceiptStorage.cs +++ b/src/Nethermind/Nethermind.Blockchain/Receipts/InMemoryReceiptStorage.cs @@ -18,7 +18,8 @@ public class InMemoryReceiptStorage : IReceiptStorage private readonly ConcurrentDictionary _transactions = new(); #pragma warning disable CS0067 - public event EventHandler ReceiptsInserted; + public event EventHandler? NewCanonicalReceipts; + public event EventHandler? ReceiptsInserted; #pragma warning restore CS0067 public InMemoryReceiptStorage(bool allowReceiptIterator = true, IBlockTree? blockTree = null) @@ -32,7 +33,7 @@ public InMemoryReceiptStorage(bool allowReceiptIterator = true, IBlockTree? bloc private void BlockTree_BlockAddedToMain(object? sender, BlockReplacementEventArgs e) { EnsureCanonical(e.Block); - ReceiptsInserted?.Invoke(this, e); + NewCanonicalReceipts?.Invoke(this, e); } public Hash256 FindBlockHash(Hash256 txHash) @@ -73,6 +74,8 @@ public void Insert(Block block, TxReceipt[] txReceipts, IReleaseSpec spec, bool { EnsureCanonical(block); } + + ReceiptsInserted?.Invoke(this, new(block.Header, txReceipts)); } public bool HasBlock(long blockNumber, Hash256 hash) diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/NullReceiptStorage.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/NullReceiptStorage.cs index 1c06f8b3d21e..7ed7bb5637ae 100644 --- a/src/Nethermind/Nethermind.Blockchain/Receipts/NullReceiptStorage.cs +++ b/src/Nethermind/Nethermind.Blockchain/Receipts/NullReceiptStorage.cs @@ -13,7 +13,8 @@ public class NullReceiptStorage : IReceiptStorage public static NullReceiptStorage Instance { get; } = new(); #pragma warning disable CS0067 - public event EventHandler ReceiptsInserted; + public event EventHandler? NewCanonicalReceipts; + public event EventHandler? ReceiptsInserted; #pragma warning restore CS0067 public Hash256? FindBlockHash(Hash256 hash) => null; diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs index 5d5715278943..2db303f9dd8b 100644 --- a/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs +++ b/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs @@ -38,7 +38,8 @@ public class PersistentReceiptStorage : IReceiptStorage private const int CacheSize = 64; private readonly LruCache _receiptsCache = new(CacheSize, CacheSize, "receipts"); - public event EventHandler ReceiptsInserted; + public event EventHandler? NewCanonicalReceipts; + public event EventHandler? ReceiptsInserted; public PersistentReceiptStorage( IColumnsDb receiptsDb, @@ -73,9 +74,9 @@ public PersistentReceiptStorage( private void BlockTreeOnBlockAddedToMain(object? sender, BlockReplacementEventArgs e) { EnsureCanonical(e.Block); - ReceiptsInserted?.Invoke(this, e); + NewCanonicalReceipts?.Invoke(this, e); - // Dont block main loop + // Don't block the main loop Task.Run(() => { Block newMain = e.Block; @@ -222,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; @@ -244,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) @@ -288,6 +294,8 @@ public void Insert(Block block, TxReceipt[]? txReceipts, IReleaseSpec spec, bool { EnsureCanonical(block, lastBlockNumber); } + + ReceiptsInserted?.Invoke(this, new(block.Header, txReceipts)); } public long MigratedBlockNumber diff --git a/src/Nethermind/Nethermind.Blockchain/ReorgDepthFinalizedStateProvider.cs b/src/Nethermind/Nethermind.Blockchain/ReorgDepthFinalizedStateProvider.cs new file mode 100644 index 000000000000..fda29ae09107 --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain/ReorgDepthFinalizedStateProvider.cs @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +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 => Math.Max(0, blockTree.BestKnownNumber - Reorganization.MaxDepth); + + public Hash256? GetFinalizedStateRootAt(long blockNumber) + { + if (FinalizedBlockNumber < blockNumber) return null; + return blockTree.FindHeader(blockNumber, BlockTreeLookupOptions.RequireCanonical)?.StateRoot; + } +} diff --git a/src/Nethermind/Nethermind.Blockchain/Spec/ChainHeadSpecProvider.cs b/src/Nethermind/Nethermind.Blockchain/Spec/ChainHeadSpecProvider.cs index b9ddc9f9ae63..7530d72461b5 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 9fe0a072ecf6..2857cf0890a0 100644 --- a/src/Nethermind/Nethermind.Blockchain/SpecificBlockReadOnlyStateProvider.cs +++ b/src/Nethermind/Nethermind.Blockchain/SpecificBlockReadOnlyStateProvider.cs @@ -7,7 +7,6 @@ using Nethermind.Core.Crypto; using Nethermind.Evm.State; using Nethermind.State; -using Nethermind.Trie; namespace Nethermind.Blockchain { @@ -31,18 +30,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 dca9b5979433..acd70b687a47 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; } @@ -138,6 +132,9 @@ public interface ISyncConfig : IConfig [ConfigItem(Description = "_Technical._ Whether to enable snap serving. WARNING: Very slow on hash db layout. Default is to enable on halfpath layout.", DefaultValue = "null", HiddenFromDocs = true)] bool? SnapServingEnabled { get; set; } + [ConfigItem(Description = "The maximum depth (in blocks) for serving snap sync requests. Higher values allow serving requests for older blocks, useful for networks with fast block times like Arbitrum.", DefaultValue = "128")] + int SnapServingMaxDepth { get; set; } + [ConfigItem(Description = "_Technical._ MultiSyncModeSelector sync mode timer loop interval. Used for testing.", DefaultValue = "1000", HiddenFromDocs = true)] int MultiSyncModeSelectorLoopTimerMs { get; set; } diff --git a/src/Nethermind/Nethermind.Blockchain/Synchronization/SyncConfig.cs b/src/Nethermind/Nethermind.Blockchain/Synchronization/SyncConfig.cs index 096d8f57444c..3060c83855a2 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; } @@ -65,6 +66,7 @@ public string? PivotHash public int ExitOnSyncedWaitTimeSec { get; set; } = 60; public int MallocTrimIntervalSec { get; set; } = 300; public bool? SnapServingEnabled { get; set; } = null; + public int SnapServingMaxDepth { get; set; } = 128; public int MultiSyncModeSelectorLoopTimerMs { get; set; } = 1000; public int SyncDispatcherEmptyRequestDelayMs { get; set; } = 10; public int SyncDispatcherAllocateTimeoutMs { get; set; } = 1000; diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/BlockReceiptsTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/BlockReceiptsTracer.cs index ab4549b0aca3..30140372e4e8 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/BlockReceiptsTracer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/BlockReceiptsTracer.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Runtime.InteropServices; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Evm; @@ -199,7 +199,7 @@ public void ReportFees(UInt256 fees, UInt256 burntFees) protected int _currentIndex { get; private set; } private readonly List _txReceipts = new(); protected Transaction? CurrentTx; - public IReadOnlyList TxReceipts => _txReceipts; + public ReadOnlySpan TxReceipts => CollectionsMarshal.AsSpan(_txReceipts); public TxReceipt LastReceipt => _txReceipts[^1]; public bool IsTracingRewards => _otherTracer.IsTracingRewards; @@ -216,7 +216,7 @@ public void Restore(int snapshot) _txReceipts.RemoveAt(_txReceipts.Count - 1); } - Block.Header.GasUsed = _txReceipts.Count > 0 ? _txReceipts.Last().GasUsedTotal : 0; + Block.Header.GasUsed = _txReceipts.Count > 0 ? _txReceipts[^1].GasUsedTotal : 0; } public void ReportReward(Address author, string rewardType, UInt256 rewardValue) => diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/EstimateGasTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/EstimateGasTracer.cs index 2a3b74b2d2ae..1d6f49def139 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,15 @@ public void ReportActionError(EvmExceptionType exceptionType, long gasLeft) public override void ReportOperationError(EvmExceptionType error) { - OutOfGas |= error == EvmExceptionType.OutOfGas || error == EvmExceptionType.Revert; + if (_currentNestingLevel == 0) + { + OutOfGas |= error == EvmExceptionType.OutOfGas; + + if (error == EvmExceptionType.Revert) + { + 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 777682391ac7..8f245f699f2a 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/JavaScript/Engine.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/Engine.cs index 10f71db7cb87..7657fc533c11 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/Engine.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/Engine.cs @@ -14,7 +14,6 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Evm; -using Nethermind.Evm.Precompiles; using Nethermind.Logging; #pragma warning disable CS0162 // Unreachable code detected diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs index 296e39acbb4f..2460334a9b60 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs @@ -11,6 +11,7 @@ using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; +using Nethermind.Evm.CodeAnalysis; namespace Nethermind.Blockchain.Tracing.GethStyle.Custom.JavaScript; @@ -108,7 +109,7 @@ public override void ReportAction(long gas, UInt256 value, Address from, Address public override void StartOperation(int pc, Instruction opcode, long gas, in ExecutionEnvironment env, int codeSection = 0, int functionDepth = 0) { - _log.pc = pc + env.CodeInfo.PcOffset(); + _log.pc = pc + (env.CodeInfo is EofCodeInfo eof ? eof.PcOffset() : 0); _log.op = new Log.Opcode(opcode); _log.gas = gas; _log.depth = env.GetGethTraceDepth(); diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs index 565e15a80a36..4c69cdc72712 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs @@ -57,16 +57,20 @@ public NativeCallTracer( public override GethLikeTxTrace BuildResult() { GethLikeTxTrace result = base.BuildResult(); - NativeCallTracerCallFrame firstCallFrame = _callStack[0]; Debug.Assert(_callStack.Count == 1, $"Unexpected frames on call stack, expected only master frame, found {_callStack.Count} frames."); - _callStack.RemoveAt(0); - _disposables.Add(firstCallFrame); + if (_callStack.Count is not 0) + { + NativeCallTracerCallFrame firstCallFrame = _callStack[0]; + _callStack.RemoveAt(0); + _disposables.Add(firstCallFrame); - result.TxHash = _txHash; - result.CustomTracerResult = new GethLikeCustomTrace { Value = firstCallFrame }; + result.TxHash = _txHash; + result.CustomTracerResult = new GethLikeCustomTrace { Value = firstCallFrame }; + } + result.TxHash = _txHash; _resultBuilt = true; return result; 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 b75fe892ce84..1b84577e64e0 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.Blockchain/Tracing/GethStyle/GethLikeTxTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethLikeTxTracer.cs index 8f2838518cf6..a73acf74b898 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethLikeTxTracer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethLikeTxTracer.cs @@ -74,7 +74,9 @@ protected GethLikeTxTracer(GethTraceOptions options) : base(options) { } public override void StartOperation(int pc, Instruction opcode, long gas, in ExecutionEnvironment env, int codeSection = 0, int functionDepth = 0) { if (CurrentTraceEntry is not null) + { AddTraceEntry(CurrentTraceEntry); + } CurrentTraceEntry = CreateTraceEntry(opcode); CurrentTraceEntry.Depth = env.GetGethTraceDepth(); @@ -86,27 +88,35 @@ public override void StartOperation(int pc, Instruction opcode, long gas, in Exe _gasCostAlreadySetForCurrentOp = false; } - public override void ReportOperationError(EvmExceptionType error) => CurrentTraceEntry.Error = GetErrorDescription(error); + public override void ReportOperationError(EvmExceptionType error) + { + if (CurrentTraceEntry is not null) + CurrentTraceEntry.Error = GetErrorDescription(error); + } public override void ReportOperationRemainingGas(long gas) { - if (!_gasCostAlreadySetForCurrentOp) + if (!_gasCostAlreadySetForCurrentOp && CurrentTraceEntry is not null) { CurrentTraceEntry.GasCost = CurrentTraceEntry.Gas - gas; _gasCostAlreadySetForCurrentOp = true; } } - public override void SetOperationMemorySize(ulong newSize) => CurrentTraceEntry.UpdateMemorySize(newSize); + public override void SetOperationMemorySize(ulong newSize) + { + CurrentTraceEntry?.UpdateMemorySize(newSize); + } public override void SetOperationStack(TraceStack stack) { - CurrentTraceEntry.Stack = stack.ToHexWordList(); + if (CurrentTraceEntry is not null) + CurrentTraceEntry.Stack = stack.ToHexWordList(); } public override void SetOperationMemory(TraceMemory memoryTrace) { - if (IsTracingFullMemory) + if (IsTracingFullMemory && CurrentTraceEntry is not null) CurrentTraceEntry.Memory = memoryTrace.ToHexWordList(); } diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethTraceOptions.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethTraceOptions.cs index 6af4bd4fea7e..66605682db0b 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethTraceOptions.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethTraceOptions.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Text.Json; -using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Evm; @@ -13,33 +12,26 @@ namespace Nethermind.Blockchain.Tracing.GethStyle; public record GethTraceOptions { - [JsonPropertyName("disableMemory")] [Obsolete("Use EnableMemory instead.")] public bool DisableMemory { get => !EnableMemory; init => EnableMemory = !value; } - [JsonPropertyName("disableStorage")] public bool DisableStorage { get; init; } - [JsonPropertyName("enableMemory")] public bool EnableMemory { get; init; } - [JsonPropertyName("disableStack")] public bool DisableStack { get; init; } - [JsonPropertyName("timeout")] public string Timeout { get; init; } - [JsonPropertyName("tracer")] public string Tracer { get; init; } - [JsonPropertyName("txHash")] public Hash256? TxHash { get; init; } - [JsonPropertyName("tracerConfig")] public JsonElement? TracerConfig { get; init; } - [JsonPropertyName("stateOverrides")] public Dictionary? StateOverrides { get; init; } + public BlockOverride? BlockOverrides { get; set; } + public static GethTraceOptions Default { get; } = new(); } diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/ITxLogsMutator.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/ITxLogsMutator.cs deleted file mode 100644 index f071b8ce358f..000000000000 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/ITxLogsMutator.cs +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Nethermind.Core; - -namespace Nethermind.Blockchain.Tracing; - -public interface ITxLogsMutator -{ - bool IsMutatingLogs { get; } - - void SetLogsToMutate(ICollection logsToMutate); -} diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/ParityStyle/ParityLikeTxTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/ParityStyle/ParityLikeTxTracer.cs index 43d4ec399cfe..cc1b104b0a85 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/ParityStyle/ParityLikeTxTracer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/ParityStyle/ParityLikeTxTracer.cs @@ -9,6 +9,7 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Evm; +using Nethermind.Evm.CodeAnalysis; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; using Nethermind.Int256; @@ -236,7 +237,7 @@ public override void StartOperation(int pc, Instruction opcode, long gas, in Exe { ParityVmOperationTrace operationTrace = new(); _gasAlreadySetForCurrentOp = false; - operationTrace.Pc = pc + env.CodeInfo.PcOffset(); + operationTrace.Pc = pc + (env.CodeInfo is EofCodeInfo eof ? eof.PcOffset() : 0); operationTrace.Cost = gas; // skip codeSection // skip functionDepth diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/Proofs/ProofBlockTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/Proofs/ProofBlockTracer.cs index c01b2cf454ad..ab8a6a5a11cb 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/Proofs/ProofBlockTracer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/Proofs/ProofBlockTracer.cs @@ -6,16 +6,10 @@ namespace Nethermind.Blockchain.Tracing.Proofs; -public class ProofBlockTracer : BlockTracerBase +public class ProofBlockTracer(Hash256? txHash, bool treatZeroAccountDifferently) + : BlockTracerBase(txHash) { - private readonly bool _treatSystemAccountDifferently; - - public ProofBlockTracer(Hash256? txHash, bool treatSystemAccountDifferently) : base(txHash) - { - _treatSystemAccountDifferently = treatSystemAccountDifferently; - } - - protected override ProofTxTracer OnStart(Transaction? tx) => new(_treatSystemAccountDifferently); + protected override ProofTxTracer OnStart(Transaction? tx) => new(treatZeroAccountDifferently); /// /// Here I decided to return tracer after experimenting with ProofTxTrace class. It encapsulates less but avoid additional type introduction which does not bring much value. diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/Proofs/ProofTxTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/Proofs/ProofTxTracer.cs index 1c30f8e6a527..90c79d71e357 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/Proofs/ProofTxTracer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/Proofs/ProofTxTracer.cs @@ -11,7 +11,7 @@ namespace Nethermind.Blockchain.Tracing.Proofs; -public class ProofTxTracer(bool treatSystemAccountDifferently) : TxTracer +public class ProofTxTracer(bool treatZeroAccountDifferently) : TxTracer { public HashSet
Accounts { get; } = new(); @@ -33,7 +33,7 @@ public override void ReportBlockHash(Hash256 blockHash) public override void ReportBalanceChange(Address address, UInt256? before, UInt256? after) { - if (treatSystemAccountDifferently && Address.SystemUser == address && before is null && after?.IsZero != false) + if (treatZeroAccountDifferently && Address.Zero == address && before is null && after?.IsZero != false) { return; } @@ -43,7 +43,7 @@ public override void ReportBalanceChange(Address address, UInt256? before, UInt2 public override void ReportCodeChange(Address address, byte[]? before, byte[]? after) { - if (treatSystemAccountDifferently && Address.SystemUser == address && before is null && + if (treatZeroAccountDifferently && Address.Zero == address && before is null && after == Array.Empty()) { return; @@ -54,7 +54,7 @@ public override void ReportCodeChange(Address address, byte[]? before, byte[]? a public override void ReportNonceChange(Address address, UInt256? before, UInt256? after) { - if (treatSystemAccountDifferently && Address.SystemUser == address && before is null && after?.IsZero != false) + if (treatZeroAccountDifferently && Address.Zero == address && before is null && after?.IsZero != false) { return; } diff --git a/src/Nethermind/Nethermind.Blockchain/Utils/LastNStateRootTracker.cs b/src/Nethermind/Nethermind.Blockchain/Utils/LastNStateRootTracker.cs index c1824d21eaaa..66c4727bfac7 100644 --- a/src/Nethermind/Nethermind.Blockchain/Utils/LastNStateRootTracker.cs +++ b/src/Nethermind/Nethermind.Blockchain/Utils/LastNStateRootTracker.cs @@ -11,8 +11,8 @@ namespace Nethermind.Blockchain.Utils; -// TODO: Move responsibility to IWorldStateManager? Could be, but if IWorldStateManager store more than 128 blocks -// of state, that would be out of spec for snap and it would fail hive test. +// TODO: Move responsibility to IWorldStateManager? Could be, but if IWorldStateManager stores more blocks +// of state than configured, that would require updating the snap serving configuration (ISyncConfig.SnapServingMaxDepth). public class LastNStateRootTracker : ILastNStateRootTracker, IDisposable { private readonly IBlockTree _blockTree; diff --git a/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs b/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs index 9b4f311e1abd..9c5b6dc2b00b 100644 --- a/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs +++ b/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs @@ -28,10 +28,8 @@ using System.Threading.Tasks; using Autofac; using FluentAssertions; -using Nethermind.Api; using Nethermind.Consensus.Validators; using Nethermind.Core.Test.Modules; -using Nethermind.State; namespace Nethermind.Clique.Test; @@ -105,7 +103,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 +278,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 +287,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; @@ -865,7 +863,6 @@ public async Task Can_stop() await goerli.StopNode(TestItem.PrivateKeyA, dontDispose: true); goerli.ProcessGenesis(); - await Task.Delay(1000); goerli.AssertHeadBlockIs(TestItem.PrivateKeyA, 0); await goerli.StopNode(TestItem.PrivateKeyA); diff --git a/src/Nethermind/Nethermind.Clique.Test/CliqueRpcModuleTests.cs b/src/Nethermind/Nethermind.Clique.Test/CliqueRpcModuleTests.cs index 943ed3cf80cd..497061367a98 100644 --- a/src/Nethermind/Nethermind.Clique.Test/CliqueRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.Clique.Test/CliqueRpcModuleTests.cs @@ -40,7 +40,7 @@ public void Sets_clique_block_producer_properly() Substitute.For(), Substitute.For(), new CliqueSealer(signer, cliqueConfig, Substitute.For(), LimboLogs.Instance), - new TargetAdjustedGasLimitCalculator(HoleskySpecProvider.Instance, new BlocksConfig()), + new TargetAdjustedGasLimitCalculator(HoodiSpecProvider.Instance, new BlocksConfig()), MainnetSpecProvider.Instance, cliqueConfig, LimboLogs.Instance); diff --git a/src/Nethermind/Nethermind.Config.Test/ConfigFileTestsBase.cs b/src/Nethermind/Nethermind.Config.Test/ConfigFileTestsBase.cs index 4edd15870730..3d8d49777668 100644 --- a/src/Nethermind/Nethermind.Config.Test/ConfigFileTestsBase.cs +++ b/src/Nethermind/Nethermind.Config.Test/ConfigFileTestsBase.cs @@ -77,9 +77,9 @@ protected IEnumerable SepoliaConfigs protected IEnumerable ChiadoConfigs => Configs.Where(static config => config.Contains("chiado")); - [ConfigFileGroup("holesky")] + [ConfigFileGroup("hoodi")] protected IEnumerable HoleskyConfigs - => Configs.Where(static config => config.Contains("holesky")); + => Configs.Where(static config => config.Contains("hoodi")); [ConfigFileGroup("spaceneth")] protected IEnumerable SpacenethConfigs @@ -160,14 +160,9 @@ protected IEnumerable GetConfigProviders(string configWildca } } - protected class TestConfigProvider : ConfigProvider + protected class TestConfigProvider(string fileName) : ConfigProvider { - public string FileName { get; } - - public TestConfigProvider(string fileName) - { - FileName = fileName; - } + public string FileName { get; } = fileName; } private static TestConfigProvider GetConfigProviderFromFile(string configFile) @@ -207,13 +202,8 @@ private Dictionary> BuildConfigGroups() } } - protected class ConfigFileGroup : Attribute + protected class ConfigFileGroup(string name) : Attribute { - public ConfigFileGroup(string name) - { - Name = name; - } - - public string Name { get; } + public string Name { get; } = name; } } diff --git a/src/Nethermind/Nethermind.Config.Test/ConfigProvider_FindIncorrectSettings_Tests.cs b/src/Nethermind/Nethermind.Config.Test/ConfigProvider_FindIncorrectSettings_Tests.cs index e06da50a042d..87ccec887038 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.Test/JsonConfigProviderTests.cs b/src/Nethermind/Nethermind.Config.Test/JsonConfigProviderTests.cs index 85a92a159094..9ebe2d334fc7 100644 --- a/src/Nethermind/Nethermind.Config.Test/JsonConfigProviderTests.cs +++ b/src/Nethermind/Nethermind.Config.Test/JsonConfigProviderTests.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Linq; using Nethermind.Api; using Nethermind.Consensus.Processing; using Nethermind.Core.Collections; diff --git a/src/Nethermind/Nethermind.Config/BlocksConfig.cs b/src/Nethermind/Nethermind.Config/BlocksConfig.cs index 36a1c5c5834d..1eeba56cc523 100644 --- a/src/Nethermind/Nethermind.Config/BlocksConfig.cs +++ b/src/Nethermind/Nethermind.Config/BlocksConfig.cs @@ -58,6 +58,9 @@ private static string GetDefaultVersionExtraData() public ulong SecondsPerSlot { get; set; } = 12; public bool PreWarmStateOnBlockProcessing { get; set; } = true; + + public bool CachePrecompilesOnBlockProcessing { get; set; } = true; + public int PreWarmStateConcurrency { get; set; } = 0; public int BlockProductionTimeoutMs { get; set; } = 4_000; @@ -85,15 +88,17 @@ public string ExtraData _extraDataBytes = bytes; } } - public byte[] GetExtraDataBytes() - { - return _extraDataBytes; - } + + public bool BuildBlocksOnMainState { get; set; } + + public byte[] GetExtraDataBytes() => _extraDataBytes; public string GasToken { get => GasTokenTicker; set => GasTokenTicker = value; } public static string GasTokenTicker { get; set; } = "ETH"; public long BlockProductionMaxTxKilobytes { get; set; } = DefaultMaxTxKilobytes; + + public int? BlockProductionBlobLimit { get; set; } } } diff --git a/src/Nethermind/Nethermind.Config/ConfigExtensions.cs b/src/Nethermind/Nethermind.Config/ConfigExtensions.cs index 16dcd1000cc4..e9f889161c39 100644 --- a/src/Nethermind/Nethermind.Config/ConfigExtensions.cs +++ b/src/Nethermind/Nethermind.Config/ConfigExtensions.cs @@ -8,6 +8,7 @@ using System.Reflection; namespace Nethermind.Config; + public static class ConfigExtensions { private static readonly ConcurrentDictionary PortOptions = new(); diff --git a/src/Nethermind/Nethermind.Config/ConfigItemAttribute.cs b/src/Nethermind/Nethermind.Config/ConfigItemAttribute.cs index e37ece79cad3..21038431e3ca 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/ConfigSourceHelper.cs b/src/Nethermind/Nethermind.Config/ConfigSourceHelper.cs index 8b305d4fb1f9..4f63e951c321 100644 --- a/src/Nethermind/Nethermind.Config/ConfigSourceHelper.cs +++ b/src/Nethermind/Nethermind.Config/ConfigSourceHelper.cs @@ -154,6 +154,11 @@ private static object GetValue(Type valueType, string itemValue) return new Hash256(itemValue); } + if (valueType == typeof(NetworkNode)) + { + return new NetworkNode(itemValue); + } + if (valueType.IsEnum) { return Enum.TryParse(valueType, itemValue, true, out object enumValue) diff --git a/src/Nethermind/Nethermind.Config/Enode.cs b/src/Nethermind/Nethermind.Config/Enode.cs index c7203886f462..576fbc0a6e76 100644 --- a/src/Nethermind/Nethermind.Config/Enode.cs +++ b/src/Nethermind/Nethermind.Config/Enode.cs @@ -1,14 +1,14 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only #nullable enable +using Nethermind.Core; +using Nethermind.Core.Crypto; using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net; using System.Net.Sockets; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Core.Exceptions; namespace Nethermind.Config { @@ -32,8 +32,7 @@ ArgumentException GetDnsException(string hostName, Exception? innerException = n ArgumentException GetPortException(string hostName) => new($"Can't get Port for host {hostName}."); - if (!(Uri.TryCreate(enodeString, new UriCreationOptions() { }, out Uri? parsed) - && parsed.Scheme.Equals("enode", StringComparison.OrdinalIgnoreCase))) + if (!IsEnode(enodeString, out Uri? parsed)) { throw new ArgumentException($"Invalid enode value '{enodeString}'"); } @@ -108,5 +107,8 @@ ArgumentException GetPortException(string hostName) => : $"enode://{_nodeKey.ToString(false)}@{HostIp}:{Port}?discport={DiscoveryPort}"; public override string ToString() => Info; + + public static bool IsEnode(string enodeString, [NotNullWhen(true)] out Uri? parsed) => + Uri.TryCreate(enodeString, new UriCreationOptions(), out parsed) && parsed.Scheme.Equals("enode", StringComparison.OrdinalIgnoreCase); } } diff --git a/src/Nethermind/Nethermind.Config/ExitCodes.cs b/src/Nethermind/Nethermind.Config/ExitCodes.cs index 88b7a2ced0d9..f20c402193fd 100644 --- a/src/Nethermind/Nethermind.Config/ExitCodes.cs +++ b/src/Nethermind/Nethermind.Config/ExitCodes.cs @@ -19,6 +19,8 @@ public static class ExitCodes public const int UnrecognizedOption = 106; 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.Config/IBlocksConfig.cs b/src/Nethermind/Nethermind.Config/IBlocksConfig.cs index 91d4345d1eb5..5706b1b458fe 100644 --- a/src/Nethermind/Nethermind.Config/IBlocksConfig.cs +++ b/src/Nethermind/Nethermind.Config/IBlocksConfig.cs @@ -40,6 +40,9 @@ public interface IBlocksConfig : IConfig [ConfigItem(Description = "Whether to pre-warm the state when processing blocks. This can lead to an up to 2x speed-up in the main loop block processing.", DefaultValue = "True")] bool PreWarmStateOnBlockProcessing { get; set; } + [ConfigItem(Description = "Whether to cache precompile results when processing blocks.", DefaultValue = "True", HiddenFromDocs = true)] + bool CachePrecompilesOnBlockProcessing { get; set; } + [ConfigItem(Description = "Specify pre-warm state concurrency. Default is logical processor - 1.", DefaultValue = "0", HiddenFromDocs = true)] int PreWarmStateConcurrency { get; set; } @@ -49,11 +52,17 @@ public interface IBlocksConfig : IConfig [ConfigItem(Description = "The genesis block load timeout, in milliseconds.", DefaultValue = "40000")] int GenesisTimeoutMs { get; set; } - [ConfigItem(Description = "The max transaction bytes to add in block production, in kilobytes.", DefaultValue = "9728")] + [ConfigItem(Description = "The max transaction bytes to add in block production, in kilobytes.", DefaultValue = "7936")] long BlockProductionMaxTxKilobytes { get; set; } [ConfigItem(Description = "The ticker that gas rewards are denominated in for processing logs", DefaultValue = "ETH", HiddenFromDocs = true)] string GasToken { get; set; } + [ConfigItem(Description = "Builds blocks on main (non-readonly) state", DefaultValue = "false", HiddenFromDocs = true)] + bool BuildBlocksOnMainState { get; set; } + byte[] GetExtraDataBytes(); + + [ConfigItem(Description = "The max blob count after which the block producer should stop adding blobs. Minimum value is `0`.", DefaultValue = "null")] + int? BlockProductionBlobLimit { get; set; } } diff --git a/src/Nethermind/Nethermind.Config/Nethermind.Config.csproj b/src/Nethermind/Nethermind.Config/Nethermind.Config.csproj index 70d4ded9a7bd..86fe2542f99c 100644 --- a/src/Nethermind/Nethermind.Config/Nethermind.Config.csproj +++ b/src/Nethermind/Nethermind.Config/Nethermind.Config.csproj @@ -1,7 +1,9 @@ - + + + diff --git a/src/Nethermind/Nethermind.Config/NetworkNode.cs b/src/Nethermind/Nethermind.Config/NetworkNode.cs index ce35250ee489..225573b76075 100644 --- a/src/Nethermind/Nethermind.Config/NetworkNode.cs +++ b/src/Nethermind/Nethermind.Config/NetworkNode.cs @@ -1,70 +1,104 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +#nullable enable +using Lantern.Discv5.Enr; +using Lantern.Discv5.Enr.Entries; +using Lantern.Discv5.Enr.Identity; +using Lantern.Discv5.Enr.Identity.V4; +using Nethermind.Core.Crypto; +using Nethermind.Logging; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Net; -using Nethermind.Core.Crypto; -using Nethermind.Logging; -namespace Nethermind.Config +namespace Nethermind.Config; + +/// +/// Node data for storage and configuration only. +/// +public class NetworkNode { - /// - /// Node data for storage and configuration only. - /// - public class NetworkNode - { - private readonly Enode _enode; + private static readonly EnrFactory _enrFactory = new(new EnrEntryRegistry()); + private static readonly IIdentityVerifier identityVerifier = new IdentityVerifierV4(); + + private readonly Enode? _enode; + private readonly Enr? _enr; + + [MemberNotNullWhen(true, nameof(Enode))] + [MemberNotNullWhen(false, nameof(Enr))] + public bool IsEnode => _enode is not null; + + [MemberNotNullWhen(true, nameof(Enr))] + [MemberNotNullWhen(false, nameof(Enode))] + public bool IsEnr => _enr is not null; - public NetworkNode(string enode) + public NetworkNode(string nodeString) + { + if (Enode.IsEnode(nodeString, out _)) { - //enode://0d837e193233c08d6950913bf69105096457fbe204679d6c6c021c36bb5ad83d167350440670e7fec189d80abc18076f45f44bfe480c85b6c632735463d34e4b@89.197.135.74:30303 - _enode = new Enode(enode); + _enode = new Enode(nodeString); } - - public static NetworkNode[] ParseNodes(string enodesString, ILogger logger) + else { - string[] nodeStrings = enodesString?.Split(",", StringSplitOptions.RemoveEmptyEntries); - if (nodeStrings is null) - { - return []; - } - - List nodes = new(); - foreach (string nodeString in nodeStrings) - { - try - { - nodes.Add(new NetworkNode(nodeString.Trim())); - } - catch (Exception e) - { - if (logger.IsError) logger.Error($"Could not parse enode data from {nodeString}", e); - } - } + _enr = _enrFactory.CreateFromString(nodeString, identityVerifier); + } + } - return nodes.ToArray(); + public static NetworkNode[] ParseNodes(string? nodeRecords, ILogger logger) + { + if (nodeRecords is null) + { + return []; } - public override string ToString() => _enode.ToString(); + string[] nodeStrings = nodeRecords.Split(",", StringSplitOptions.RemoveEmptyEntries); - public NetworkNode(PublicKey publicKey, string ip, int port, long reputation = 0) + return ParseNodes(nodeStrings, logger); + } + + public static NetworkNode[] ParseNodes(string[]? nodeRecords, ILogger logger) + { + if (nodeRecords is null) { - _enode = new Enode(publicKey, IPAddress.Parse(ip), port); - Reputation = reputation; + return []; } - public NetworkNode(Enode enode) + List nodes = new(nodeRecords.Length); + + foreach (string nodeString in nodeRecords) { - _enode = enode; + try + { + nodes.Add(new NetworkNode(nodeString.Trim())); + } + catch (Exception e) + { + if (logger.IsError) logger.Error($"Could not parse enode data from {nodeString}", e); + } } - public Enode Enode => _enode; + return [.. nodes]; + } + + public override string ToString() => IsEnode ? Enode.ToString() : Enr.ToString(); - public PublicKey NodeId => _enode.PublicKey; - public string Host => _enode.HostIp.ToString(); - public IPAddress HostIp => _enode.HostIp; - public int Port => _enode.Port; - public long Reputation { get; set; } + public NetworkNode(PublicKey publicKey, string ip, int port, long reputation = 0) + : this(new Enode(publicKey, IPAddress.Parse(ip), port)) + { + Reputation = reputation; } + + public NetworkNode(Enode enode) => _enode = enode; + + public Enode? Enode => _enode; + + public Enr? Enr => _enr; + + public PublicKey NodeId => IsEnode ? Enode.PublicKey : new PublicKey(Enr.GetEntry(EnrEntryKey.Secp256K1).Value); + public string Host => IsEnode ? Enode.HostIp.ToString() : Enr.GetEntry(EnrEntryKey.Ip).Value.ToString(); + public IPAddress HostIp => IsEnode ? Enode.HostIp : Enr.GetEntry(EnrEntryKey.Ip).Value; + public int Port => IsEnode ? Enode.Port : Enr.GetEntry(EnrEntryKey.Tcp).Value; + public long Reputation { get; set; } } diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockFinalizationManager.cs b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockFinalizationManager.cs index 87d5e8a15959..76a37745f107 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockFinalizationManager.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockFinalizationManager.cs @@ -345,6 +345,7 @@ private set public void Dispose() { _branchProcessor.BlockProcessed -= OnBlockProcessed; + _branchProcessor.BlocksProcessing -= OnBlocksProcessing; } [DebuggerDisplay("Count = {Count}")] diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockProcessor.cs b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockProcessor.cs index 02e4badf8b7a..b710603c74fb 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockProcessor.cs @@ -57,7 +57,7 @@ public AuRaBlockProcessor(ISpecProvider specProvider, stateProvider, receiptStorage, beaconBlockRootHandler, - new BlockhashStore(specProvider, stateProvider), + new BlockhashStore(stateProvider), logManager, withdrawalProcessor, executionRequestsProcessor) @@ -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/AuRaHeaderValidator.cs b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaHeaderValidator.cs index 35514657fea9..e2b1f84a0a44 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaHeaderValidator.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaHeaderValidator.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using System.Linq; using Nethermind.Blockchain; diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaPlugin.cs b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaPlugin.cs index 58b878a44780..d7f682349a35 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 c1beed8e1e81..705f81b10636 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 02498c9c75e4..a0d55dae6113 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/Contracts/BlockGasLimitContract.cs b/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/BlockGasLimitContract.cs index da661c70b4af..bb148a327249 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/BlockGasLimitContract.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/BlockGasLimitContract.cs @@ -5,10 +5,8 @@ using Nethermind.Abi; using Nethermind.Blockchain; using Nethermind.Blockchain.Contracts; -using Nethermind.Consensus.Processing; using Nethermind.Core; using Nethermind.Int256; -using Nethermind.Evm.TransactionProcessing; namespace Nethermind.Consensus.AuRa.Contracts { diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/TransactionPermissionContractV2.cs b/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/TransactionPermissionContractV2.cs index f528cf22ae3a..66fc45470831 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/TransactionPermissionContractV2.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/TransactionPermissionContractV2.cs @@ -6,7 +6,6 @@ using Nethermind.Blockchain; using Nethermind.Core; using Nethermind.Int256; -using Nethermind.Evm.TransactionProcessing; namespace Nethermind.Consensus.AuRa.Contracts { diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/VersionedTransactionPermissionContract.cs b/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/VersionedTransactionPermissionContract.cs index 91a5b37167b6..c930e0edbb64 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/VersionedTransactionPermissionContract.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/VersionedTransactionPermissionContract.cs @@ -9,7 +9,6 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Int256; -using Nethermind.Evm.TransactionProcessing; using Nethermind.Logging; namespace Nethermind.Consensus.AuRa.Contracts diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaNethermindApi.cs b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaNethermindApi.cs index fe34043f5784..0edbcfb63c5d 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaNethermindApi.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaNethermindApi.cs @@ -16,13 +16,8 @@ namespace Nethermind.Consensus.AuRa.InitializationSteps { - public class AuRaNethermindApi : NethermindApi + public class AuRaNethermindApi(NethermindApi.Dependencies dependencies) : NethermindApi(dependencies) { - public AuRaNethermindApi(Dependencies dependencies) - : base(dependencies) - { - } - public new IAuRaBlockFinalizationManager? FinalizationManager { get => base.FinalizationManager as IAuRaBlockFinalizationManager; diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuraGenesisPostProcessor.cs b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuraGenesisPostProcessor.cs index 21a041dd34a8..ab28bb8c1187 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuraGenesisPostProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuraGenesisPostProcessor.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Linq; using Nethermind.Blockchain; using Nethermind.Core; diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs index 882fae82c179..a4c285bd14a6 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs @@ -20,31 +20,26 @@ namespace Nethermind.Consensus.AuRa.InitializationSteps; -public class InitializeBlockchainAuRa : InitializeBlockchain +public class InitializeBlockchainAuRa(AuRaNethermindApi api, IChainHeadInfoProvider chainHeadInfoProvider) + : InitializeBlockchain(api, chainHeadInfoProvider) { - private readonly AuRaNethermindApi _api; - private INethermindApi NethermindApi => _api; - - public InitializeBlockchainAuRa(AuRaNethermindApi api, IChainHeadInfoProvider chainHeadInfoProvider) : base(api, chainHeadInfoProvider) - { - _api = api; - } + private INethermindApi NethermindApi => api; protected override async Task InitBlockchain() { - var chainSpecAuRa = _api.ChainSpec.EngineChainSpecParametersProvider.GetChainSpecParameters(); - _api.FinalizationManager = new AuRaBlockFinalizationManager( - _api.BlockTree!, - _api.ChainLevelInfoRepository!, - _api.ValidatorStore!, + var chainSpecAuRa = api.ChainSpec.EngineChainSpecParametersProvider.GetChainSpecParameters(); + api.FinalizationManager = new AuRaBlockFinalizationManager( + api.BlockTree!, + api.ChainLevelInfoRepository!, + api.ValidatorStore!, new ValidSealerStrategy(), - _api.LogManager, + api.LogManager, chainSpecAuRa.TwoThirdsMajorityTransition); await base.InitBlockchain(); // Got cyclic dependency. AuRaBlockFinalizationManager -> IAuraValidator -> AuraBlockProcessor -> AuraBlockFinalizationManager. - _api.FinalizationManager.SetMainBlockBranchProcessor(_api.MainProcessingContext!.BranchProcessor!); + api.FinalizationManager.SetMainBlockBranchProcessor(api.MainProcessingContext!.BranchProcessor!); } private IComparer CreateTxPoolTxComparer(TxPriorityContract? txPriorityContract, TxPriorityContract.LocalDataSource? localDataSource) @@ -54,24 +49,24 @@ private IComparer CreateTxPoolTxComparer(TxPriorityContract? txPrio ContractDataStore
whitelistContractDataStore = new ContractDataStoreWithLocalData
( new HashSetContractDataStoreCollection
(), txPriorityContract?.SendersWhitelist, - _api.BlockTree, - _api.ReceiptFinder, - _api.LogManager, + api.BlockTree, + api.ReceiptFinder, + api.LogManager, localDataSource?.GetWhitelistLocalDataSource() ?? new EmptyLocalDataSource>()); DictionaryContractDataStore prioritiesContractDataStore = new DictionaryContractDataStore( new TxPriorityContract.DestinationSortedListContractDataStoreCollection(), txPriorityContract?.Priorities, - _api.BlockTree, - _api.ReceiptFinder, - _api.LogManager, + api.BlockTree, + api.ReceiptFinder, + api.LogManager, localDataSource?.GetPrioritiesLocalDataSource()); - _api.DisposeStack.Push(whitelistContractDataStore); - _api.DisposeStack.Push(prioritiesContractDataStore); - IComparer txByPriorityComparer = new CompareTxByPriorityOnHead(whitelistContractDataStore, prioritiesContractDataStore, _api.BlockTree); - IComparer sameSenderNonceComparer = new CompareTxSameSenderNonce(new GasPriceTxComparer(_api.BlockTree, _api.SpecProvider!), txByPriorityComparer); + api.DisposeStack.Push(whitelistContractDataStore); + api.DisposeStack.Push(prioritiesContractDataStore); + IComparer txByPriorityComparer = new CompareTxByPriorityOnHead(whitelistContractDataStore, prioritiesContractDataStore, api.BlockTree); + IComparer sameSenderNonceComparer = new CompareTxSameSenderNonce(new GasPriceTxComparer(api.BlockTree, api.SpecProvider!), txByPriorityComparer); return sameSenderNonceComparer .ThenBy(CompareTxByTimestamp.Instance) @@ -85,33 +80,33 @@ private IComparer CreateTxPoolTxComparer(TxPriorityContract? txPrio protected override TxPool.TxPool CreateTxPool(IChainHeadInfoProvider chainHeadInfoProvider) { // This has to be different object than the _processingReadOnlyTransactionProcessorSource as this is in separate thread - TxPriorityContract txPriorityContract = _api.TxAuRaFilterBuilders.CreateTxPrioritySources(); - TxPriorityContract.LocalDataSource? localDataSource = _api.AuraStatefulComponents.TxPriorityContractLocalDataSource; + TxPriorityContract txPriorityContract = api.TxAuRaFilterBuilders.CreateTxPrioritySources(); + TxPriorityContract.LocalDataSource? localDataSource = api.AuraStatefulComponents.TxPriorityContractLocalDataSource; ReportTxPriorityRules(txPriorityContract, localDataSource); DictionaryContractDataStore? minGasPricesContractDataStore - = _api.TxAuRaFilterBuilders.CreateMinGasPricesDataStore(txPriorityContract, localDataSource); + = api.TxAuRaFilterBuilders.CreateMinGasPricesDataStore(txPriorityContract, localDataSource); - ITxFilter txPoolFilter = _api.TxAuRaFilterBuilders.CreateAuRaTxFilterForProducer(minGasPricesContractDataStore); + ITxFilter txPoolFilter = api.TxAuRaFilterBuilders.CreateAuRaTxFilterForProducer(minGasPricesContractDataStore); return new TxPool.TxPool( - _api.EthereumEcdsa!, - _api.BlobTxStorage ?? NullBlobTxStorage.Instance, + api.EthereumEcdsa!, + api.BlobTxStorage ?? NullBlobTxStorage.Instance, chainHeadInfoProvider, NethermindApi.Config(), - _api.TxValidator!, - _api.LogManager, + api.TxValidator!, + api.LogManager, CreateTxPoolTxComparer(txPriorityContract, localDataSource), - _api.TxGossipPolicy, - new TxFilterAdapter(_api.BlockTree, txPoolFilter, _api.LogManager, _api.SpecProvider), - _api.HeadTxValidator, + api.TxGossipPolicy, + new TxFilterAdapter(api.BlockTree, txPoolFilter, api.LogManager, api.SpecProvider), + api.HeadTxValidator, txPriorityContract is not null || localDataSource is not null); } private void ReportTxPriorityRules(TxPriorityContract? txPriorityContract, TxPriorityContract.LocalDataSource? localDataSource) { - ILogger logger = _api.LogManager.GetClassLogger(); + ILogger logger = api.LogManager.GetClassLogger(); if (localDataSource?.FilePath is not null) { diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/StartBlockProducerAuRa.cs b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/StartBlockProducerAuRa.cs index a92127f90d7d..caa2c2fd8939 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 83dc02b25b2a..6925261f4fa3 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 84cda756fc62..6106d575c2f8 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/CompareTxByPriorityBase.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/CompareTxByPriorityBase.cs @@ -9,27 +9,26 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Int256; +using Nethermind.TxPool.Comparison; namespace Nethermind.Consensus.AuRa.Transactions { - public abstract class CompareTxByPriorityBase : IComparer + public abstract class CompareTxByPriorityBase( + IContractDataStore
sendersWhitelist, // expected HashSet based + IDictionaryContractDataStore priorities) + : IComparer { - private readonly IContractDataStore
_sendersWhitelist; - private readonly IDictionaryContractDataStore _priorities; + private readonly IContractDataStore
_sendersWhitelist = sendersWhitelist ?? throw new ArgumentNullException(nameof(sendersWhitelist)); + private readonly IDictionaryContractDataStore _priorities = priorities ?? throw new ArgumentNullException(nameof(priorities)); private Hash256 _blockHash; private ISet
_sendersWhiteListSet; - public CompareTxByPriorityBase(IContractDataStore
sendersWhitelist, // expected HashSet based - IDictionaryContractDataStore priorities) // expected SortedList based) - { - _sendersWhitelist = sendersWhitelist ?? throw new ArgumentNullException(nameof(sendersWhitelist)); - _priorities = priorities ?? throw new ArgumentNullException(nameof(priorities)); - } + // expected SortedList based) protected abstract BlockHeader BlockHeader { get; } public UInt256 GetPriority(Transaction tx) => - _priorities.TryGetValue(BlockHeader, tx, out var destination) + _priorities.TryGetValue(BlockHeader, tx, out TxPriorityContract.Destination destination) ? destination.Value : UInt256.Zero; @@ -45,21 +44,21 @@ private void CheckReloadSendersWhitelist() if (blockHeader.Hash != _blockHash) { _sendersWhiteListSet = _sendersWhitelist.GetItemsFromContractAtBlock(blockHeader).AsSet(); - _blockHash = blockHeader.Hash; + _blockHash = blockHeader.Hash!; } } - public int Compare(Transaction x, Transaction y) + public int Compare(Transaction? x, Transaction? y) { - if (ReferenceEquals(x, y)) return 0; - if (y is null) return 1; - if (x is null) return -1; + if (ReferenceEquals(x, y)) return TxComparisonResult.Equal; + if (y is null) return TxComparisonResult.YFirst; + if (x is null) return TxComparisonResult.XFirst; // 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/GeneratedTxSourceSealer.cs b/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/GeneratedTxSourceSealer.cs index 48afbc98e8e0..e14881fb2631 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/GeneratedTxSourceSealer.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/GeneratedTxSourceSealer.cs @@ -7,7 +7,6 @@ using Nethermind.Consensus.Producers; using Nethermind.Consensus.Transactions; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.State; diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/RandomContractTxSource.cs b/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/RandomContractTxSource.cs index af8200850920..c4dc44efa373 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.AuRa/Validators/PendingValidatorsDecoder.cs b/src/Nethermind/Nethermind.Consensus.AuRa/Validators/PendingValidatorsDecoder.cs index 7dc6dcc28945..4160d122fc5d 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/Validators/PendingValidatorsDecoder.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/Validators/PendingValidatorsDecoder.cs @@ -7,9 +7,9 @@ namespace Nethermind.Consensus.AuRa.Validators { - internal class PendingValidatorsDecoder : IRlpObjectDecoder, IRlpStreamDecoder + internal sealed class PendingValidatorsDecoder : RlpStreamDecoder, IRlpObjectDecoder { - public PendingValidators Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override PendingValidators DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (rlpStream.IsNextItemNull()) { @@ -54,7 +54,7 @@ public Rlp Encode(PendingValidators item, RlpBehaviors rlpBehaviors = RlpBehavio return new Rlp(rlpStream.Data.ToArray()); } - public void Encode(RlpStream rlpStream, PendingValidators item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream rlpStream, PendingValidators item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { (int contentLength, int addressesLength) = GetContentLength(item, rlpBehaviors); rlpStream.StartSequence(contentLength); @@ -68,7 +68,7 @@ public void Encode(RlpStream rlpStream, PendingValidators item, RlpBehaviors rlp rlpStream.Encode(item.AreFinalized); } - public int GetLength(PendingValidators item, RlpBehaviors rlpBehaviors) => + public override int GetLength(PendingValidators item, RlpBehaviors rlpBehaviors) => item is null ? 1 : Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors).Total); private static (int Total, int Addresses) GetContentLength(PendingValidators item, RlpBehaviors rlpBehaviors) diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/Validators/ValidatorInfoDecoder.cs b/src/Nethermind/Nethermind.Consensus.AuRa/Validators/ValidatorInfoDecoder.cs index 462db51a1967..783878be3eec 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/Validators/ValidatorInfoDecoder.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/Validators/ValidatorInfoDecoder.cs @@ -6,9 +6,9 @@ namespace Nethermind.Consensus.AuRa.Validators { - internal class ValidatorInfoDecoder : IRlpStreamDecoder, IRlpObjectDecoder + internal sealed class ValidatorInfoDecoder : RlpStreamDecoder, IRlpObjectDecoder { - public ValidatorInfo? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override ValidatorInfo? DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (rlpStream.IsNextItemNull()) { @@ -23,7 +23,9 @@ internal class ValidatorInfoDecoder : IRlpStreamDecoder, IRlpObje int addressesSequenceLength = rlpStream.ReadSequenceLength(); int addressesCheck = rlpStream.Position + addressesSequenceLength; - Address[] addresses = new Address[addressesSequenceLength / Rlp.LengthOfAddressRlp]; + var count = addressesSequenceLength / Rlp.LengthOfAddressRlp; + rlpStream.GuardLimit(count); + Address[] addresses = new Address[count]; int i = 0; while (rlpStream.Position < addressesCheck) { @@ -47,7 +49,7 @@ public Rlp Encode(ValidatorInfo? item, RlpBehaviors rlpBehaviors = RlpBehaviors. return new Rlp(rlpStream.Data.ToArray()); } - public void Encode(RlpStream stream, ValidatorInfo? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, ValidatorInfo? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) { @@ -66,7 +68,7 @@ public void Encode(RlpStream stream, ValidatorInfo? item, RlpBehaviors rlpBehavi } } - public int GetLength(ValidatorInfo? item, RlpBehaviors rlpBehaviors) => item is null ? 1 : Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors).Total); + public override int GetLength(ValidatorInfo? item, RlpBehaviors rlpBehaviors) => item is null ? 1 : Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors).Total); private static (int Total, int Validators) GetContentLength(ValidatorInfo item, RlpBehaviors rlpBehaviors) { diff --git a/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs b/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs index b71813d8461f..9ad3590d1392 100644 --- a/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs +++ b/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Nethermind.Core; namespace Nethermind.Consensus.Clique @@ -12,23 +11,6 @@ public static bool IsInTurn(this BlockHeader header) { return header.Difficulty == Clique.DifficultyInTurn; } - - internal static Address[] ExtractSigners(BlockHeader blockHeader) - { - if (blockHeader.ExtraData is null) - { - throw new Exception(string.Empty); - } - - Span signersData = blockHeader.ExtraData.AsSpan(Clique.ExtraVanityLength, (blockHeader.ExtraData.Length - Clique.ExtraSealLength)); - Address[] signers = new Address[signersData.Length / Address.Size]; - for (int i = 0; i < signers.Length; i++) - { - signers[i] = new Address(signersData.Slice(i * 20, 20).ToArray()); - } - - return signers; - } } internal static class BlockExtensions diff --git a/src/Nethermind/Nethermind.Consensus.Clique/CliqueSealer.cs b/src/Nethermind/Nethermind.Consensus.Clique/CliqueSealer.cs index f7f8ffed6450..c2553622c2bf 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/SnapshotDecoder.cs b/src/Nethermind/Nethermind.Consensus.Clique/SnapshotDecoder.cs index 436256334cb2..1d35ce104ffd 100644 --- a/src/Nethermind/Nethermind.Consensus.Clique/SnapshotDecoder.cs +++ b/src/Nethermind/Nethermind.Consensus.Clique/SnapshotDecoder.cs @@ -9,9 +9,9 @@ namespace Nethermind.Consensus.Clique { - internal class SnapshotDecoder : IRlpStreamDecoder + internal sealed class SnapshotDecoder : RlpStreamDecoder { - public Snapshot Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override Snapshot DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { rlpStream.ReadSequenceLength(); @@ -30,7 +30,7 @@ public Snapshot Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehav return snapshot; } - public void Encode(RlpStream stream, Snapshot item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, Snapshot item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { (int contentLength, int signersLength, int votesLength, int tallyLength) = GetContentLength(item, rlpBehaviors); @@ -43,7 +43,7 @@ public void Encode(RlpStream stream, Snapshot item, RlpBehaviors rlpBehaviors = } - public int GetLength(Snapshot item, RlpBehaviors rlpBehaviors) + public override int GetLength(Snapshot item, RlpBehaviors rlpBehaviors) { (int contentLength, int _, int _, int _) = GetContentLength(item, rlpBehaviors); return Rlp.LengthOfSequence(contentLength); @@ -68,8 +68,9 @@ private static (int contentLength, int signersLength, int votesLength, int tally private static SortedList DecodeSigners(RlpStream rlpStream) { rlpStream.ReadSequenceLength(); - SortedList signers = new SortedList(AddressComparer.Instance); int length = rlpStream.DecodeInt(); + rlpStream.GuardLimit(length); + SortedList signers = new(AddressComparer.Instance); for (int i = 0; i < length; i++) { Address signer = rlpStream.DecodeAddress(); @@ -83,8 +84,9 @@ private static SortedList DecodeSigners(RlpStream rlpStream) private static List DecodeVotes(RlpStream rlpStream) { rlpStream.ReadSequenceLength(); - List votes = new List(); int length = rlpStream.DecodeInt(); + rlpStream.GuardLimit(length); + List votes = new(length); for (int i = 0; i < length; i++) { Address signer = rlpStream.DecodeAddress(); @@ -100,8 +102,9 @@ private static List DecodeVotes(RlpStream rlpStream) private static Dictionary DecodeTally(RlpStream rlpStream) { rlpStream.ReadSequenceLength(); - Dictionary tally = new Dictionary(); int length = rlpStream.DecodeInt(); + rlpStream.GuardLimit(length); + Dictionary tally = new(length); for (int i = 0; i < length; i++) { Address address = rlpStream.DecodeAddress(); diff --git a/src/Nethermind/Nethermind.Consensus.Clique/SnapshotManager.cs b/src/Nethermind/Nethermind.Consensus.Clique/SnapshotManager.cs index e782eac5931e..9a63d1949f95 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.Ethash/Ethash.cs b/src/Nethermind/Nethermind.Consensus.Ethash/Ethash.cs index 1ab94f174a84..001413925e00 100644 --- a/src/Nethermind/Nethermind.Consensus.Ethash/Ethash.cs +++ b/src/Nethermind/Nethermind.Consensus.Ethash/Ethash.cs @@ -155,7 +155,7 @@ private bool IsLessOrEqualThanTarget(ReadOnlySpan result, in UInt256 diffi IEthashDataSet dataSet = _hintBasedCache.Get(epoch); if (dataSet is null) { - if (_logger.IsWarn) _logger.Warn($"Ethash cache miss for block {header.ToString(BlockHeader.Format.Short)}"); + if (_logger.IsTrace) _logger.Trace($"Ethash cache miss for block {header.ToString(BlockHeader.Format.Short)}"); dataSet = BuildCache(epoch); } @@ -167,8 +167,7 @@ private bool IsLessOrEqualThanTarget(ReadOnlySpan result, in UInt256 diffi byte[] mixHash; while (true) { - ValueHash256 result; - (mixHash, result, _) = Hashimoto(fullSize, dataSet, headerHashed, null, nonce); + (mixHash, ValueHash256 result, _) = Hashimoto(fullSize, dataSet, headerHashed, null, nonce); if (IsLessOrEqualThanTarget(result.Bytes, header.Difficulty)) { break; diff --git a/src/Nethermind/Nethermind.Consensus.Ethash/EthashPlugin.cs b/src/Nethermind/Nethermind.Consensus.Ethash/EthashPlugin.cs index 74aa1056e715..9fc861e1b7f6 100644 --- a/src/Nethermind/Nethermind.Consensus.Ethash/EthashPlugin.cs +++ b/src/Nethermind/Nethermind.Consensus.Ethash/EthashPlugin.cs @@ -7,7 +7,6 @@ using Nethermind.Api; using Nethermind.Api.Extensions; using Nethermind.Consensus.Rewards; -using Nethermind.Consensus.Transactions; using Nethermind.Core; using Nethermind.Specs.ChainSpecStyle; diff --git a/src/Nethermind/Nethermind.Consensus.Test/BlockExtenstionsTests.cs b/src/Nethermind/Nethermind.Consensus.Test/BlockExtenstionsTests.cs index 5c0cc4f82b46..a37dbe23e031 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/CensorshipDetectorTests.cs b/src/Nethermind/Nethermind.Consensus.Test/CensorshipDetectorTests.cs index 1197be8c3dad..79cb6fd906dd 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/CensorshipDetectorTests.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/CensorshipDetectorTests.cs @@ -18,13 +18,8 @@ using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; -using Nethermind.Db; -using Nethermind.Evm; using Nethermind.Logging; using Nethermind.Specs; -using Nethermind.Evm.State; -using Nethermind.State; -using Nethermind.Trie.Pruning; using Nethermind.TxPool; using NSubstitute; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Consensus.Test/ExecutionRequestProcessorTests.cs b/src/Nethermind/Nethermind.Consensus.Test/ExecutionRequestProcessorTests.cs index 089a6d7a762f..f8569ebb99c2 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/ExecutionRequestProcessorTests.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/ExecutionRequestProcessorTests.cs @@ -11,23 +11,18 @@ using Nethermind.Core.Specs; using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; -using Nethermind.Db; using Nethermind.Evm; -using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; using Nethermind.Int256; -using Nethermind.Logging; using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.Evm.State; -using Nethermind.Trie.Pruning; using NSubstitute; using NUnit.Framework; using System; using System.Collections.Generic; using System.Linq; using Nethermind.Blockchain.Tracing; -using Nethermind.State; namespace Nethermind.Consensus.Test; diff --git a/src/Nethermind/Nethermind.Consensus.Test/GenesisLoaderTests.cs b/src/Nethermind/Nethermind.Consensus.Test/GenesisLoaderTests.cs new file mode 100644 index 000000000000..9c41299d4ec6 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus.Test/GenesisLoaderTests.cs @@ -0,0 +1,217 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading; +using FluentAssertions; +using Nethermind.Blockchain; +using Nethermind.Consensus.Processing; +using Nethermind.Core; +using Nethermind.Core.Test.Builders; +using Nethermind.Evm.State; +using Nethermind.Logging; +using Nethermind.State; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Consensus.Test; + +[TestFixture] +public class GenesisLoaderTests +{ + [Test] + public void Load_ShouldFlushCacheAfterSuccessfulGenesisProcessing() + { + // Arrange + Block genesisBlock = Build.A.Block.Genesis.TestObject; + + IGenesisBuilder genesisBuilder = Substitute.For(); + genesisBuilder.Build().Returns(genesisBlock); + + IStateReader stateReader = Substitute.For(); + + IBlockTree blockTree = Substitute.For(); + blockTree.When(x => x.SuggestBlock(Arg.Any())).Do(_ => + { + // Simulate block processing by triggering NewHeadBlock event + blockTree.NewHeadBlock += Raise.EventWith(blockTree, new BlockEventArgs(genesisBlock)); + }); + + IWorldState worldState = Substitute.For(); + IDisposable scopeDisposable = Substitute.For(); + worldState.BeginScope(IWorldState.PreGenesis).Returns(scopeDisposable); + + IWorldStateManager worldStateManager = Substitute.For(); + + IBlockchainProcessor blockchainProcessor = Substitute.For(); + + GenesisLoader.Config config = new(null, TimeSpan.FromSeconds(10)); + ILogManager logManager = LimboLogs.Instance; + + GenesisLoader loader = new( + genesisBuilder, + stateReader, + blockTree, + worldState, + worldStateManager, + blockchainProcessor, + config, + logManager + ); + + // Act + loader.Load(); + + // Assert - verify FlushCache was called + worldStateManager.Received(1).FlushCache(Arg.Any()); + } + + [Test] + public void Load_ShouldNotFlushCache_WhenGenesisProcessingTimesOut() + { + // Arrange + Block genesisBlock = Build.A.Block.Genesis.TestObject; + + IGenesisBuilder genesisBuilder = Substitute.For(); + genesisBuilder.Build().Returns(genesisBlock); + + IStateReader stateReader = Substitute.For(); + + IBlockTree blockTree = Substitute.For(); + blockTree.When(x => x.SuggestBlock(Arg.Any())).Do(_ => + { + // Do nothing - simulate timeout + }); + + IWorldState worldState = Substitute.For(); + IDisposable scopeDisposable = Substitute.For(); + worldState.BeginScope(IWorldState.PreGenesis).Returns(scopeDisposable); + + IWorldStateManager worldStateManager = Substitute.For(); + + IBlockchainProcessor blockchainProcessor = Substitute.For(); + + GenesisLoader.Config config = new(null, TimeSpan.FromMilliseconds(100)); + ILogManager logManager = LimboLogs.Instance; + + GenesisLoader loader = new( + genesisBuilder, + stateReader, + blockTree, + worldState, + worldStateManager, + blockchainProcessor, + config, + logManager + ); + + // Act & Assert - expect timeout exception + Assert.Throws(() => loader.Load()); + + // Verify FlushCache was NOT called since genesis processing failed + worldStateManager.DidNotReceive().FlushCache(Arg.Any()); + } + + [Test] + public void Load_ShouldNotFlushCache_WhenGenesisBlockIsInvalid() + { + // Arrange + Block genesisBlock = Build.A.Block.Genesis.TestObject; + + IGenesisBuilder genesisBuilder = Substitute.For(); + genesisBuilder.Build().Returns(genesisBlock); + + IStateReader stateReader = Substitute.For(); + + IBlockTree blockTree = Substitute.For(); + + IWorldState worldState = Substitute.For(); + IDisposable scopeDisposable = Substitute.For(); + worldState.BeginScope(IWorldState.PreGenesis).Returns(scopeDisposable); + + IWorldStateManager worldStateManager = Substitute.For(); + + IBlockchainProcessor blockchainProcessor = Substitute.For(); + blockTree.When(x => x.SuggestBlock(Arg.Any())).Do(_ => + { + // Simulate invalid block by triggering InvalidBlock event + blockchainProcessor.InvalidBlock += Raise.EventWith( + blockchainProcessor, + new IBlockchainProcessor.InvalidBlockEventArgs { InvalidBlock = genesisBlock }); + }); + + GenesisLoader.Config config = new(null, TimeSpan.FromSeconds(10)); + ILogManager logManager = LimboLogs.Instance; + + GenesisLoader loader = new( + genesisBuilder, + stateReader, + blockTree, + worldState, + worldStateManager, + blockchainProcessor, + config, + logManager + ); + + // Act & Assert - expect InvalidBlockException + Assert.Throws(() => loader.Load()); + + // Verify FlushCache was NOT called since genesis was invalid + worldStateManager.DidNotReceive().FlushCache(Arg.Any()); + } + + [Test] + public void Load_ShouldFlushCacheAfterScopeExit() + { + // Arrange + Block genesisBlock = Build.A.Block.Genesis.TestObject; + + IGenesisBuilder genesisBuilder = Substitute.For(); + genesisBuilder.Build().Returns(genesisBlock); + + IStateReader stateReader = Substitute.For(); + + IBlockTree blockTree = Substitute.For(); + bool scopeExited = false; + blockTree.When(x => x.SuggestBlock(Arg.Any())).Do(_ => + { + // Simulate block processing by triggering NewHeadBlock event + blockTree.NewHeadBlock += Raise.EventWith(blockTree, new BlockEventArgs(genesisBlock)); + }); + + IWorldState worldState = Substitute.For(); + IDisposable scopeDisposable = Substitute.For(); + scopeDisposable.When(x => x.Dispose()).Do(_ => scopeExited = true); + worldState.BeginScope(IWorldState.PreGenesis).Returns(scopeDisposable); + + IWorldStateManager worldStateManager = Substitute.For(); + worldStateManager.When(x => x.FlushCache(Arg.Any())).Do(_ => + { + // Verify that scope was exited before FlushCache was called + scopeExited.Should().BeTrue("FlushCache should be called after scope exit"); + }); + + IBlockchainProcessor blockchainProcessor = Substitute.For(); + + GenesisLoader.Config config = new(null, TimeSpan.FromSeconds(10)); + ILogManager logManager = LimboLogs.Instance; + + GenesisLoader loader = new( + genesisBuilder, + stateReader, + blockTree, + worldState, + worldStateManager, + blockchainProcessor, + config, + logManager + ); + + // Act + loader.Load(); + + // Assert - verify FlushCache was called after scope exit + worldStateManager.Received(1).FlushCache(Arg.Any()); + } +} diff --git a/src/Nethermind/Nethermind.Consensus.Test/MiningConfigurationTests.cs b/src/Nethermind/Nethermind.Consensus.Test/MiningConfigurationTests.cs deleted file mode 100644 index 81c31ee8c090..000000000000 --- a/src/Nethermind/Nethermind.Consensus.Test/MiningConfigurationTests.cs +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Config; -using Nethermind.Core.Exceptions; -using Nethermind.Init.Steps; -using NUnit.Framework; - -namespace Nethermind.Consensus.Test; - -public class MiningConfigurationTests -{ - [Test] - public void mining_configuration_updates_with_blocks_config_values() - { - IBlocksConfig blocksConfig = new BlocksConfig(); - IMiningConfig miningConfig = new MiningConfig(); - string testValue = "Test 123"; - blocksConfig.ExtraData = testValue; - MigrateConfigs.MigrateBlocksConfig(blocksConfig, miningConfig.BlocksConfig); - Assert.That(blocksConfig.ExtraData, Is.EqualTo(miningConfig.BlocksConfig.ExtraData)); - - //Change init order - IMiningConfig miningConfig2 = new MiningConfig(); - IBlocksConfig blocksConfig2 = new BlocksConfig(); - - string testValue2 = "Test 321"; - blocksConfig2.ExtraData = testValue2; - MigrateConfigs.MigrateBlocksConfig(blocksConfig2, miningConfig2.BlocksConfig); - Assert.That(blocksConfig2.ExtraData, Is.EqualTo(miningConfig2.BlocksConfig.ExtraData)); - - } - [Test] - public void blocks_configuration_updates_with_mining_config_values() - { - IBlocksConfig blocksConfig = new BlocksConfig(); - IMiningConfig miningConfig = new MiningConfig(); - string testValue = "Test 123"; - miningConfig.ExtraData = testValue; - MigrateConfigs.MigrateBlocksConfig(blocksConfig, miningConfig.BlocksConfig); - Assert.That(blocksConfig.ExtraData, Is.EqualTo(miningConfig.BlocksConfig.ExtraData)); - - //Change init order - IMiningConfig miningConfig2 = new MiningConfig(); - IBlocksConfig blocksConfig2 = new BlocksConfig(); - - string testValue2 = "Test 321"; - miningConfig2.ExtraData = testValue2; - MigrateConfigs.MigrateBlocksConfig(blocksConfig2, miningConfig2.BlocksConfig); - Assert.That(blocksConfig2.ExtraData, Is.EqualTo(miningConfig2.BlocksConfig.ExtraData)); - } - - [Test] - public void If_blocks_configuration_conflicts_with_mining_config_values_throw() - { - IMiningConfig miningConfig = new MiningConfig(); - IBlocksConfig blocksConfig = new BlocksConfig(); - - string testValueA = "Test 123"; - blocksConfig.ExtraData = testValueA; - - string testValueB = "Test 000"; - miningConfig.ExtraData = testValueB; - - Assert.Throws(() => MigrateConfigs.MigrateBlocksConfig(blocksConfig, miningConfig.BlocksConfig)); - } -} diff --git a/src/Nethermind/Nethermind.Consensus.Test/ReadOnlyTxProcessingScopeTests.cs b/src/Nethermind/Nethermind.Consensus.Test/ReadOnlyTxProcessingScopeTests.cs index b753ea688468..6c231ecdff3a 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/ReadOnlyTxProcessingScopeTests.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/ReadOnlyTxProcessingScopeTests.cs @@ -7,7 +7,6 @@ using Nethermind.Core; using Nethermind.Evm.TransactionProcessing; using Nethermind.Evm.State; -using Nethermind.State; using NSubstitute; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Consensus.Test/RecoverSignaturesTest.cs b/src/Nethermind/Nethermind.Consensus.Test/RecoverSignaturesTest.cs index 6f734f41f721..9f91b9a2fc1c 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/RecoverSignaturesTest.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/RecoverSignaturesTest.cs @@ -9,11 +9,10 @@ using Nethermind.Crypto; using Nethermind.Core.Specs; using Nethermind.Logging; -using Nethermind.TxPool; using System.Linq; -using Nethermind.Core.Crypto; namespace Nethermind.Consensus.Test; + [TestFixture] public class RecoverSignaturesTest { diff --git a/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs b/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs index 1e1a3306beca..f93dcb2af89a 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.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; @@ -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,17 +52,17 @@ 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) => { - counter++; + Interlocked.Increment(ref counter); await waitSignal.WaitAsync(token); - counter--; + Interlocked.Decrement(ref counter); }); - scheduler.ScheduleTask(1, async (_, token) => + scheduler.TryScheduleTask(1, async (_, token) => { - counter++; + Interlocked.Increment(ref counter); await waitSignal.WaitAsync(token); - counter--; + Interlocked.Decrement(ref counter); }); Assert.That(() => counter, Is.EqualTo(2).After(5000, 1)); @@ -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 @@ -98,15 +98,16 @@ public async Task Test_task_will_cancel_on_block_processing() } [Test] + [Retry(3)] public async Task Test_task_that_is_scheduled_during_block_processing_will_continue_after() { - await using BackgroundTaskScheduler scheduler = new BackgroundTaskScheduler(_branchProcessor, _chainHeadInfo, 2, 65536, LimboLogs.Instance); + await using BackgroundTaskScheduler scheduler = new(_branchProcessor, _chainHeadInfo, 2, 65536, LimboLogs.Instance); _branchProcessor.BlocksProcessing += Raise.EventWith(new BlocksProcessingEventArgs(null)); int executionCount = 0; for (int i = 0; i < 5; i++) { - scheduler.ScheduleTask(1, (_, token) => + scheduler.TryScheduleTask(1, (_, token) => { executionCount++; return Task.CompletedTask; @@ -117,18 +118,18 @@ public async Task Test_task_that_is_scheduled_during_block_processing_will_conti executionCount.Should().Be(0); _branchProcessor.BlockProcessed += Raise.EventWith(new BlockProcessedEventArgs(null, null)); - Assert.That(() => executionCount, Is.EqualTo(5).After(50, 1)); + Assert.That(() => executionCount, Is.EqualTo(5).After(1000, 10)); } [Test] public async Task Test_task_that_is_scheduled_during_block_processing_but_deadlined_will_get_called_and_cancelled() { - await using BackgroundTaskScheduler scheduler = new BackgroundTaskScheduler(_branchProcessor, _chainHeadInfo, 2, 65536, LimboLogs.Instance); + await using BackgroundTaskScheduler scheduler = new(_branchProcessor, _chainHeadInfo, 2, 65536, LimboLogs.Instance); _branchProcessor.BlocksProcessing += Raise.EventWith(new BlocksProcessingEventArgs(null)); bool wasCancelled = false; ManualResetEvent waitSignal = new ManualResetEvent(false); - scheduler.ScheduleTask(1, (_, token) => + scheduler.TryScheduleTask(1, (_, token) => { wasCancelled = token.IsCancellationRequested; waitSignal.Set(); @@ -141,4 +142,179 @@ public async Task Test_task_that_is_scheduled_during_block_processing_but_deadli wasCancelled.Should().BeTrue(); } + + [Test] + public async Task Test_expired_tasks_are_drained_during_block_processing() + { + int capacity = 16; + await using BackgroundTaskScheduler scheduler = new(_branchProcessor, _chainHeadInfo, 1, capacity, LimboLogs.Instance); + + // Start block processing — signal is reset, token cancelled + _branchProcessor.BlocksProcessing += Raise.EventWith(new BlocksProcessingEventArgs(null)); + + int cancelledCount = 0; + for (int i = 0; i < capacity; i++) + { + scheduler.TryScheduleTask(1, (_, token) => + { + if (token.IsCancellationRequested) + { + Interlocked.Increment(ref cancelledCount); + } + return Task.CompletedTask; + }, TimeSpan.FromMilliseconds(1)); + } + + // Expired tasks should be drained even while block processing is in progress + Assert.That(() => cancelledCount, Is.EqualTo(capacity).After(2000, 10)); + + _branchProcessor.BlockProcessed += Raise.EventWith(new BlockProcessedEventArgs(null, null)); + } + + [Test] + public async Task Test_queue_accepts_new_tasks_after_expired_tasks_drain_during_block_processing() + { + int capacity = 16; + await using BackgroundTaskScheduler scheduler = new(_branchProcessor, _chainHeadInfo, 1, capacity, LimboLogs.Instance); + + // Start block processing — signal is reset, token cancelled + _branchProcessor.BlocksProcessing += Raise.EventWith(new BlocksProcessingEventArgs(null)); + + // Fill the queue with short-lived tasks + for (int i = 0; i < capacity; i++) + { + scheduler.TryScheduleTask(1, (_, _) => Task.CompletedTask, TimeSpan.FromMilliseconds(1)).Should().BeTrue(); + } + + // Wait for deadlines to pass and expired tasks to be drained + await Task.Delay(200); + + // New tasks should be accepted because expired tasks freed up queue space + for (int i = 0; i < capacity; i++) + { + bool accepted = scheduler.TryScheduleTask(1, (_, _) => Task.CompletedTask, TimeSpan.FromMilliseconds(1)); + accepted.Should().BeTrue($"Task {i} should be accepted after expired tasks were drained"); + } + + _branchProcessor.BlockProcessed += Raise.EventWith(new BlockProcessedEventArgs(null, null)); + } + + [Test] + public async Task Test_high_capacity_queue_survives_repeated_block_processing_cycles() + { + int capacity = 1024; + int concurrency = 2; + await using BackgroundTaskScheduler scheduler = new(_branchProcessor, _chainHeadInfo, concurrency, capacity, LimboLogs.Instance); + + int executedCount = 0; + int cancelledCount = 0; + + // --- Phase 1: Fill the queue to capacity during block processing --- + _branchProcessor.BlocksProcessing += Raise.EventWith(new BlocksProcessingEventArgs(null)); + + for (int i = 0; i < capacity; i++) + { + bool accepted = scheduler.TryScheduleTask(1, (_, token) => + { + if (token.IsCancellationRequested) + Interlocked.Increment(ref cancelledCount); + else + Interlocked.Increment(ref executedCount); + return Task.CompletedTask; + }, TimeSpan.FromMilliseconds(10)); + accepted.Should().BeTrue($"Phase 1: task {i} should be accepted up to capacity"); + } + + // Wait for deadlines to expire and tasks to drain + Assert.That( + () => Volatile.Read(ref cancelledCount), + Is.EqualTo(capacity).After(5000, 10), + "all tasks should be drained with cancelled tokens during block processing"); + + // --- Phase 2: End block processing, verify queue accepts tasks and runs them normally --- + _branchProcessor.BlockProcessed += Raise.EventWith(new BlockProcessedEventArgs(null, null)); + + Interlocked.Exchange(ref executedCount, 0); + + int phase2Count = capacity / 2; + for (int i = 0; i < phase2Count; i++) + { + bool accepted = scheduler.TryScheduleTask(1, (_, _) => + { + Interlocked.Increment(ref executedCount); + return Task.CompletedTask; + }); + accepted.Should().BeTrue($"Phase 2: task {i} should be accepted after queue drained"); + } + + Assert.That( + () => Volatile.Read(ref executedCount), + Is.EqualTo(phase2Count).After(5000, 10), + "all phase 2 tasks should execute normally after block processing ends"); + + // --- Phase 3: Another block processing cycle with mixed short and long timeouts --- + _branchProcessor.BlocksProcessing += Raise.EventWith(new BlocksProcessingEventArgs(null)); + + int phase3CancelledCount = 0; + int phase3ExecutedCount = 0; + + // Short-lived tasks (will expire during block processing) + int shortLivedCount = capacity / 2; + for (int i = 0; i < shortLivedCount; i++) + { + scheduler.TryScheduleTask(1, (_, token) => + { + if (token.IsCancellationRequested) + Interlocked.Increment(ref phase3CancelledCount); + return Task.CompletedTask; + }, TimeSpan.FromMilliseconds(5)).Should().BeTrue($"Phase 3: short-lived task {i} should be accepted"); + } + + // Long-lived tasks (will survive until block processing ends) + int longLivedCount = capacity / 4; + for (int i = 0; i < longLivedCount; i++) + { + scheduler.TryScheduleTask(1, (_, token) => + { + if (!token.IsCancellationRequested) + Interlocked.Increment(ref phase3ExecutedCount); + return Task.CompletedTask; + }, TimeSpan.FromSeconds(30)).Should().BeTrue($"Phase 3: long-lived task {i} should be accepted"); + } + + // Wait for short-lived tasks to expire and drain + Assert.That( + () => Volatile.Read(ref phase3CancelledCount), + Is.EqualTo(shortLivedCount).After(5000, 10), + "short-lived tasks should drain with cancelled tokens during block processing"); + + // Long-lived tasks should not have executed yet (still waiting for block processing to end) + Volatile.Read(ref phase3ExecutedCount).Should().Be(0, + "long-lived tasks should wait during block processing"); + + // End block processing — long-lived tasks should now execute + _branchProcessor.BlockProcessed += Raise.EventWith(new BlockProcessedEventArgs(null, null)); + + Assert.That( + () => Volatile.Read(ref phase3ExecutedCount), + Is.EqualTo(longLivedCount).After(5000, 10), + "long-lived tasks should execute after block processing ends"); + + // --- Phase 4: Verify queue is fully operational with one more fill-and-drain --- + Interlocked.Exchange(ref executedCount, 0); + + for (int i = 0; i < capacity; i++) + { + scheduler.TryScheduleTask(1, (_, _) => + { + Interlocked.Increment(ref executedCount); + return Task.CompletedTask; + }).Should().BeTrue($"Phase 4: task {i} should be accepted in fully recovered queue"); + } + + Assert.That( + () => Volatile.Read(ref executedCount), + Is.EqualTo(capacity).After(5000, 10), + "all tasks in the final phase should execute successfully"); + } } diff --git a/src/Nethermind/Nethermind.Consensus.Test/ShareableTxProcessingSourceTests.cs b/src/Nethermind/Nethermind.Consensus.Test/ShareableTxProcessingSourceTests.cs index e515519257fd..e8d1c1f95a7d 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/ShareableTxProcessingSourceTests.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/ShareableTxProcessingSourceTests.cs @@ -7,7 +7,6 @@ using Nethermind.Core; using Nethermind.Core.Test.Builders; using Nethermind.Core.Test.Modules; -using Nethermind.Evm.TransactionProcessing; using NUnit.Framework; namespace Nethermind.Consensus.Test; diff --git a/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs b/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs index 882e58a7a4de..4761d0e9ee83 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/Comparers/BlobTxPriorityComparer.cs b/src/Nethermind/Nethermind.Consensus/Comparers/BlobTxPriorityComparer.cs new file mode 100644 index 000000000000..69fcaaf0cb82 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Comparers/BlobTxPriorityComparer.cs @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Core; +using Nethermind.TxPool.Comparison; + +namespace Nethermind.Consensus.Comparers; + +/// +/// Tie-breaker comparer that prefers blob transactions over non-blob transactions. +/// Returns if only x supports blobs, +/// if only y supports blobs, +/// or if both or neither support blobs. +/// +public sealed class BlobTxPriorityComparer : IComparer +{ + public static readonly BlobTxPriorityComparer Instance = new(); + + private BlobTxPriorityComparer() { } + + public int Compare(Transaction? x, Transaction? y) + { + if (ReferenceEquals(x, y)) return TxComparisonResult.Equal; + if (x is null) return TxComparisonResult.XFirst; + if (y is null) return TxComparisonResult.YFirst; + + return x.SupportsBlobs == y.SupportsBlobs ? TxComparisonResult.Equal : + x.SupportsBlobs ? TxComparisonResult.XFirst : TxComparisonResult.YFirst; + } +} diff --git a/src/Nethermind/Nethermind.Consensus/Comparers/GasPriceTxComparer.cs b/src/Nethermind/Nethermind.Consensus/Comparers/GasPriceTxComparer.cs index e23d16ae19d8..c53672d9b301 100644 --- a/src/Nethermind/Nethermind.Consensus/Comparers/GasPriceTxComparer.cs +++ b/src/Nethermind/Nethermind.Consensus/Comparers/GasPriceTxComparer.cs @@ -6,6 +6,7 @@ using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; +using Nethermind.TxPool.Comparison; namespace Nethermind.Consensus.Comparers { @@ -22,18 +23,18 @@ public GasPriceTxComparer(IBlockFinder blockFinder, ISpecProvider specProvider) public int Compare(Transaction? x, Transaction? y) { - if (ReferenceEquals(x, y)) return 0; - if (y is null) return 1; - if (x is null) return -1; + if (ReferenceEquals(x, y)) return TxComparisonResult.Equal; + if (y is null) return TxComparisonResult.YFirst; + if (x is null) return TxComparisonResult.XFirst; - // if gas bottleneck was calculated, it's highest priority for sorting + // if gas bottleneck was calculated, it's the highest priority for sorting // if not, different method of sorting by gas price is needed if (x.GasBottleneck is not null && y.GasBottleneck is not null) { return y!.GasBottleneck.Value.CompareTo(x!.GasBottleneck.GetValueOrDefault()); } - // When we're adding Tx to TxPool we don't know the base fee of the block in which transaction will be added. + // When we're adding Tx to TxPool, we don't know the base fee of the block in which transaction will be added. // We can get a base fee from the current head. Block block = _blockFinder.Head; bool isEip1559Enabled = _specProvider.GetSpecFor1559(block?.Number ?? 0L).IsEip1559Enabled; diff --git a/src/Nethermind/Nethermind.Consensus/Comparers/GasPriceTxComparerForProducer.cs b/src/Nethermind/Nethermind.Consensus/Comparers/GasPriceTxComparerForProducer.cs index 9c3e3d1f34be..2e007a8cb0be 100644 --- a/src/Nethermind/Nethermind.Consensus/Comparers/GasPriceTxComparerForProducer.cs +++ b/src/Nethermind/Nethermind.Consensus/Comparers/GasPriceTxComparerForProducer.cs @@ -10,21 +10,15 @@ namespace Nethermind.Consensus.Comparers { /// Block producer knows what will be base fee of next block. We can extract it from blockPreparationContextService and /// use to order transactions - public class GasPriceTxComparerForProducer : IComparer + public class GasPriceTxComparerForProducer( + BlockPreparationContext blockPreparationContext, + ISpecProvider specProvider) + : IComparer { - private readonly BlockPreparationContext _blockPreparationContext; - private readonly ISpecProvider _specProvider; - - public GasPriceTxComparerForProducer(BlockPreparationContext blockPreparationContext, ISpecProvider specProvider) - { - _blockPreparationContext = blockPreparationContext; - _specProvider = specProvider; - } - public int Compare(Transaction? x, Transaction? y) { - bool isEip1559Enabled = _specProvider.GetSpecFor1559(_blockPreparationContext.BlockNumber).IsEip1559Enabled; - return GasPriceTxComparerHelper.Compare(x, y, _blockPreparationContext.BaseFee, isEip1559Enabled); + bool isEip1559Enabled = specProvider.GetSpecFor1559(blockPreparationContext.BlockNumber).IsEip1559Enabled; + return GasPriceTxComparerHelper.Compare(x, y, blockPreparationContext.BaseFee, isEip1559Enabled); } } } diff --git a/src/Nethermind/Nethermind.Consensus/Comparers/GasPriceTxComparerHelper.cs b/src/Nethermind/Nethermind.Consensus/Comparers/GasPriceTxComparerHelper.cs index 7b4b67ded0da..56b96eb75f3c 100644 --- a/src/Nethermind/Nethermind.Consensus/Comparers/GasPriceTxComparerHelper.cs +++ b/src/Nethermind/Nethermind.Consensus/Comparers/GasPriceTxComparerHelper.cs @@ -3,6 +3,7 @@ using Nethermind.Core; using Nethermind.Int256; +using Nethermind.TxPool.Comparison; namespace Nethermind.Consensus.Comparers { @@ -10,17 +11,17 @@ public static class GasPriceTxComparerHelper { public static int Compare(Transaction? x, Transaction? y, in UInt256 baseFee, bool isEip1559Enabled) { - if (ReferenceEquals(x, y)) return 0; - if (y is null) return 1; - if (x is null) return -1; + if (ReferenceEquals(x, y)) return TxComparisonResult.Equal; + if (y is null) return TxComparisonResult.YFirst; + if (x is null) return TxComparisonResult.XFirst; // EIP1559 changed the way we're sorting transactions. The transaction with a higher miner tip should go first if (isEip1559Enabled) { UInt256 xGasPrice = UInt256.Min(x.MaxFeePerGas, x.MaxPriorityFeePerGas + baseFee); UInt256 yGasPrice = UInt256.Min(y.MaxFeePerGas, y.MaxPriorityFeePerGas + baseFee); - if (xGasPrice < yGasPrice) return 1; - if (xGasPrice > yGasPrice) return -1; + if (xGasPrice < yGasPrice) return TxComparisonResult.YFirst; + if (xGasPrice > yGasPrice) return TxComparisonResult.XFirst; return y.MaxFeePerGas.CompareTo(x.MaxFeePerGas); } diff --git a/src/Nethermind/Nethermind.Consensus/Comparers/TransactionComparerProvider.cs b/src/Nethermind/Nethermind.Consensus/Comparers/TransactionComparerProvider.cs index fc4f3c811e3c..42360f19aef7 100644 --- a/src/Nethermind/Nethermind.Consensus/Comparers/TransactionComparerProvider.cs +++ b/src/Nethermind/Nethermind.Consensus/Comparers/TransactionComparerProvider.cs @@ -9,42 +9,23 @@ namespace Nethermind.Consensus.Comparers { - public class TransactionComparerProvider : ITransactionComparerProvider + public class TransactionComparerProvider(ISpecProvider specProvider, IBlockFinder blockFinder) + : ITransactionComparerProvider { - private readonly ISpecProvider _specProvider; - private readonly IBlockFinder _blockFinder; - // we're caching default comparer private IComparer? _defaultComparer = null; - public TransactionComparerProvider(ISpecProvider specProvider, IBlockFinder blockFinder) - { - _specProvider = specProvider; - _blockFinder = blockFinder; - } - - public IComparer GetDefaultComparer() - { - if (_defaultComparer is null) - { - IComparer gasPriceComparer = new GasPriceTxComparer(_blockFinder, _specProvider); - _defaultComparer = gasPriceComparer - .ThenBy(CompareTxByTimestamp.Instance) - .ThenBy(CompareTxByPoolIndex.Instance) - .ThenBy(CompareTxByGasLimit.Instance); - } - - return _defaultComparer; - } + public IComparer GetDefaultComparer() => + _defaultComparer ??= new GasPriceTxComparer(blockFinder, specProvider) + .ThenBy(CompareTxByTimestamp.Instance) + .ThenBy(CompareTxByPoolIndex.Instance) + .ThenBy(CompareTxByGasLimit.Instance); - public IComparer GetDefaultProducerComparer(BlockPreparationContext blockPreparationContext) - { - IComparer gasPriceComparer = - new GasPriceTxComparerForProducer(blockPreparationContext, _specProvider); - return gasPriceComparer + public IComparer GetDefaultProducerComparer(BlockPreparationContext blockPreparationContext) => + new GasPriceTxComparerForProducer(blockPreparationContext, specProvider) + .ThenBy(BlobTxPriorityComparer.Instance) .ThenBy(CompareTxByTimestamp.Instance) .ThenBy(CompareTxByPoolIndex.Instance) .ThenBy(CompareTxByGasLimit.Instance); - } } } diff --git a/src/Nethermind/Nethermind.Consensus/ExecutionRequests/ExecutionRequestProcessor.cs b/src/Nethermind/Nethermind.Consensus/ExecutionRequests/ExecutionRequestProcessor.cs index b45d4c7299b6..757382c354d7 100644 --- a/src/Nethermind/Nethermind.Consensus/ExecutionRequests/ExecutionRequestProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/ExecutionRequests/ExecutionRequestProcessor.cs @@ -61,27 +61,36 @@ public void ProcessExecutionRequests(Block block, IWorldState state, TxReceipt[] if (!spec.RequestsEnabled || block.IsGenesis) return; - using ArrayPoolList requests = new(3); + ArrayPoolListRef requests = new(3); + try + { + ProcessDeposits(block, receipts, spec, ref requests); - ProcessDeposits(block, receipts, spec, requests); + if (spec.WithdrawalRequestsEnabled) + { + ReadRequests(block, state, spec.Eip7002ContractAddress, ref requests, _withdrawalTransaction, + ExecutionRequestType.WithdrawalRequest, + BlockErrorMessages.WithdrawalsContractEmpty, BlockErrorMessages.WithdrawalsContractFailed); + } - if (spec.WithdrawalRequestsEnabled) - { - ReadRequests(block, state, spec.Eip7002ContractAddress, requests, _withdrawalTransaction, ExecutionRequestType.WithdrawalRequest, - BlockErrorMessages.WithdrawalsContractEmpty, BlockErrorMessages.WithdrawalsContractFailed); - } + if (spec.ConsolidationRequestsEnabled) + { + ReadRequests(block, state, spec.Eip7251ContractAddress, ref requests, _consolidationTransaction, + ExecutionRequestType.ConsolidationRequest, + BlockErrorMessages.ConsolidationsContractEmpty, BlockErrorMessages.ConsolidationsContractFailed); + } - if (spec.ConsolidationRequestsEnabled) + block.ExecutionRequests = [.. requests]; + block.Header.RequestsHash = + ExecutionRequestExtensions.CalculateHashFromFlatEncodedRequests(block.ExecutionRequests); + } + finally { - ReadRequests(block, state, spec.Eip7251ContractAddress, requests, _consolidationTransaction, ExecutionRequestType.ConsolidationRequest, - BlockErrorMessages.ConsolidationsContractEmpty, BlockErrorMessages.ConsolidationsContractFailed); + requests.Dispose(); } - - block.ExecutionRequests = [.. requests]; - block.Header.RequestsHash = ExecutionRequestExtensions.CalculateHashFromFlatEncodedRequests(block.ExecutionRequests); } - private void ProcessDeposits(Block block, TxReceipt[] receipts, IReleaseSpec spec, ArrayPoolList requests) + private void ProcessDeposits(Block block, TxReceipt[] receipts, IReleaseSpec spec, ref ArrayPoolListRef requests) { if (!spec.DepositsEnabled) return; @@ -115,15 +124,15 @@ private void ProcessDeposits(Block block, TxReceipt[] receipts, IReleaseSpec spe private void DecodeDepositRequest(Block block, LogEntry log, Span buffer) { - object[] result = null; + object[] result; try { result = _abiEncoder.Decode(AbiEncodingStyle.None, DepositEventAbi, log.Data); ValidateLayout(result, block); } - catch (Exception e) when (e is AbiException or OverflowException) + catch (AbiException e) { - throw new InvalidBlockException(block, BlockErrorMessages.InvalidDepositEventLayout(e.Message)); + throw new InvalidBlockException(block, BlockErrorMessages.InvalidDepositEventLayout(e.Message), e); } int offset = 0; @@ -160,7 +169,7 @@ static void Validate(Block block, object obj, string name, int expectedSize) } } - private void ReadRequests(Block block, IWorldState state, Address contractAddress, ArrayPoolList requests, + private void ReadRequests(Block block, IWorldState state, Address contractAddress, ref ArrayPoolListRef requests, Transaction systemTx, ExecutionRequestType type, string contractEmptyError, string contractFailedError) { if (!state.HasCode(contractAddress)) diff --git a/src/Nethermind/Nethermind.Consensus/IBlockProducerFactory.cs b/src/Nethermind/Nethermind.Consensus/IBlockProducerFactory.cs index f89cef995db9..15ac802c7a32 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/IHeaderSigner.cs b/src/Nethermind/Nethermind.Consensus/IHeaderSigner.cs index a51b1ca800a3..8f4f854bd36a 100644 --- a/src/Nethermind/Nethermind.Consensus/IHeaderSigner.cs +++ b/src/Nethermind/Nethermind.Consensus/IHeaderSigner.cs @@ -5,6 +5,7 @@ using Nethermind.Core.Crypto; namespace Nethermind.Consensus; + public interface IHeaderSigner : ISigner { bool CanSignHeader { get; } diff --git a/src/Nethermind/Nethermind.Consensus/IMiningConfig.cs b/src/Nethermind/Nethermind.Consensus/IMiningConfig.cs index c7a39cec7f45..3bd63ff5145c 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; @@ -10,48 +9,6 @@ public interface IMiningConfig : IConfig { [ConfigItem(Description = "Whether to produce blocks.", DefaultValue = "false")] bool Enabled { get; set; } - - [ConfigItem( - Description = "Deprecated since v1.14.6. Please use Blocks.TargetBlockGasLimit. " + - "Values you set here are forwarded to it. " + - "Conflicting values will cause Exceptions. " + - "Block gas limit that the block producer should try to reach in the fastest " + - "possible way based on protocol rules. " + - "NULL value means that the miner should follow other miners.", - HiddenFromDocs = true, - DefaultValue = "null")] - long? TargetBlockGasLimit { get; set; } - - [ConfigItem( - Description = "Deprecated since v1.14.6. Please use Blocks.MinGasPrice " + - "Values you set here are forwarded to it. " + - "Conflicting values will cause Exceptions. " + - "Minimum gas premium for transactions accepted by the block producer. " + - "Before EIP1559: Minimum gas price for transactions accepted by the block producer.", - HiddenFromDocs = true, - DefaultValue = "1")] - UInt256 MinGasPrice { get; set; } - - [ConfigItem( - Description = "Deprecated since v1.14.6. Please use Blocks.RandomizedBlocks " + - "Values you set here are forwarded to it. " + - "Conflicting values will cause Exceptions. " + - "Only used in NethDev. Setting this to true will change the difficulty " + - "of the block randomly within the constraints.", - HiddenFromDocs = true, - DefaultValue = "false")] - bool RandomizedBlocks { get; set; } - - [ConfigItem(Description = "Deprecated since v1.14.6. Please use Blocks.ExtraData" + - "Values you set here are forwarded to it. " + - "Conflicting values will cause Exceptions. " + - "Block header extra data. 32-bytes shall be extra data max length.", - HiddenFromDocs = true, - DefaultValue = "Nethermind")] - string ExtraData { get; set; } - - [ConfigItem(HiddenFromDocs = true, DisabledForCli = true, DefaultValue = "null")] - IBlocksConfig? BlocksConfig { get; } [ConfigItem( Description = "The URL of an external signer like [Clef](https://github.com/ethereum/go-ethereum/blob/master/cmd/clef/tutorial.md).", HiddenFromDocs = false, diff --git a/src/Nethermind/Nethermind.Consensus/MiningConfig.cs b/src/Nethermind/Nethermind.Consensus/MiningConfig.cs index 76471d68f371..2e7b5177ef24 100644 --- a/src/Nethermind/Nethermind.Consensus/MiningConfig.cs +++ b/src/Nethermind/Nethermind.Consensus/MiningConfig.cs @@ -1,75 +1,10 @@ // 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 { public bool Enabled { get; set; } - - public long? TargetBlockGasLimit - { - get - { - return BlocksConfig.TargetBlockGasLimit; - } - set - { - BlocksConfig.TargetBlockGasLimit = value; - } - } - - public UInt256 MinGasPrice - { - get - { - return BlocksConfig.MinGasPrice; - } - set - { - BlocksConfig.MinGasPrice = value; - } - } - - public bool RandomizedBlocks - { - get - { - return BlocksConfig.RandomizedBlocks; - } - set - { - BlocksConfig.RandomizedBlocks = value; - } - } - - public string ExtraData - { - get - { - return BlocksConfig.ExtraData; - } - set - { - BlocksConfig.ExtraData = value; - } - } - - private IBlocksConfig? _blocksConfig = null; - - public IBlocksConfig? BlocksConfig - { - get - { - // Last initialisation due to the awaiting of interface defaults application on assembly - _blocksConfig ??= new BlocksConfig(); - - return _blocksConfig; - } - } - public string? Signer { get; set; } } diff --git a/src/Nethermind/Nethermind.Consensus/NullSigner.cs b/src/Nethermind/Nethermind.Consensus/NullSigner.cs index 8f5e70ba4da7..2210d9114353 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 ce6a7f705b08..50582c606ca1 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 5305d5ccdbed..61c90ff43f61 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockCachePreWarmer.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockCachePreWarmer.cs @@ -2,13 +2,13 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.ObjectPool; using Nethermind.Blockchain; using Nethermind.Config; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Core.Threading; using Nethermind.Evm; @@ -19,36 +19,36 @@ using Nethermind.Evm.State; using Nethermind.Core.Eip2930; using Nethermind.Core.Collections; +using Nethermind.Core.Extensions; using Nethermind.State; using Nethermind.Trie; 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 int _concurrencyLevel = concurrency == 0 ? Math.Min(Environment.ProcessorCount - 1, 16) : concurrency; + 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) { } @@ -56,8 +56,9 @@ public Task PreWarmCaches(Block suggestedBlock, BlockHeader? parent, IReleaseSpe { if (preBlockCaches is not null) { - _currentBlockState = new(this, suggestedBlock, parent, spec); CacheType result = preBlockCaches.ClearCaches(); + nodeStorageCache.ClearCaches(); + nodeStorageCache.Enabled = true; if (result != default) { if (_logger.IsWarn) _logger.Warn($"Caches {result} are not empty. Clearing them."); @@ -65,13 +66,14 @@ public Task PreWarmCaches(Block suggestedBlock, BlockHeader? parent, IReleaseSpe if (parent is not null && _concurrencyLevel > 1 && !cancellationToken.IsCancellationRequested) { + BlockState blockState = new(this, suggestedBlock, parent, spec); ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = _concurrencyLevel, CancellationToken = cancellationToken }; // Run address warmer ahead of transactions warmer, but queue to ThreadPool so it doesn't block the txs var addressWarmer = new AddressWarmer(parallelOptions, suggestedBlock, parent, spec, systemAccessLists, this); ThreadPool.UnsafeQueueUserWorkItem(addressWarmer, preferLocal: false); - // Do not pass cancellation token to the task, we don't want exceptions to be thrown in main processing thread - return Task.Run(() => PreWarmCachesParallel(_currentBlockState, suggestedBlock, parent, spec, parallelOptions, addressWarmer, cancellationToken)); + // Do not pass the cancellation token to the task, we don't want exceptions to be thrown in the main processing thread + return Task.Run(() => PreWarmCachesParallel(blockState, suggestedBlock, parent, spec, parallelOptions, addressWarmer, cancellationToken)); } } @@ -82,12 +84,12 @@ public CacheType ClearCaches() { if (_logger.IsDebug) _logger.Debug("Clearing caches"); CacheType cachesCleared = preBlockCaches?.ClearCaches() ?? default; + cachesCleared |= nodeStorageCache.ClearCaches() ? CacheType.Rlp : CacheType.None; if (_logger.IsDebug) _logger.Debug($"Cleared caches: {cachesCleared}"); - return cachesCleared; } - private void PreWarmCachesParallel(BlockStateSource blockState, Block suggestedBlock, BlockHeader parent, IReleaseSpec spec, ParallelOptions parallelOptions, AddressWarmer addressWarmer, CancellationToken cancellationToken) + private void PreWarmCachesParallel(BlockState blockState, Block suggestedBlock, BlockHeader parent, IReleaseSpec spec, ParallelOptions parallelOptions, AddressWarmer addressWarmer, CancellationToken cancellationToken) { try { @@ -106,16 +108,11 @@ private void PreWarmCachesParallel(BlockStateSource blockState, Block suggestedB } finally { - // Don't compete task until address warmer is also done. + // Don't compete the task until address warmer is also done. addressWarmer.Wait(); } } - public void OnBeforeTxExecution(Transaction transaction) - { - _currentBlockState?.IncrementTransactionCounter(); - } - private void WarmupWithdrawals(ParallelOptions parallelOptions, IReleaseSpec spec, Block block, BlockHeader? parent) { if (parallelOptions.CancellationToken.IsCancellationRequested) return; @@ -124,21 +121,21 @@ private void WarmupWithdrawals(ParallelOptions parallelOptions, IReleaseSpec spe { if (spec.WithdrawalsEnabled && block.Withdrawals is not null) { - ParallelUnbalancedWork.For(0, block.Withdrawals.Length, parallelOptions, (envPool: _envPool, block, parent), + ParallelUnbalancedWork.For(0, block.Withdrawals.Length, parallelOptions, (EnvPool: _envPool, Block: block, Parent: parent), static (i, state) => { - IReadOnlyTxProcessorSource env = state.envPool.Get(); + IReadOnlyTxProcessorSource env = state.EnvPool.Get(); try { - using IReadOnlyTxProcessingScope scope = env.Build(state.parent); - scope.WorldState.WarmUp(state.block.Withdrawals[i].Address); + using IReadOnlyTxProcessingScope scope = env.Build(state.Parent); + scope.WorldState.WarmUp(state.Block.Withdrawals![i].Address); } catch (MissingTrieNodeException) { } finally { - state.envPool.Return(env); + state.EnvPool.Return(env); } return state; @@ -151,75 +148,67 @@ private void WarmupWithdrawals(ParallelOptions parallelOptions, IReleaseSpec spe } catch (Exception ex) { - if (_logger.IsDebug) _logger.Error($"DEBUG/ERROR Error pre-warming withdrawal", ex); + if (_logger.IsDebug) _logger.Error("DEBUG/ERROR Error pre-warming withdrawal", ex); } } - private void WarmupTransactions(BlockStateSource blockState, ParallelOptions parallelOptions) + private void WarmupTransactions(BlockState blockState, ParallelOptions parallelOptions) { if (parallelOptions.CancellationToken.IsCancellationRequested) return; try { Block block = blockState.Block; - ParallelUnbalancedWork.For( - 0, - block.Transactions.Length, - parallelOptions, - blockState.InitThreadState, - static (i, state) => - { - Transaction? tx = null; - try - { - // If the transaction has already been processed or being processed, exit early - if (state.LastExecutedTransaction >= i) - { - return state; - } + if (block.Transactions.Length == 0) return; - tx = state.Block.Transactions[i]; + // Group transactions by sender to process same-sender transactions sequentially + // This ensures state changes (balance, storage) from tx[N] are visible to tx[N+1] + Dictionary>? senderGroups = GroupTransactionsBySender(block); - Address senderAddress = tx.SenderAddress!; - IWorldState worldState = state.Scope.WorldState; - if (!worldState.AccountExists(senderAddress)) - { - worldState.CreateAccountIfNotExists(senderAddress, UInt256.Zero); - } + try + { + // Convert to array for parallel iteration + ArrayPoolList> groupArray = senderGroups.Values.ToPooledList(); - UInt256 nonceDelta = UInt256.Zero; - for (int prev = 0; prev < i; prev++) + // Parallel across different senders, sequential within the same sender + ParallelUnbalancedWork.For( + 0, + groupArray.Count, + parallelOptions, + (blockState, groupArray, parallelOptions.CancellationToken), + static (groupIndex, tupleState) => { - if (senderAddress == state.Block.Transactions[prev].SenderAddress) + (BlockState? blockState, ArrayPoolList> groups, CancellationToken token) = tupleState; + ArrayPoolList<(int Index, Transaction Tx)>? txList = groups[groupIndex]; + + // Get thread-local processing state for this sender's transactions + IReadOnlyTxProcessorSource env = blockState.PreWarmer._envPool.Get(); + try { - nonceDelta++; + using IReadOnlyTxProcessingScope scope = env.Build(blockState.Parent); + BlockExecutionContext context = new(blockState.Block.Header, blockState.Spec); + scope.TransactionProcessor.SetBlockExecutionContext(context); + + // Sequential within the same sender-state changes propagate correctly + foreach ((int txIndex, Transaction? tx) in txList.AsSpan()) + { + if (token.IsCancellationRequested) return tupleState; + WarmupSingleTransaction(scope, tx, txIndex, blockState); + } + } + finally + { + blockState.PreWarmer._envPool.Return(env); } - } - - if (!nonceDelta.IsZero) - { - worldState.IncrementNonce(senderAddress, nonceDelta); - } - - if (state.Spec.UseTxAccessLists) - { - worldState.WarmUp(tx.AccessList); // eip-2930 - } - TransactionResult result = state.Scope.TransactionProcessor.Warmup(tx, NullTxTracer.Instance); - if (state.Logger.IsTrace) state.Logger.Trace($"Finished pre-warming cache for tx[{i}] {tx.Hash} with {result}"); - } - catch (Exception ex) when (ex is EvmException or OverflowException) - { - // Ignore, regular tx processing exceptions - } - catch (Exception ex) - { - if (state.Logger.IsDebug) state.Logger.Error($"DEBUG/ERROR Error pre-warming cache {tx?.Hash}", ex); - } - return state; - }, - BlockStateSource.FinallyAction); + return tupleState; + }); + } + finally + { + foreach (KeyValuePair> kvp in senderGroups) + kvp.Value.Dispose(); + } } catch (OperationCanceledException) { @@ -227,7 +216,62 @@ private void WarmupTransactions(BlockStateSource blockState, ParallelOptions par } catch (Exception ex) { - if (_logger.IsDebug) _logger.Error($"DEBUG/ERROR Error pre-warming withdrawal", ex); + if (_logger.IsDebug) _logger.Error($"DEBUG/ERROR Error pre-warming transactions", ex); + } + } + + private static Dictionary> GroupTransactionsBySender(Block block) + { + Dictionary> groups = new(); + + for (int i = 0; i < block.Transactions.Length; i++) + { + Transaction tx = block.Transactions[i]; + Address sender = tx.SenderAddress!; + + if (!groups.TryGetValue(sender, out ArrayPoolList<(int, Transaction)> list)) + { + list = new(4); + groups[sender] = list; + } + list.Add((i, tx)); + } + + return groups; + } + + private static void WarmupSingleTransaction( + IReadOnlyTxProcessingScope scope, + Transaction tx, + int txIndex, + BlockState blockState) + { + try + { + Address senderAddress = tx.SenderAddress!; + IWorldState worldState = scope.WorldState; + + if (!worldState.AccountExists(senderAddress)) + { + worldState.CreateAccountIfNotExists(senderAddress, UInt256.Zero); + } + + if (blockState.Spec.UseTxAccessLists) + { + worldState.WarmUp(tx.AccessList); // eip-2930 + } + + TransactionResult result = scope.TransactionProcessor.Warmup(tx, NullTxTracer.Instance); + + if (blockState.PreWarmer._logger.IsTrace) blockState.PreWarmer._logger.Trace($"Finished pre-warming cache for tx[{txIndex}] {tx.Hash} with {result}"); + } + catch (Exception ex) when (ex is EvmException or OverflowException) + { + // Ignore, regular tx processing exceptions + } + catch (Exception ex) + { + if (blockState.PreWarmer._logger.IsDebug) blockState.PreWarmer._logger.Error($"DEBUG/ERROR Error pre-warming cache {tx.Hash}", ex); } } @@ -235,7 +279,6 @@ private class AddressWarmer(ParallelOptions parallelOptions, Block block, BlockH : IThreadPoolWorkItem { private readonly Block Block = block; - private readonly Hash256 StateRoot = parent.StateRoot; private readonly BlockCachePreWarmer PreWarmer = preWarmer; private readonly ArrayPoolList? SystemTxAccessLists = GetAccessLists(block, spec, systemAccessLists); private readonly ManualResetEventSlim _doneEvent = new(initialState: false); @@ -277,7 +320,7 @@ private void WarmupAddresses(ParallelOptions parallelOptions, Block block) { if (parallelOptions.CancellationToken.IsCancellationRequested) { - SystemTxAccessLists.Dispose(); + SystemTxAccessLists?.Dispose(); return; } @@ -286,7 +329,7 @@ private void WarmupAddresses(ParallelOptions parallelOptions, Block block) { if (SystemTxAccessLists is not null) { - var env = envPool.Get(); + IReadOnlyTxProcessorSource env = envPool.Get(); try { using IReadOnlyTxProcessingScope scope = env.Build(parent); @@ -319,13 +362,13 @@ private void WarmupAddresses(ParallelOptions parallelOptions, Block block) { if (sender is not null) { - state.Scope.WorldState.WarmUp(sender); + state.Scope!.WorldState.WarmUp(sender); } Address to = tx.To; if (to is not null) { - state.Scope.WorldState.WarmUp(to); + state.Scope!.WorldState.WarmUp(to); } } catch (MissingTrieNodeException) @@ -347,12 +390,12 @@ private readonly struct AddressWarmingState(ObjectPool FinallyAction { get; } = DisposeThreadState; - public readonly ObjectPool EnvPool = envPool; + private readonly ObjectPool EnvPool = envPool; + private readonly IReadOnlyTxProcessorSource? Env; public readonly Block Block = block; - public readonly IReadOnlyTxProcessorSource? Env; public readonly IReadOnlyTxProcessingScope? Scope; - public AddressWarmingState(ObjectPool envPool, Block block, BlockHeader parent, IReadOnlyTxProcessorSource env, IReadOnlyTxProcessingScope scope) : this(envPool, block, parent) + private AddressWarmingState(ObjectPool envPool, Block block, BlockHeader parent, IReadOnlyTxProcessorSource env, IReadOnlyTxProcessingScope scope) : this(envPool, block, parent) { Env = env; Scope = scope; @@ -366,65 +409,21 @@ public AddressWarmingState InitThreadState() public void Dispose() { - Scope.Dispose(); - EnvPool.Return(Env); + Scope?.Dispose(); + if (Env is not null) + { + EnvPool.Return(Env); + } } 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; } - private class BlockStateSource(BlockCachePreWarmer preWarmer, Block block, BlockHeader parent, IReleaseSpec spec) - { - public static Action FinallyAction { get; } = DisposeThreadState; - - public readonly BlockCachePreWarmer PreWarmer = preWarmer; - public readonly Block Block = block; - public readonly BlockHeader Parent = parent; - public readonly IReleaseSpec Spec = spec; - public volatile int LastExecutedTransaction = 0; - - public BlockState InitThreadState() - { - return new(this); - } - - private static void DisposeThreadState(BlockState state) => state.Dispose(); - - public void IncrementTransactionCounter() - { - Interlocked.Increment(ref LastExecutedTransaction); - } - } - - private readonly struct BlockState - { - private readonly BlockStateSource Src; - public readonly IReadOnlyTxProcessorSource Env; - public readonly IReadOnlyTxProcessingScope Scope; - - public ref readonly ILogger Logger => ref Src.PreWarmer._logger; - public IReleaseSpec Spec => Src.Spec; - public Block Block => Src.Block; - public int LastExecutedTransaction => Src.LastExecutedTransaction; - - public BlockState(BlockStateSource src) - { - Src = src; - Env = src.PreWarmer._envPool.Get(); - Scope = Env.Build(src.Parent); - Scope.TransactionProcessor.SetBlockExecutionContext(new BlockExecutionContext(Block.Header, Spec)); - } - - public void Dispose() - { - Scope.Dispose(); - Src.PreWarmer._envPool.Return(Env); - } - } + private record BlockState(BlockCachePreWarmer PreWarmer, Block Block, BlockHeader Parent, IReleaseSpec Spec); } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionPicker.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionPicker.cs index d06684f97b2c..b1ff6fdd855e 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionPicker.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionPicker.cs @@ -78,7 +78,7 @@ public virtual AddingTxEventArgs CanAddTransaction(Block block, Transaction curr IReleaseSpec spec = _specProvider.GetSpec(block.Header); if (currentTx.IsAboveInitCode(spec)) { - return args.Set(TxAction.Skip, TransactionResult.TransactionSizeOverMaxInitCodeSize.Error); + return args.Set(TxAction.Skip, TransactionResult.TransactionSizeOverMaxInitCodeSize.ErrorDescription); } if (!_ignoreEip3607 && stateProvider.IsInvalidContractSender(spec, currentTx.SenderAddress)) diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionsExecutor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionsExecutor.cs index 19f15ffb97d0..8071dc34907a 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; @@ -56,7 +55,7 @@ public virtual TxReceipt[] ProcessTransactions(Block block, ProcessingOptions pr int txCount = blockToProduce is not null ? defaultTxCount : block.Transactions.Length; IEnumerable transactions = blockToProduce?.Transactions ?? block.Transactions; - using ArrayPoolList includedTx = new(txCount); + using ArrayPoolListRef includedTx = new(txCount); HashSet consideredTx = new(ByHashTxComparer.Instance); int i = 0; @@ -112,7 +111,7 @@ private TxAction ProcessTransaction( } else { - args.Set(TxAction.Skip, result.Error!); + args.Set(TxAction.Skip, result.ErrorDescription!); } } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs index 0f1dc661c729..31c97c9d2639 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs @@ -1,10 +1,8 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Threading; using Nethermind.Blockchain; using Nethermind.Blockchain.Tracing; @@ -45,14 +43,14 @@ public TxReceipt[] ProcessTransactions(Block block, ProcessingOptions processing protected virtual void ProcessTransaction(Block block, Transaction currentTx, int index, BlockReceiptsTracer receiptsTracer, ProcessingOptions processingOptions) { TransactionResult result = transactionProcessor.ProcessTransaction(currentTx, receiptsTracer, processingOptions, stateProvider); - if (!result) ThrowInvalidBlockException(result, block.Header, currentTx, index); + if (!result) ThrowInvalidTransactionException(result, block.Header, currentTx, index); transactionProcessedEventHandler?.OnTransactionProcessed(new TxProcessedEventArgs(index, currentTx, block.Header, receiptsTracer.TxReceipts[index])); } [DoesNotReturn, StackTraceHidden] - private void ThrowInvalidBlockException(TransactionResult result, BlockHeader header, Transaction currentTx, int index) + private void ThrowInvalidTransactionException(TransactionResult result, BlockHeader header, Transaction currentTx, int index) { - throw new InvalidBlockException(header, $"Transaction {currentTx.Hash} at index {index} failed with error {result.Error}"); + throw new InvalidTransactionException(header, $"Transaction {currentTx.Hash} at index {index} failed with error {result.ErrorDescription}", result); } /// diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs index f9b5d930d6ed..cc2ab84b814d 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs @@ -44,7 +44,7 @@ public partial class BlockProcessor( : IBlockProcessor { private readonly ILogger _logger = logManager.GetClassLogger(); - protected readonly WorldStateMetricsDecorator _stateProvider = new WorldStateMetricsDecorator(stateProvider); + protected readonly WorldStateMetricsDecorator _stateProvider = new(stateProvider); private readonly IReceiptsRootCalculator _receiptsRootCalculator = ReceiptsRootCalculator.Instance; /// @@ -82,7 +82,7 @@ private void ValidateProcessedBlock(Block suggestedBlock, ProcessingOptions opti suggestedBlock.ExecutionRequests = block.ExecutionRequests; } - private bool ShouldComputeStateRoot(BlockHeader header) => + protected bool ShouldComputeStateRoot(BlockHeader header) => !header.IsGenesis || !specProvider.GenesisStateUnavailable; protected virtual TxReceipt[] ProcessBlock( @@ -179,7 +179,7 @@ private void StoreTxReceipts(Block block, TxReceipt[] txReceipts, IReleaseSpec s receiptStorage.Insert(block, txReceipts, spec, false); } - private Block PrepareBlockForProcessing(Block suggestedBlock) + protected virtual Block PrepareBlockForProcessing(Block suggestedBlock) { if (_logger.IsTrace) _logger.Trace($"{suggestedBlock.Header.ToString(BlockHeader.Format.Full)}"); BlockHeader bh = suggestedBlock.Header; diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs index 22d7b1c2f896..77ae74dddf2a 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs @@ -57,14 +57,16 @@ public sealed class BlockchainProcessor : IBlockchainProcessor, IBlockProcessing new BoundedChannelOptions(MaxProcessingQueueSize) { // Optimize for single reader concurrency - SingleReader = true + SingleReader = true, + // If queues are empty we want the block processing to continue on NewPayload thread and inherit its priority + AllowSynchronousContinuations = true, }); private bool _recoveryComplete = false; private int _queueCount; private bool _disposed; - private readonly ProcessingStats _stats; + private readonly IProcessingStats _stats; private CancellationTokenSource? _loopCancellationSource; private Task? _recoveryTask; @@ -89,13 +91,15 @@ public sealed class BlockchainProcessor : IBlockchainProcessor, IBlockProcessing /// /// /// + /// public BlockchainProcessor( IBlockTree blockTree, IBranchProcessor branchProcessor, IBlockPreprocessorStep recoveryStep, IStateReader stateReader, ILogManager logManager, - Options options) + Options options, + IProcessingStats processingStats) { _logger = logManager.GetClassLogger(); _blockTree = blockTree; @@ -104,7 +108,7 @@ public BlockchainProcessor( _stateReader = stateReader; _options = options; - _stats = new ProcessingStats(stateReader, logManager.GetClassLogger()); + _stats = processingStats; _loopCancellationSource = new CancellationTokenSource(); _stats.NewProcessingStatistics += OnNewProcessingStatistics; } @@ -143,6 +147,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 +430,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); @@ -670,7 +677,7 @@ static void ThrowOrphanedBlock(Block firstBlock) private ProcessingBranch PrepareProcessingBranch(Block suggestedBlock, ProcessingOptions options) { - BlockHeader branchingPoint = null; + BlockHeader? branchingPoint = null; ArrayPoolList blocksToBeAddedToMain = new((int)Reorganization.PersistenceInterval); bool branchingCondition; @@ -774,7 +781,7 @@ void TraceBranchingConditions(BlockHeader branchingPoint, bool notFoundTheBranch } [MethodImpl(MethodImplOptions.NoInlining)] - void TraceBranchingPoint(BlockHeader branchingPoint) + void TraceBranchingPoint(BlockHeader? branchingPoint) { if (branchingPoint is not null && branchingPoint.Hash != _blockTree.Head?.Hash) { @@ -800,7 +807,7 @@ void TraceParentBlock(Block toBeProcessed) => _logger.Trace($"Found parent {toBeProcessed?.ToString(Block.Format.Short)}"); [MethodImpl(MethodImplOptions.NoInlining)] - void TraceStateRootLookup(Hash256 stateRoot) + void TraceStateRootLookup(Hash256? stateRoot) => _logger.Trace($"State root lookup: {stateRoot}"); [DoesNotReturn, StackTraceHidden] diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BranchProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BranchProcessor.cs index 7160f10badc4..719adc623677 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BranchProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BranchProcessor.cs @@ -9,6 +9,7 @@ using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; +using Nethermind.Evm; using Nethermind.Evm.State; using Nethermind.Evm.Tracing; using Nethermind.Logging; @@ -22,6 +23,7 @@ public class BranchProcessor( ISpecProvider specProvider, IWorldState stateProvider, IBeaconBlockRootHandler beaconBlockRootHandler, + IBlockhashProvider blockhashProvider, ILogManager logManager, IBlockCachePreWarmer? preWarmer = null) : IBranchProcessor @@ -49,7 +51,6 @@ public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlo { if (suggestedBlocks.Count == 0) return []; - BlockHeader? previousBranchStateRoot = baseBlock; Block suggestedBlock = suggestedBlocks[0]; IDisposable? worldStateCloser = null; @@ -57,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 { @@ -69,24 +70,29 @@ public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlo } else { - worldStateCloser = stateProvider.BeginScope(baseBlock); + BlockHeader? scopeBaseBlock = baseBlock ?? (suggestedBlock.IsGenesis ? suggestedBlock.Header : null); + worldStateCloser = stateProvider.BeginScope(scopeBaseBlock); } - // Start prewarming as early as possible - WaitForCacheClear(); - IReleaseSpec spec = specProvider.GetSpec(suggestedBlock.Header); - (CancellationTokenSource? prewarmCancellation, Task? preWarmTask) - = PreWarmTransactions(suggestedBlock, baseBlock, spec); + CancellationTokenSource? backgroundCancellation = new(); + Task? preWarmTask = null; - BlocksProcessing?.Invoke(this, new BlocksProcessingEventArgs(suggestedBlocks)); - - BlockHeader? preBlockBaseBlock = baseBlock; - - bool notReadOnly = !options.ContainsFlag(ProcessingOptions.ReadOnlyChain); - int blocksCount = suggestedBlocks.Count; - Block[] processedBlocks = new Block[blocksCount]; try { + // Start prewarming as early as possible + WaitForCacheClear(); + IReleaseSpec spec = specProvider.GetSpec(suggestedBlock.Header); + preWarmTask = PreWarmTransactions(suggestedBlock, baseBlock!, spec, backgroundCancellation.Token); + Task? prefetchBlockhash = blockhashProvider.Prefetch(suggestedBlock.Header, backgroundCancellation.Token); + + BlocksProcessing?.Invoke(this, new BlocksProcessingEventArgs(suggestedBlocks)); + + BlockHeader? preBlockBaseBlock = baseBlock; + + bool notReadOnly = !options.ContainsFlag(ProcessingOptions.ReadOnlyChain); + int blocksCount = suggestedBlocks.Count; + Block[] processedBlocks = new Block[blocksCount]; + for (int i = 0; i < blocksCount; i++) { WaitForCacheClear(); @@ -98,10 +104,9 @@ public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlo } // If prewarmCancellation is not null it means we are in first iteration of loop // and started prewarming at method entry, so don't start it again - if (prewarmCancellation is null) - { - (prewarmCancellation, preWarmTask) = PreWarmTransactions(suggestedBlock, preBlockBaseBlock, spec); - } + backgroundCancellation ??= new CancellationTokenSource(); + preWarmTask ??= PreWarmTransactions(suggestedBlock, preBlockBaseBlock, spec, backgroundCancellation.Token); + prefetchBlockhash ??= blockhashProvider.Prefetch(suggestedBlock.Header, backgroundCancellation.Token); if (blocksCount > 64 && i % 8 == 0) { @@ -116,11 +121,9 @@ public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlo Block processedBlock; TxReceipt[] receipts; - if (prewarmCancellation is not null) + if (preWarmTask is not null) { (processedBlock, receipts) = blockProcessor.ProcessOne(suggestedBlock, options, blockTracer, spec, token); - // Block is processed, we can cancel the prewarm task - CancellationTokenExtensions.CancelDisposeAndClear(ref prewarmCancellation); } else { @@ -133,6 +136,9 @@ public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlo (processedBlock, receipts) = blockProcessor.ProcessOne(suggestedBlock, options, blockTracer, spec, token); } + // Block is processed, we can cancel background tasks + CancellationTokenExtensions.CancelDisposeAndClear(ref backgroundCancellation); + processedBlocks[i] = processedBlock; // be cautious here as AuRa depends on processing @@ -153,16 +159,17 @@ public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlo if (isCommitPoint && notReadOnly) { if (_logger.IsInfo) _logger.Info($"Commit part of a long blocks branch {i}/{blocksCount}"); - previousBranchStateRoot = suggestedBlock.Header; + BlockHeader previousBranchStateRoot = suggestedBlock.Header; - worldStateCloser.Dispose(); + worldStateCloser?.Dispose(); worldStateCloser = stateProvider.BeginScope(previousBranchStateRoot); } preBlockBaseBlock = processedBlock.Header; // Make sure the prewarm task is finished before we reset the state - preWarmTask?.GetAwaiter().GetResult(); - preWarmTask = null; + WaitAndClear(ref preWarmTask); + prefetchBlockhash = null; + _stateProvider.Reset(); // Calculate the transaction hashes in the background and release tx sequence memory @@ -176,31 +183,32 @@ public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlo catch (Exception ex) // try to restore at all cost { if (_logger.IsWarn) _logger.Warn($"Encountered exception {ex} while processing blocks."); - CancellationTokenExtensions.CancelDisposeAndClear(ref prewarmCancellation); + CancellationTokenExtensions.CancelDisposeAndClear(ref backgroundCancellation); QueueClearCaches(preWarmTask); - preWarmTask?.GetAwaiter().GetResult(); + WaitAndClear(ref preWarmTask); throw; } finally { worldStateCloser?.Dispose(); } - } - - private (CancellationTokenSource prewarmCancellation, Task preWarmTask) PreWarmTransactions(Block suggestedBlock, BlockHeader preBlockBaseBlock, IReleaseSpec spec) - { - if (preWarmer is null || suggestedBlock.Transactions.Length < 3) return (null, null); - CancellationTokenSource prewarmCancellation = new(); - Task preWarmTask = preWarmer.PreWarmCaches(suggestedBlock, - preBlockBaseBlock, - spec, - prewarmCancellation.Token, - beaconBlockRootHandler); - - return (prewarmCancellation, preWarmTask); + static void WaitAndClear(ref Task? task) + { + task?.GetAwaiter().GetResult(); + task = null; + } } + private Task? PreWarmTransactions(Block suggestedBlock, BlockHeader preBlockBaseBlock, IReleaseSpec spec, CancellationToken token) => + suggestedBlock.Transactions.Length < 3 + ? null + : preWarmer?.PreWarmCaches(suggestedBlock, + preBlockBaseBlock, + spec, + token, + beaconBlockRootHandler); + private void WaitForCacheClear() => _clearTask.GetAwaiter().GetResult(); private void QueueClearCaches(Task? preWarmTask) diff --git a/src/Nethermind/Nethermind.Consensus/Processing/CensorshipDetector/CensorshipDetector.cs b/src/Nethermind/Nethermind.Consensus/Processing/CensorshipDetector/CensorshipDetector.cs index cc56f546338b..b225e7e5cb72 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 c743aefa99ef..50d14deb9549 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/GenesisLoader.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/GenesisLoader.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Diagnostics; using System.Threading; using Nethermind.Blockchain; using Nethermind.Config; @@ -18,6 +19,7 @@ public class GenesisLoader( IStateReader stateReader, IBlockTree blockTree, IWorldState worldState, + IWorldStateManager worldStateManager, IBlockchainProcessor blockchainProcessor, GenesisLoader.Config genesisConfig, ILogManager logManager @@ -29,6 +31,12 @@ public record Config(Hash256? ExpectedGenesisHash, TimeSpan GenesisTimeout); ILogger _logger = logManager.GetClassLogger(); public void Load() + { + DoLoad(); + worldStateManager.FlushCache(CancellationToken.None); + } + + private void DoLoad() { using var _ = worldState.BeginScope(IWorldState.PreGenesis); @@ -56,7 +64,7 @@ void GenesisProcessed(object? sender, BlockEventArgs args) blockTree.NewHeadBlock += GenesisProcessed; blockTree.SuggestBlock(genesis); - bool genesisLoaded = genesisProcessedEvent.Wait(genesisConfig.GenesisTimeout); + bool genesisLoaded = genesisProcessedEvent.Wait(Debugger.IsAttached ? TimeSpan.FromMilliseconds(Timeout.Infinite) : genesisConfig.GenesisTimeout); if (!genesisLoaded) { throw new TimeoutException($"Genesis block was not processed after {genesisConfig.GenesisTimeout.TotalSeconds} seconds. If you are running custom chain with very big genesis file consider increasing {nameof(BlocksConfig)}.{nameof(IBlocksConfig.GenesisTimeoutMs)}."); @@ -76,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/IBlockCachePreWarmer.cs b/src/Nethermind/Nethermind.Consensus/Processing/IBlockCachePreWarmer.cs index a62b840652a0..2a3715132938 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/IBlockCachePreWarmer.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/IBlockCachePreWarmer.cs @@ -5,7 +5,6 @@ using System.Threading; using System.Threading.Tasks; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Core.Eip2930; using Nethermind.Core.Specs; using Nethermind.State; diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IBlockProcessingQueue.cs b/src/Nethermind/Nethermind.Consensus/Processing/IBlockProcessingQueue.cs index 7ca72e70d41f..3312d286e23e 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/IBlockProcessingQueueExtensions.cs b/src/Nethermind/Nethermind.Consensus/Processing/IBlockProcessingQueueExtensions.cs new file mode 100644 index 000000000000..36309b4d35eb --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Processing/IBlockProcessingQueueExtensions.cs @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Core.Events; + +namespace Nethermind.Consensus.Processing; + +public static class BlockProcessingQueueExtensions +{ + public static async Task WaitForBlockProcessing(this IBlockProcessingQueue blockProcessingQueue, CancellationToken cancellationToken = default) + { + if (!blockProcessingQueue.IsEmpty) + { + await Wait.ForEvent(cancellationToken, + e => blockProcessingQueue.ProcessingQueueEmpty += e, + e => blockProcessingQueue.ProcessingQueueEmpty -= e); + } + } +} diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IBlockProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/IBlockProcessor.cs index 718d7e3e8fe7..2adaabe6f4b0 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/IMainProcessingContext.cs b/src/Nethermind/Nethermind.Consensus/Processing/IMainProcessingContext.cs index bfd0f83ec494..89cb567b325e 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/IMainProcessingContext.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/IMainProcessingContext.cs @@ -22,6 +22,7 @@ public interface IMainProcessingContext : IStoppableService IBranchProcessor BranchProcessor { get; } IBlockProcessor BlockProcessor { get; } IBlockchainProcessor BlockchainProcessor { get; } + IBlockProcessingQueue BlockProcessingQueue { get; } IWorldState WorldState { get; } IGenesisLoader GenesisLoader { get; } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessingScope.cs b/src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessingScope.cs deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessorSource.cs b/src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessorSource.cs deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IProcessingStats.cs b/src/Nethermind/Nethermind.Consensus/Processing/IProcessingStats.cs new file mode 100644 index 000000000000..7d7b65723e69 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Processing/IProcessingStats.cs @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; + +namespace Nethermind.Consensus.Processing; + +/// +/// Interface for processing statistics tracking during block processing. +/// +public interface IProcessingStats +{ + /// + /// Event fired when new processing statistics are available. + /// + event EventHandler? NewProcessingStatistics; + + /// + /// Start the statistics timer. + /// + void Start(); + + /// + /// Capture the starting values of metrics before processing begins. + /// + void CaptureStartStats(); + + /// + /// Update statistics after a block has been processed. + /// + /// The processed block. + /// The parent block header. + /// Processing time in microseconds. + void UpdateStats(Block? block, BlockHeader? baseBlock, long blockProcessingTimeInMicros); +} diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IReadOnlyTxProcessingEnvFactory.cs b/src/Nethermind/Nethermind.Consensus/Processing/IReadOnlyTxProcessingEnvFactory.cs index baeb04fb694b..2d46d4d7004d 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 51b998dcae41..819aabd99c2a 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/OverridableTxProcessingScope.cs b/src/Nethermind/Nethermind.Consensus/Processing/OverridableTxProcessingScope.cs deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/Nethermind/Nethermind.Consensus/Processing/PrewarmerEnvFactory.cs b/src/Nethermind/Nethermind.Consensus/Processing/PrewarmerEnvFactory.cs new file mode 100644 index 000000000000..17b3d97d0a8b --- /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 deleted file mode 100644 index f9b0774fce8e..000000000000 --- a/src/Nethermind/Nethermind.Consensus/Processing/PrewarmerTxAdapter.cs +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Core; -using Nethermind.Evm; -using Nethermind.Evm.State; -using Nethermind.Evm.Tracing; -using Nethermind.Evm.TransactionProcessing; -using Nethermind.State; - -namespace Nethermind.Consensus.Processing; - -public class PrewarmerTxAdapter(ITransactionProcessorAdapter baseAdapter, BlockCachePreWarmer preWarmer, IWorldState worldState) : ITransactionProcessorAdapter -{ - public TransactionResult Execute(Transaction transaction, ITxTracer txTracer) - { - if (worldState is IPreBlockCaches preBlockCaches && preBlockCaches.IsWarmWorldState) - { - preWarmer.OnBeforeTxExecution(transaction); - } - return baseAdapter.Execute(transaction, txTracer); - } - - public void SetBlockExecutionContext(in BlockExecutionContext blockExecutionContext) => baseAdapter.SetBlockExecutionContext(in blockExecutionContext); -} diff --git a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingOptions.cs b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingOptions.cs index 82799e5578dd..d137d0ad04a2 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingOptions.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingOptions.cs @@ -38,7 +38,7 @@ public enum ProcessingOptions /// /// Does not verify transaction nonces during processing. /// - DoNotVerifyNonce = 32, + LoadNonceFromState = 32, /// /// After processing it will not update the block tree head even if the processed block has the highest @@ -64,7 +64,7 @@ public enum ProcessingOptions /// /// EVM tracing needs to process blocks without storing the data on chain. /// - Trace = ForceProcessing | ReadOnlyChain | DoNotVerifyNonce | NoValidation, + Trace = ForceProcessing | ReadOnlyChain | LoadNonceFromState | NoValidation, /// /// EVM tracing needs to process one or more transactions on top of the specified block (instead of the previous one) diff --git a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs index 52756254fd63..0cd19058da01 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs @@ -19,27 +19,27 @@ namespace Nethermind.Consensus.Processing { public class BlockStatistics { - public long BlockCount { get; internal set; } - public long BlockFrom { get; internal set; } - public long BlockTo { get; internal set; } - public double ProcessingMs { get; internal set; } - public double SlotMs { get; internal set; } + public long BlockCount { get; set; } + public long BlockFrom { get; set; } + public long BlockTo { get; set; } + public double ProcessingMs { get; set; } + public double SlotMs { get; set; } [JsonPropertyName("mgasPerSecond")] - public double MGasPerSecond { get; internal set; } - public float MinGas { get; internal set; } - public float MedianGas { get; internal set; } - public float AveGas { get; internal set; } - public float MaxGas { get; internal set; } - public long GasLimit { get; internal set; } + public double MGasPerSecond { get; set; } + public float MinGas { get; set; } + public float MedianGas { get; set; } + public float AveGas { get; set; } + public float MaxGas { get; set; } + public long GasLimit { get; set; } } //TODO Consult on disabling of such metrics from configuration - internal class ProcessingStats + public class ProcessingStats : IProcessingStats { private static readonly DefaultObjectPool _dataPool = new(new BlockDataPolicy(), 16); private readonly Action _executeFromThreadPool; public event EventHandler? NewProcessingStatistics; - private readonly IStateReader _stateReader; - private readonly ILogger _logger; + protected readonly IStateReader _stateReader; + protected readonly ILogger _logger; private readonly Stopwatch _runStopwatch = new(); private bool _showBlobs; @@ -69,12 +69,12 @@ internal class ProcessingStats private long _contractsAnalyzed; private long _cachedContractsUsed; - public ProcessingStats(IStateReader stateReader, ILogger logger) + public ProcessingStats(IStateReader stateReader, ILogManager logManager) { _executeFromThreadPool = ExecuteFromThreadPool; _stateReader = stateReader; - _logger = logger; + _logger = logManager.GetClassLogger(); // the line below just to avoid compilation errors if (_logger.IsTrace) _logger.Trace($"Processing Stats in debug mode?: {_logger.IsDebug}"); @@ -151,7 +151,7 @@ void ExecuteFromThreadPool(BlockData data) } } - private void GenerateReport(BlockData data) + protected virtual void GenerateReport(BlockData data) { const long weiToEth = 1_000_000_000_000_000_000; const string resetColor = "\u001b[37m"; @@ -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() @@ -443,7 +443,7 @@ public bool Return(BlockData data) } } - private class BlockData + protected class BlockData { public Block Block; public BlockHeader? BaseBlock; diff --git a/src/Nethermind/Nethermind.Consensus/Processing/ShareableTxProcessingSource.cs b/src/Nethermind/Nethermind.Consensus/Processing/ShareableTxProcessingSource.cs index b0ea3a22b5d1..dbdcda33322b 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/Processing/TransactionProcessorAdapterExtensions.cs b/src/Nethermind/Nethermind.Consensus/Processing/TransactionProcessorAdapterExtensions.cs index b8a8e275233e..2f125d18f1c7 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/TransactionProcessorAdapterExtensions.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/TransactionProcessorAdapterExtensions.cs @@ -17,7 +17,7 @@ public static TransactionResult ProcessTransaction(this ITransactionProcessorAda ProcessingOptions processingOptions, IWorldState stateProvider) { - if (processingOptions.ContainsFlag(ProcessingOptions.DoNotVerifyNonce) && currentTx.SenderAddress != Address.SystemUser) + if (processingOptions.ContainsFlag(ProcessingOptions.LoadNonceFromState) && currentTx.SenderAddress != Address.SystemUser) { currentTx.Nonce = stateProvider.GetNonce(currentTx.SenderAddress!); } diff --git a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs index 00d5328a5bc2..3a35b77e495e 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs @@ -34,13 +34,13 @@ namespace Nethermind.Consensus.Producers /// public abstract class BlockProducerBase : IBlockProducer { - private IBlockchainProcessor Processor { get; } + protected IBlockchainProcessor Processor { get; } protected IBlockTree BlockTree { get; } + protected readonly IGasLimitCalculator GasLimitCalculator; private ITimestamper Timestamper { get; } - private ISealer Sealer { get; } + protected ISealer Sealer { get; } private IWorldState StateProvider { get; } - private readonly IGasLimitCalculator _gasLimitCalculator; private readonly IDifficultyCalculator _difficultyCalculator; protected readonly ISpecProvider _specProvider; protected internal ITxSource TxSource { get; set; } @@ -67,7 +67,7 @@ protected BlockProducerBase( Sealer = sealer ?? throw new ArgumentNullException(nameof(sealer)); BlockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); StateProvider = stateProvider ?? throw new ArgumentNullException(nameof(stateProvider)); - _gasLimitCalculator = gasLimitCalculator ?? throw new ArgumentNullException(nameof(gasLimitCalculator)); + GasLimitCalculator = gasLimitCalculator ?? throw new ArgumentNullException(nameof(gasLimitCalculator)); Timestamper = timestamper ?? throw new ArgumentNullException(nameof(timestamper)); _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); _difficultyCalculator = difficultyCalculator ?? throw new ArgumentNullException(nameof(difficultyCalculator)); @@ -189,8 +189,11 @@ protected BlockProducerBase( protected virtual Task SealBlock(Block block, BlockHeader parent, CancellationToken token) => Sealer.SealBlock(block, token); - protected virtual Block? ProcessPreparedBlock(Block block, IBlockTracer? blockTracer, CancellationToken token = default) => - Processor.Process(block, ProcessingOptions.ProducingBlock, blockTracer ?? NullBlockTracer.Instance, token); + protected virtual Block? ProcessPreparedBlock(Block block, IBlockTracer? blockTracer, + CancellationToken token = default) + { + return Processor.Process(block, GetProcessingOptions(), blockTracer ?? NullBlockTracer.Instance, token); + } private bool PreparedBlockCanBeMined(Block? block) { @@ -214,7 +217,7 @@ protected virtual BlockHeader PrepareBlockHeader(BlockHeader parent, blockAuthor, UInt256.Zero, parent.Number + 1, - payloadAttributes?.GetGasLimit() ?? _gasLimitCalculator.GetGasLimit(parent), + payloadAttributes?.GetGasLimit() ?? GasLimitCalculator.GetGasLimit(parent), timestamp, _blocksConfig.GetExtraDataBytes()) { @@ -244,5 +247,12 @@ protected virtual BlockToProduce PrepareBlock(BlockHeader parent, PayloadAttribu return new BlockToProduce(header, transactions, Array.Empty(), payloadAttributes?.Withdrawals); } + + private ProcessingOptions GetProcessingOptions() + { + if (_blocksConfig.BuildBlocksOnMainState) + return ProcessingOptions.NoValidation | ProcessingOptions.StoreReceipts | ProcessingOptions.DoNotUpdateHead; + return ProcessingOptions.ProducingBlock; + } } } diff --git a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerEnvFactory.cs b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerEnvFactory.cs index 6f5923352525..3002b39d2351 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerEnvFactory.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerEnvFactory.cs @@ -27,12 +27,11 @@ protected virtual ContainerBuilder ConfigureBuilder(ContainerBuilder builder) => .AddScoped() .AddDecorator() .AddDecorator() - .AddScoped(); public IBlockProducerEnv Create() { - IWorldState worldState = worldStateManager.CreateResettableWorldState(); + IWorldStateScopeProvider worldState = worldStateManager.CreateResettableWorldState(); ILifetimeScope lifetimeScope = rootLifetime.BeginLifetimeScope(builder => ConfigureBuilder(builder) .AddScoped(worldState)); diff --git a/src/Nethermind/Nethermind.Consensus/Producers/BlockProductionPolicy.cs b/src/Nethermind/Nethermind.Consensus/Producers/BlockProductionPolicy.cs index dcfb9492054d..1e7a86248839 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/BlockProductionPolicy.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/BlockProductionPolicy.cs @@ -11,17 +11,9 @@ namespace Nethermind.Consensus.Producers; * However, in the post-merge world, our node might not be miner pre-merge, and it is a validator after the merge. Generally, in post-merge, we should always start a block production logic. If we weren't pre-merge miner merge plugin will be able to wrap null as a preMergeBlockProducer. * To resolve this problem BlockProductionPolicy was introduced. */ -public class BlockProductionPolicy : IBlockProductionPolicy +public class BlockProductionPolicy(IMiningConfig miningConfig) : IBlockProductionPolicy { - private readonly IMiningConfig _miningConfig; - - public BlockProductionPolicy( - IMiningConfig miningConfig) - { - _miningConfig = miningConfig; - } - - public bool ShouldStartBlockProduction() => _miningConfig.Enabled; + public bool ShouldStartBlockProduction() => miningConfig.Enabled; } public class NeverStartBlockProductionPolicy : IBlockProductionPolicy diff --git a/src/Nethermind/Nethermind.Consensus/Producers/BlockToProduce.cs b/src/Nethermind/Nethermind.Consensus/Producers/BlockToProduce.cs index c1e239e3a8b9..ce6d7be6f116 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/BlockToProduce.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/BlockToProduce.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.CompilerServices; using Nethermind.Core; @@ -12,6 +13,7 @@ namespace Nethermind.Consensus.Producers { + [DebuggerDisplay("{Hash} ({Number})")] public class BlockToProduce : Block { private IEnumerable? _transactions; diff --git a/src/Nethermind/Nethermind.Consensus/Producers/GlobalWorldStateBlockProducerEnvFactory.cs b/src/Nethermind/Nethermind.Consensus/Producers/GlobalWorldStateBlockProducerEnvFactory.cs new file mode 100644 index 000000000000..3427d9f494b7 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Producers/GlobalWorldStateBlockProducerEnvFactory.cs @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Autofac; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Transactions; +using Nethermind.Consensus.Withdrawals; +using Nethermind.Core; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.State; + +namespace Nethermind.Consensus.Producers +{ + /// + /// Block producer environment factory that uses the global world state and stores receipts by default. + /// Combined with NonProcessingProducedBlockSuggester will add blocks to block tree + /// + /// + /// + /// + public class GlobalWorldStateBlockProducerEnvFactory( + ILifetimeScope rootLifetime, + IWorldStateManager worldStateManager, + IBlockProducerTxSourceFactory txSourceFactory) + : IBlockProducerEnvFactory + { + protected virtual ContainerBuilder ConfigureBuilder(ContainerBuilder builder) => builder + .AddScoped(txSourceFactory.Create()) + .AddScoped(BlockchainProcessor.Options.Default) + .AddScoped() + .AddScoped() + .AddDecorator() + .AddDecorator() + + .AddScoped(); + + public virtual IBlockProducerEnv Create() + { + ILifetimeScope lifetimeScope = rootLifetime.BeginLifetimeScope(builder => + ConfigureBuilder(builder) + .AddScoped(worldStateManager.GlobalWorldState)); + + rootLifetime.Disposer.AddInstanceForAsyncDisposal(lifetimeScope); + return lifetimeScope.Resolve(); + } + } +} diff --git a/src/Nethermind/Nethermind.Consensus/Producers/IProducedBlockSuggester.cs b/src/Nethermind/Nethermind.Consensus/Producers/IProducedBlockSuggester.cs new file mode 100644 index 000000000000..82b2bf7ccf28 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Producers/IProducedBlockSuggester.cs @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Consensus.Producers; + +public interface IProducedBlockSuggester : IDisposable +{ + // Just a marker interface to support DI +} diff --git a/src/Nethermind/Nethermind.Consensus/Producers/NonProcessingProducedBlockSuggester.cs b/src/Nethermind/Nethermind.Consensus/Producers/NonProcessingProducedBlockSuggester.cs new file mode 100644 index 000000000000..59c37c9779ed --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Producers/NonProcessingProducedBlockSuggester.cs @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Core; + +namespace Nethermind.Consensus.Producers; + +public class NonProcessingProducedBlockSuggester : IProducedBlockSuggester +{ + private readonly IBlockTree _blockTree; + private readonly IBlockProducerRunner _blockProducerRunner; + + public NonProcessingProducedBlockSuggester(IBlockTree blockTree, IBlockProducerRunner blockProducer) + { + _blockTree = blockTree; + _blockProducerRunner = blockProducer; + _blockProducerRunner.BlockProduced += OnBlockProduced; + } + + private void OnBlockProduced(object? sender, BlockEventArgs e) + { + if (_blockTree.SuggestBlock(e.Block, BlockTreeSuggestOptions.None) == AddBlockResult.Added) + _blockTree.UpdateMainChain([e.Block], true); + } + + public void Dispose() => _blockProducerRunner.BlockProduced -= OnBlockProduced; +} diff --git a/src/Nethermind/Nethermind.Consensus/Producers/ProducedBlockSuggester.cs b/src/Nethermind/Nethermind.Consensus/Producers/ProducedBlockSuggester.cs index a061e7f6e8ce..b74aba7c5714 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/ProducedBlockSuggester.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/ProducedBlockSuggester.cs @@ -1,13 +1,12 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Nethermind.Blockchain; using Nethermind.Core; namespace Nethermind.Consensus.Producers { - public class ProducedBlockSuggester : IDisposable + public class ProducedBlockSuggester : IProducedBlockSuggester { private readonly IBlockTree _blockTree; private readonly IBlockProducerRunner _blockProducerRunner; diff --git a/src/Nethermind/Nethermind.Consensus/Producers/TxPoolTxSource.cs b/src/Nethermind/Nethermind.Consensus/Producers/TxPoolTxSource.cs index e4f43bab1188..1b1469978162 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/TxPoolTxSource.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/TxPoolTxSource.cs @@ -19,6 +19,7 @@ using Nethermind.Logging; using Nethermind.TxPool; using Nethermind.TxPool.Comparison; +using static Nethermind.TxPool.Comparison.TxComparisonResult; [assembly: InternalsVisibleTo("Nethermind.AuRa.Test")] @@ -51,17 +52,19 @@ public IEnumerable GetTransactions(BlockHeader parent, long gasLimi IComparer comparer = GetComparer(parent, new BlockPreparationContext(baseFee, blockNumber)) .ThenBy(ByHashTxComparer.Instance); // in order to sort properly and not lose transactions we need to differentiate on their identity which provided comparer might not be doing - Func filter = (tx) => _txFilterPipeline.Execute(tx, parent, spec); + Func filter = tx => _txFilterPipeline.Execute(tx, parent, spec); + int maxBlobCount = spec.MaxProductionBlobCount(blocksConfig.BlockProductionBlobLimit); IEnumerable transactions = GetOrderedTransactions(pendingTransactions, comparer, filter, gasLimit); - IEnumerable<(Transaction tx, long blobChain)> blobTransactions = GetOrderedBlobTransactions(pendingBlobTransactionsEquivalences, comparer, filter, (int)spec.MaxBlobCount); + IEnumerable<(Transaction tx, long blobChain)> blobTransactions = GetOrderedBlobTransactions(pendingBlobTransactionsEquivalences, comparer, filter, maxBlobCount); if (_logger.IsDebug) _logger.Debug($"Collecting pending transactions at block gas limit {gasLimit}."); int checkedTransactions = 0; int selectedTransactions = 0; - using ArrayPoolList selectedBlobTxs = new((int)spec.MaxBlobCount); - SelectBlobTransactions(blobTransactions, parent, spec, baseFee, selectedBlobTxs); + using ArrayPoolList selectedBlobTxs = new(maxBlobCount); + + SelectBlobTransactions(blobTransactions, parent, spec, baseFee, selectedBlobTxs, maxBlobCount); foreach (Transaction tx in transactions) { @@ -103,20 +106,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; } } @@ -125,7 +139,7 @@ private static IEnumerable PickBlobTxsBetterThanCurrentTx(ArrayPool while (selectedBlobTxs.Count > 0) { Transaction blobTx = selectedBlobTxs[0]; - if (comparer.Compare(blobTx, tx) > 0) + if (comparer.Compare(blobTx, tx) < Equal) { yield return blobTx; selectedBlobTxs.Remove(blobTx); @@ -137,10 +151,9 @@ private static IEnumerable PickBlobTxsBetterThanCurrentTx(ArrayPool } } - private void SelectBlobTransactions(IEnumerable<(Transaction tx, long blobChain)> blobTransactions, BlockHeader parent, IReleaseSpec spec, in UInt256 baseFee, ArrayPoolList selectedBlobTxs) + private void SelectBlobTransactions(IEnumerable<(Transaction tx, long blobChain)> blobTransactions, BlockHeader parent, IReleaseSpec spec, in UInt256 baseFee, ArrayPoolList selectedBlobTxs, int maxBlobs) { - int maxBlobsPerBlock = (int)spec.MaxBlobCount; - int maxBlobsToConsider = maxBlobsPerBlock * 5; + int maxBlobsToConsider = maxBlobs * 5; int countOfRemainingBlobs = 0; if (!TryUpdateFeePerBlobGas(parent, spec, out UInt256 feePerBlobGas)) @@ -153,7 +166,7 @@ private void SelectBlobTransactions(IEnumerable<(Transaction tx, long blobChain) foreach ((Transaction blobTx, long blobChain) in blobTransactions) { int txBlobCount = blobTx.GetBlobCount(); - if (txBlobCount > maxBlobsPerBlock) + if (txBlobCount > maxBlobs) { if (_logger.IsTrace) _logger.Trace($"Declining {blobTx.ToShortString()}, not enough blob space."); continue; @@ -168,7 +181,7 @@ private void SelectBlobTransactions(IEnumerable<(Transaction tx, long blobChain) if (txBlobCount == 1 && candidates is null) { selectedBlobTxs.Add(blobTx); - if (selectedBlobTxs.Count == maxBlobsPerBlock) + if (selectedBlobTxs.Count == maxBlobs) { // Early exit, have complete set of 1 blob txs with maximal priority fees // No need to consider other tx. @@ -196,7 +209,7 @@ private void SelectBlobTransactions(IEnumerable<(Transaction tx, long blobChain) using (candidates) { // We have leftover candidates. Check how many blob slots remain. - int leftoverCapacity = maxBlobsPerBlock - selectedBlobTxs.Count; + int leftoverCapacity = maxBlobs - selectedBlobTxs.Count; if (countOfRemainingBlobs <= leftoverCapacity) { // We can take all, no optimal picking needed. @@ -234,7 +247,7 @@ private static void ChooseBestBlobTransactions( { int maxCapacity = leftoverCapacity + 1; // The maximum total fee achievable with capacity - using ArrayPoolList dpFeesPooled = new(capacity: maxCapacity, count: maxCapacity); + using ArrayPoolListRef dpFeesPooled = new(maxCapacity, maxCapacity); Span dpFees = dpFeesPooled.AsSpan(); using ArrayPoolBitMap isChosen = new(candidateTxs.Count * maxCapacity); diff --git a/src/Nethermind/Nethermind.Consensus/Scheduler/BackgroundTaskScheduler.cs b/src/Nethermind/Nethermind.Consensus/Scheduler/BackgroundTaskScheduler.cs index d6d774813180..27ed452d518a 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,9 +33,7 @@ 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; private readonly ILogger _logger; private readonly IBranchProcessor _branchProcessor; @@ -46,6 +42,7 @@ public class BackgroundTaskScheduler : IBackgroundTaskScheduler, IAsyncDisposabl private long _queueCount; private CancellationTokenSource _blockProcessorCancellationTokenSource; + private volatile TaskCompletionSource? _blockProcessingDoneSignal; private bool _disposed = false; public BackgroundTaskScheduler(IBranchProcessor branchProcessor, IChainHeadInfoProvider headInfo, int concurrency, int capacity, ILogManager logManager) @@ -57,12 +54,17 @@ 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; - _restartQueueSignal = new ManualResetEventSlim(initialState: true); _capacity = capacity; _branchProcessor.BlocksProcessing += BranchProcessorOnBranchesProcessing; @@ -71,7 +73,6 @@ public BackgroundTaskScheduler(IBranchProcessor branchProcessor, IChainHeadInfoP // TaskScheduler to run tasks at BelowNormal priority _scheduler = new BelowNormalPriorityTaskScheduler( concurrency, - _restartQueueSignal, logManager, _mainCancellationTokenSource.Token); @@ -81,26 +82,27 @@ 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 - _restartQueueSignal.Reset(); - // On block processing, we cancel the block process cts, causing current task to get cancelled. + // Signal that block processing is in progress so the Throttle path in StartChannel + // can async-wait instead of busy-polling + _blockProcessingDoneSignal = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + // 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. - _restartQueueSignal.Set(); + // Signal that block processing is done so the Throttle path can resume + Interlocked.Exchange(ref _blockProcessingDoneSignal, null)?.TrySetResult(); } private async Task StartChannel() @@ -119,18 +121,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; } } @@ -154,55 +156,46 @@ private async Task StartChannel() continue; Throttle: - await Task.Delay(millisecondsDelay: 1); + // Wait for block processing to complete, with periodic wake-ups to drain newly expired tasks + TaskCompletionSource? signal = _blockProcessingDoneSignal; + if (signal is not null) + { + await Task.WhenAny(signal.Task, Task.Delay(millisecondsDelay: 1)); + } + else + { + await Task.Delay(millisecondsDelay: 1); + } } } - 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 +213,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 +228,7 @@ public async Task Do(CancellationToken cancellationToken) if (timeToComplete <= TimeSpan.Zero) { // Cancel immediately. Got no time left. - token = CancelledToken; + token = CancellationTokenExtensions.AlreadyCancelledToken; } else { @@ -275,17 +258,15 @@ private sealed class BelowNormalPriorityTaskScheduler : TaskScheduler, IDisposab { private readonly BlockingCollection _tasks = []; private readonly Thread[] workerThreads; - private readonly ManualResetEventSlim _restartQueueSignal; private readonly int _maxDegreeOfParallelism; private readonly ILogger _logger; private readonly CancellationToken _cancellationToken; - public BelowNormalPriorityTaskScheduler(int maxDegreeOfParallelism, ManualResetEventSlim restartQueueSignal, ILogManager logManager, CancellationToken cancellationToken) + public BelowNormalPriorityTaskScheduler(int maxDegreeOfParallelism, ILogManager logManager, CancellationToken cancellationToken) { ArgumentOutOfRangeException.ThrowIfLessThan(maxDegreeOfParallelism, 1); _logger = logManager.GetClassLogger(); - _restartQueueSignal = restartQueueSignal; _maxDegreeOfParallelism = maxDegreeOfParallelism; _cancellationToken = cancellationToken; workerThreads = [.. Enumerable.Range(0, maxDegreeOfParallelism) @@ -308,8 +289,6 @@ private void ProcessBackgroundTasks(object _) { foreach (Task task in _tasks.GetConsumingEnumerable(_cancellationToken)) { - // Wait if processing blocks - _restartQueueSignal.Wait(_cancellationToken); try { TryExecuteTask(task); diff --git a/src/Nethermind/Nethermind.Consensus/Scheduler/IBackgroundTaskScheduler.cs b/src/Nethermind/Nethermind.Consensus/Scheduler/IBackgroundTaskScheduler.cs index b585a58f9bc1..b43ec4583315 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 922fab54f150..66a70c68ad54 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 0f70a1f6568f..055a46dec12c 100644 --- a/src/Nethermind/Nethermind.Consensus/Stateless/StatelessBlockProcessingEnv.cs +++ b/src/Nethermind/Nethermind.Consensus/Stateless/StatelessBlockProcessingEnv.cs @@ -4,24 +4,19 @@ using Nethermind.Blockchain; using Nethermind.Blockchain.BeaconBlockRoot; using Nethermind.Blockchain.Blocks; -using Nethermind.Blockchain.Find; 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; @@ -41,14 +36,13 @@ public IBlockProcessor BlockProcessor public IWorldState WorldState { get => _worldState ??= new WorldState( - new TrieStore(witness.NodeStorage, NoPruning.Instance, NoPersistence.Instance, new PruningConfig(), - logManager), - witness.CodeDb, logManager); + new TrieStoreScopeProvider(new RawTrieStore(witness.NodeStorage), + witness.CodeDb, logManager), logManager); } private IBlockProcessor GetProcessor() { - IBlockTree statelessBlockTree = new StatelessBlockTree(witness.DecodedHeaders); + StatelessBlockTree statelessBlockTree = new(witness.DecodedHeaders); ITransactionProcessor txProcessor = CreateTransactionProcessor(WorldState, statelessBlockTree); IBlockProcessor.IBlockTransactionsExecutor txExecutor = new BlockProcessor.BlockValidationTransactionsExecutor( @@ -67,7 +61,7 @@ private IBlockProcessor GetProcessor() WorldState, NullReceiptStorage.Instance, new BeaconBlockRootHandler(txProcessor, WorldState), - new BlockhashStore(specProvider, WorldState), + new BlockhashStore(WorldState), logManager, new WithdrawalProcessor(WorldState, logManager), new ExecutionRequestsProcessor(txProcessor) @@ -75,10 +69,10 @@ private IBlockProcessor GetProcessor() } - private ITransactionProcessor CreateTransactionProcessor(IWorldState state, IBlockFinder blockFinder) + private ITransactionProcessor CreateTransactionProcessor(IWorldState state, IBlockhashCache blockhashCache) { - var blockhashProvider = new BlockhashProvider(blockFinder, specProvider, state, logManager); - var vm = new VirtualMachine(blockhashProvider, specProvider, logManager); - return new TransactionProcessor(BlobBaseFeeCalculator.Instance, specProvider, state, vm, new EthereumCodeInfoRepository(state), logManager); + BlockhashProvider blockhashProvider = new(blockhashCache, 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/StatelessBlockTree.cs b/src/Nethermind/Nethermind.Consensus/Stateless/StatelessBlockTree.cs index 68dd42dd8df9..a21bc941b58b 100644 --- a/src/Nethermind/Nethermind.Consensus/Stateless/StatelessBlockTree.cs +++ b/src/Nethermind/Nethermind.Consensus/Stateless/StatelessBlockTree.cs @@ -17,11 +17,11 @@ namespace Nethermind.Consensus.Stateless; /// /// This class is part of the StatelessExecution tool. It's intended to be used only inside the processing pipeline. /// -/// -public class StatelessBlockTree(IReadOnlyCollection headers) : IBlockTree +public class StatelessBlockTree(IReadOnlyCollection headers) + : IBlockTree, IBlockhashCache { - private readonly Dictionary _hashToHeader = - headers.ToDictionary(header => header.Hash ?? throw new ArgumentNullException(), header => header); + private readonly Dictionary _hashToHeader = + headers.ToDictionary(header => (Hash256AsKey)(header.Hash ?? throw new ArgumentNullException(nameof(header.Hash))), header => header); private readonly Dictionary _numberToHeader = headers.ToDictionary(header => header.Number, header => header); @@ -213,4 +213,27 @@ public event EventHandler? OnForkChoiceUpd add => throw new NotSupportedException(); remove => throw new NotSupportedException(); } + + public Hash256? GetHash(BlockHeader headBlock, int depth) => + depth == 0 + ? headBlock.Hash + : _numberToHeader.TryGetValue(headBlock.Number - depth, out BlockHeader? header) + ? header?.Hash + : null; + + public Task Prefetch(BlockHeader blockHeader, CancellationToken cancellationToken) + { + const int length = BlockhashCache.MaxDepth + 1; + Hash256[] result = new Hash256[length]; + result[0] = blockHeader.Hash; + for (int i = 1; i < length; i++) + { + if (_numberToHeader.TryGetValue(blockHeader.Number - i, out BlockHeader header)) + { + result[i] = header.Hash; + } + } + + return Task.FromResult(result); + } } diff --git a/src/Nethermind/Nethermind.Consensus/Stateless/Witness.cs b/src/Nethermind/Nethermind.Consensus/Stateless/Witness.cs index e6a139d4cb92..30ccd4591547 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/TargetAdjustedGasLimitCalculator.cs b/src/Nethermind/Nethermind.Consensus/TargetAdjustedGasLimitCalculator.cs index ee68edd3e647..90153d6ff643 100644 --- a/src/Nethermind/Nethermind.Consensus/TargetAdjustedGasLimitCalculator.cs +++ b/src/Nethermind/Nethermind.Consensus/TargetAdjustedGasLimitCalculator.cs @@ -8,10 +8,10 @@ namespace Nethermind.Consensus { - public class TargetAdjustedGasLimitCalculator(ISpecProvider? specProvider, IBlocksConfig? miningConfig) : IGasLimitCalculator + public class TargetAdjustedGasLimitCalculator(ISpecProvider? specProvider, IBlocksConfig? blocksConfig) : IGasLimitCalculator { private readonly ISpecProvider _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); - private readonly IBlocksConfig _blocksConfig = miningConfig ?? throw new ArgumentNullException(nameof(miningConfig)); + private readonly IBlocksConfig _blocksConfig = blocksConfig ?? throw new ArgumentNullException(nameof(blocksConfig)); public long GetGasLimit(BlockHeader parentHeader) { diff --git a/src/Nethermind/Nethermind.Consensus/Tracing/BlockProcessorFacade.cs b/src/Nethermind/Nethermind.Consensus/Tracing/BlockProcessorFacade.cs index 224f2ae63b76..fabb1b7d57c9 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 3a1bb9d7be62..b2bce702e101 100644 --- a/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs +++ b/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs @@ -174,17 +174,19 @@ 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! BlockHeader baseBlockHeader = block.Header; + if ((processingOptions & ProcessingOptions.ForceSameBlock) == 0) { baseBlockHeader = FindParent(block); } - using var scope = blockProcessingEnv.BuildAndOverride(baseBlockHeader, options.StateOverrides); + options.BlockOverrides?.ApplyOverrides(block.Header); + using Scope scope = blockProcessingEnv.BuildAndOverride(baseBlockHeader, options.StateOverrides); IBlockTracer tracer = CreateOptionsTracer(block.Header, options with { TxHash = txHash }, scope.Component.WorldState, specProvider); try diff --git a/src/Nethermind/Nethermind.Consensus/Tracing/ITracer.cs b/src/Nethermind/Nethermind.Consensus/Tracing/ITracer.cs index 95478961c51f..5064310f4d23 100644 --- a/src/Nethermind/Nethermind.Consensus/Tracing/ITracer.cs +++ b/src/Nethermind/Nethermind.Consensus/Tracing/ITracer.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Evm.Tracing; using Nethermind.Trie; @@ -27,6 +26,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 92e60ae2bdf7..42e45a2d01e4 100644 --- a/src/Nethermind/Nethermind.Consensus/Tracing/Tracer.cs +++ b/src/Nethermind/Nethermind.Consensus/Tracing/Tracer.cs @@ -4,7 +4,6 @@ using System; using Nethermind.Consensus.Processing; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Evm.Tracing; using Nethermind.State; using Nethermind.Trie; @@ -33,12 +32,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 8e1ba0005752..614ca9b63a36 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/Transactions/NextBlockSpecHelper.cs b/src/Nethermind/Nethermind.Consensus/Transactions/NextBlockSpecHelper.cs index 71d1b3607fcf..5bc5d2e594d7 100644 --- a/src/Nethermind/Nethermind.Consensus/Transactions/NextBlockSpecHelper.cs +++ b/src/Nethermind/Nethermind.Consensus/Transactions/NextBlockSpecHelper.cs @@ -7,6 +7,7 @@ using Nethermind.Core.Specs; namespace Nethermind.Consensus.Transactions; + internal static class NextBlockSpecHelper { public static IReleaseSpec GetSpec(ISpecProvider specProvider, BlockHeader parentHeader, diff --git a/src/Nethermind/Nethermind.Consensus/TxFilterBuilders.cs b/src/Nethermind/Nethermind.Consensus/TxFilterBuilders.cs index 39bd8807bf6e..294b4f667d5e 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 17397d1a17d8..60f8923635c3 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs @@ -71,10 +71,10 @@ private bool ValidateBlock(Block block, BlockHeader? parent, out stri return ValidateBlockSize(block, spec, ref errorMessage) && ValidateTransactions(block, spec, ref errorMessage) && - ValidateEip4844Fields(block, spec, ref errorMessage) && - ValidateUncles(block, spec, validateHashes, ref errorMessage) && ValidateHeader(block, parent, ref errorMessage) && + ValidateUncles(block, spec, validateHashes, ref errorMessage) && ValidateTxRootMatchesTxs(block, validateHashes, ref errorMessage) && + ValidateEip4844Fields(block, spec, ref errorMessage) && ValidateWithdrawals(block, spec, validateHashes, ref errorMessage); } @@ -90,7 +90,8 @@ private bool ValidateHeader(Block block, BlockHeader? parent, ref str return blockHeaderValid; } - private bool ValidateUncles(Block block, IReleaseSpec spec, bool validateHashes, ref string? errorMessage) + private bool ValidateUncles(Block block, IReleaseSpec spec, bool validateHashes, ref string? errorMessage) + where TOrphaned : struct, IFlag { if (spec.MaximumUncleCount < block.Uncles.Length) { @@ -106,7 +107,7 @@ private bool ValidateUncles(Block block, IReleaseSpec spec, bool validateHashes, return false; } - if (block.Uncles.Length > 0 && !_unclesValidator.Validate(block.Header, block.Uncles)) + if (typeof(TOrphaned) == typeof(OffFlag) && block.Uncles.Length > 0 && !_unclesValidator.Validate(block.Header, block.Uncles)) { if (_logger.IsDebug) _logger.Debug($"{Invalid(block)} Invalid uncles"); errorMessage = BlockErrorMessages.InvalidUncle; @@ -211,12 +212,20 @@ public bool ValidateProcessedBlock(Block processedBlock, TxReceipt[] receipts, B error ??= BlockErrorMessages.InvalidRequestsHash(suggestedBlock.Header.RequestsHash, processedBlock.Header.RequestsHash); } - for (int i = 0; i < processedBlock.Transactions.Length; i++) + if (receipts.Length != processedBlock.Transactions.Length) { - if (receipts[i].Error is not null && receipts[i].GasUsed == 0 && receipts[i].Error == "invalid") + if (_logger.IsWarn) _logger.Warn($"- receipt count mismatch: expected {processedBlock.Transactions.Length} receipts to match transaction count, got {receipts.Length}"); + error ??= BlockErrorMessages.ReceiptCountMismatch(processedBlock.Transactions.Length, receipts.Length); + } + else + { + for (int i = 0; i < processedBlock.Transactions.Length; i++) { - if (_logger.IsWarn) _logger.Warn($"- invalid transaction {i}"); - error ??= BlockErrorMessages.InvalidTxInBlock(i); + if (receipts[i].Error is not null && receipts[i].GasUsed == 0 && receipts[i].Error == "invalid") + { + if (_logger.IsWarn) _logger.Warn($"- invalid transaction {i}"); + error ??= BlockErrorMessages.InvalidTxInBlock(i); + } } } diff --git a/src/Nethermind/Nethermind.Consensus/Validators/HeaderValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/HeaderValidator.cs index 43322f640a7a..3236ae5fa5dc 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) { @@ -349,7 +349,7 @@ protected virtual bool ValidateBlobGasFields(BlockHeader header, BlockHeader par return false; } - ulong? expectedExcessBlobGas = BlobGasCalculator.CalculateExcessBlobGas(parent, spec); + ulong? expectedExcessBlobGas = CalculateExcessBlobGas(parent, spec); if (header.ExcessBlobGas != expectedExcessBlobGas) { if (_logger.IsWarn) _logger.Warn($"ExcessBlobGas field is incorrect: {header.ExcessBlobGas}, should be {expectedExcessBlobGas}."); @@ -376,5 +376,10 @@ protected virtual bool ValidateBlobGasFields(BlockHeader header, BlockHeader par return true; } + + protected virtual ulong? CalculateExcessBlobGas(BlockHeader parent, IReleaseSpec spec) + { + return BlobGasCalculator.CalculateExcessBlobGas(parent, spec); + } } } diff --git a/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs index f173826f13db..144678942032 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; @@ -307,13 +307,12 @@ private MempoolBlobTxProofVersionValidator() { } public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) { - return transaction switch - { - LightTransaction lightTx => ValidateProofVersion(lightTx.ProofVersion, releaseSpec), - { Type: TxType.Blob, NetworkWrapper: ShardBlobNetworkWrapper wrapper } => ValidateProofVersion(wrapper.Version, releaseSpec), - { Type: TxType.Blob, NetworkWrapper: not null } => TxErrorMessages.InvalidTransactionForm, - _ => ValidationResult.Success, - }; + if (!transaction.SupportsBlobs) return ValidationResult.Success; + + ProofVersion? version = transaction.GetProofVersion(); + return version is null + ? transaction.NetworkWrapper is not null ? TxErrorMessages.InvalidTransactionForm : ValidationResult.Success + : ValidateProofVersion(version.Value, releaseSpec); static ValidationResult ValidateProofVersion(ProofVersion txProofVersion, IReleaseSpec spec) => txProofVersion != spec.BlobProofVersion ? TxErrorMessages.InvalidProofVersion : ValidationResult.Success; diff --git a/src/Nethermind/Nethermind.Core.Test/AddressTests.cs b/src/Nethermind/Nethermind.Core.Test/AddressTests.cs index bb029f46be50..fc2c80dda27f 100644 --- a/src/Nethermind/Nethermind.Core.Test/AddressTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/AddressTests.cs @@ -12,7 +12,6 @@ using Nethermind.Core.Test.Builders; using Nethermind.Int256; using Nethermind.Evm; -using Nethermind.Evm.Precompiles; using NUnit.Framework; namespace Nethermind.Core.Test; @@ -183,6 +182,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/BlockTests.cs b/src/Nethermind/Nethermind.Core.Test/BlockTests.cs index 633745bb4c90..f134a0584ed2 100644 --- a/src/Nethermind/Nethermind.Core.Test/BlockTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/BlockTests.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using FluentAssertions; using Nethermind.Core.Collections; diff --git a/src/Nethermind/Nethermind.Core.Test/Blockchain/BasicTestBlockchain.cs b/src/Nethermind/Nethermind.Core.Test/Blockchain/BasicTestBlockchain.cs index 2e389cf18931..e4edcd794fe1 100644 --- a/src/Nethermind/Nethermind.Core.Test/Blockchain/BasicTestBlockchain.cs +++ b/src/Nethermind/Nethermind.Core.Test/Blockchain/BasicTestBlockchain.cs @@ -2,12 +2,12 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections; -using System.Collections.Generic; using System.Threading.Tasks; using Autofac; -using Nethermind.Config; +using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; +using Nethermind.Evm; +using Nethermind.State; namespace Nethermind.Core.Test.Blockchain; @@ -27,10 +27,17 @@ public static async Task Create(Action? c public async Task BuildSomeBlocks(int numOfBlocks) { + var nonce = WorldStateManager.GlobalStateReader.GetNonce(BlockTree.Head!.Header, TestItem.PrivateKeyA.Address); for (int i = 0; i < numOfBlocks; i++) { - await AddBlock(Builders.Build.A.Transaction.WithTo(TestItem.AddressD) - .SignedAndResolved(TestItem.PrivateKeyA).TestObject); + IReleaseSpec spec = SpecProvider.GetSpec(BlockTree.Head!.Header); + + await AddBlock(Builders.Build.A.Transaction + .WithTo(TestItem.AddressD) + .WithNonce(nonce) + .WithGasLimit(GasCostOf.Transaction) + .SignedAndResolved(TestItem.PrivateKeyA, spec.IsEip155Enabled).TestObject); + nonce++; } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Blockchain/InvalidBlockDetector.cs b/src/Nethermind/Nethermind.Core.Test/Blockchain/InvalidBlockDetector.cs index 25a23f481a28..1b5c415ac76b 100644 --- a/src/Nethermind/Nethermind.Core.Test/Blockchain/InvalidBlockDetector.cs +++ b/src/Nethermind/Nethermind.Core.Test/Blockchain/InvalidBlockDetector.cs @@ -3,7 +3,6 @@ using System; using System.Threading; -using Autofac; using Nethermind.Blockchain; using Nethermind.Consensus.Processing; using Nethermind.Core.Specs; diff --git a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs index 077fd67b2f6e..29da3aac0abe 100644 --- a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs +++ b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs @@ -3,14 +3,11 @@ using System; using System.Collections.Generic; -using System.Configuration; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Autofac; -using FluentAssertions; -using Nethermind.Api; using Nethermind.Blockchain; using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; @@ -43,7 +40,6 @@ using Nethermind.State; using Nethermind.State.Repositories; using Nethermind.TxPool; -using Nethermind.Blockchain.Blocks; using Nethermind.Init.Modules; namespace Nethermind.Core.Test.Blockchain; @@ -51,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; @@ -61,10 +57,12 @@ 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; public IBlockchainProcessor BlockchainProcessor => _fromContainer.MainProcessingContext.BlockchainProcessor; + public IBlockProcessingQueue BlockProcessingQueue => _fromContainer.MainProcessingContext.BlockProcessingQueue; public IBlockPreprocessorStep BlockPreprocessorStep => _fromContainer.BlockPreprocessorStep; public IBlockTree BlockTree => _fromContainer.BlockTree; @@ -101,8 +99,7 @@ protected TestBlockchain() public static readonly UInt256 InitialValue = 1000.Ether(); - protected AutoCancelTokenSource _cts; - public CancellationToken CancellationToken => _cts.Token; + public CancellationToken CancellationToken => CreateCancellationSource().Token; private TestBlockchainUtil TestUtil => _fromContainer.TestBlockchainUtil; private PoWTestBlockchainUtil PoWTestUtil => _fromContainer.PoWTestBlockchainUtil; @@ -111,7 +108,7 @@ protected TestBlockchain() public ManualTimestamper Timestamper => _fromContainer.ManualTimestamper; protected IBlocksConfig BlocksConfig => Container.Resolve(); - public ProducedBlockSuggester Suggester { get; protected set; } = null!; + public virtual IProducedBlockSuggester Suggester { get; protected set; } = null!; public IExecutionRequestsProcessor MainExecutionRequestsProcessor => ((MainProcessingContext)_fromContainer.MainProcessingContext).LifetimeScope.Resolve(); public IChainLevelInfoRepository ChainLevelInfoRepository => _fromContainer.ChainLevelInfoRepository; @@ -126,7 +123,7 @@ protected TestBlockchain() protected ChainSpec ChainSpec => _chainSpec ??= CreateChainSpec(); // Resolving all these component at once is faster. - private FromContainer _fromContainer = null!; + protected FromContainer _fromContainer = null!; public class FromContainer( Lazy stateReader, Lazy ethereumEcdsa, @@ -221,8 +218,6 @@ protected virtual async Task Build(Action? con BlockProducerRunner.Start(); Suggester = new ProducedBlockSuggester(BlockTree, BlockProducerRunner); - _cts = AutoCancelTokenSource.ThatCancelAfter(Debugger.IsAttached ? TimeSpan.FromMilliseconds(-1) : TimeSpan.FromMilliseconds(TestTimout)); - if (testConfiguration.SuggestGenesisOnStart) { // The block added event is not waited by genesis, but its needed to wait here so that `AddBlock` wait correctly. @@ -258,12 +253,12 @@ protected virtual ContainerBuilder ConfigureContainer(ContainerBuilder builder, .AddSingleton(Always.Valid) .AddSingleton(new NethDevSealEngine(TestItem.AddressD)) - .AddSingleton((_) => this.BlockProducer) - .AddSingleton((_) => this.BlockProducerRunner) + .AddSingleton((_) => BlockProducer) + .AddSingleton((_) => BlockProducerRunner) .AddSingleton((cfg) => new TestBlockchainUtil.Config(cfg.SlotTime)) - .AddSingleton((ctx) => new PoWTestBlockchainUtil( + .AddSingleton((ctx) => new PoWTestBlockchainUtil( ctx.Resolve(), ctx.Resolve(), ctx.Resolve(), @@ -328,7 +323,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); @@ -391,17 +386,23 @@ public Block Build() } } + protected virtual AutoCancelTokenSource CreateCancellationSource() + { + return AutoCancelTokenSource.ThatCancelAfter(Debugger.IsAttached ? TimeSpan.FromMilliseconds(-1) : TimeSpan.FromMilliseconds(TestTimeout)); + } + protected virtual async Task AddBlocksOnStart() { await AddBlock(); await AddBlock(CreateTransactionBuilder().WithNonce(0).TestObject); await AddBlock(CreateTransactionBuilder().WithNonce(1).TestObject, CreateTransactionBuilder().WithNonce(2).TestObject); + using AutoCancelTokenSource cts = CreateCancellationSource(); while (true) { - CancellationToken.ThrowIfCancellationRequested(); + cts.Token.ThrowIfCancellationRequested(); if (BlockTree.Head?.Number == 3) return; - await Task.Delay(1, CancellationToken); + await Task.Delay(1, cts.Token); } } @@ -428,41 +429,52 @@ private TransactionBuilder CreateTransactionBuilder() public async Task WaitForNewHead() { - await BlockTree.WaitForNewBlock(_cts.Token); + await BlockTree.WaitForNewBlock(CreateCancellationSource().Token); } public Task WaitForNewHeadWhere(Func predicate) { + AutoCancelTokenSource cts = CreateCancellationSource(); return Wait.ForEventCondition( - _cts.Token, + CreateCancellationSource().Token, (h) => BlockTree.BlockAddedToMain += h, (h) => BlockTree.BlockAddedToMain -= h, (e) => predicate(e.Block)); } - public virtual async Task AddBlock(params Transaction[] transactions) + public virtual Task AddBlock(params Transaction[] transactions) + { + return TestUtil.AddBlockAndWaitForHead(false, CreateCancellationSource().Token, transactions); + } + + public Task AddBlock(TestBlockchainUtil.AddBlockFlags flags, params Transaction[] transactions) { - await TestUtil.AddBlockAndWaitForHead(false, _cts.Token, transactions); + return TestUtil.AddBlock(flags, CreateCancellationSource().Token, transactions); } - public async Task AddBlock(TestBlockchainUtil.AddBlockFlags flags, params Transaction[] transactions) + public virtual Task AddBlockFromParent(BlockHeader parent, params Transaction[] transactions) { - await TestUtil.AddBlock(flags, _cts.Token, transactions); + return TestUtil.AddBlock(parent, TestBlockchainUtil.AddBlockFlags.DoNotWaitForHead, CreateCancellationSource().Token, transactions); } public async Task AddBlockMayMissTx(params Transaction[] transactions) { - await TestUtil.AddBlockAndWaitForHead(true, _cts.Token, transactions); + await TestUtil.AddBlockAndWaitForHead(true, CreateCancellationSource().Token, transactions); + } + + public async Task AddBlockMayHaveExtraTx(params Transaction[] transactions) + { + await TestUtil.AddBlockMayHaveExtraTx(true, CreateCancellationSource().Token, transactions); } public async Task AddBlockThroughPoW(params Transaction[] transactions) { - await PoWTestUtil.AddBlockAndWaitForHead(_cts.Token, transactions); + await PoWTestUtil.AddBlockAndWaitForHead(CreateCancellationSource().Token, transactions); } public async Task AddBlockDoNotWaitForHead(params Transaction[] transactions) { - await TestUtil.AddBlockDoNotWaitForHead(false, _cts.Token, transactions); + await TestUtil.AddBlockDoNotWaitForHead(false, CreateCancellationSource().Token, transactions); } public void AddTransactions(params Transaction[] txs) diff --git a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchainUtil.cs b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchainUtil.cs index e13e873cecdd..81ea78581f69 100644 --- a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchainUtil.cs +++ b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchainUtil.cs @@ -31,7 +31,11 @@ public record Config(long SlotTime = 10); private Task _previousAddBlock = Task.CompletedTask; - public async Task AddBlock(AddBlockFlags flags, CancellationToken cancellationToken, params Transaction[] transactions) + public Task AddBlock(AddBlockFlags flags, CancellationToken cancellationToken, params Transaction[] transactions) + { + return AddBlock(blockTree.GetProducedBlockParent(null)!, flags, cancellationToken, transactions); + } + public async Task AddBlock(BlockHeader parentToBuildOn, AddBlockFlags flags, CancellationToken cancellationToken, params Transaction[] transactions) { Task waitforHead = flags.HasFlag(AddBlockFlags.DoNotWaitForHead) ? Task.CompletedTask @@ -71,7 +75,7 @@ void OnInvalidBlock(object? sender, IBlockchainProcessor.InvalidBlockEventArgs e while (true) { cancellationToken.ThrowIfCancellationRequested(); - block = await blockProducer.BuildBlock(parentHeader: blockTree.GetProducedBlockParent(null), cancellationToken: cancellationToken); + block = await blockProducer.BuildBlock(parentHeader: parentToBuildOn, cancellationToken: cancellationToken); if (invalidBlock is not null) Assert.Fail($"Invalid block {invalidBlock} produced"); @@ -110,23 +114,35 @@ void OnInvalidBlock(object? sender, IBlockchainProcessor.InvalidBlockEventArgs e await txNewHead; // Wait for tx new head event so that processed tx was removed from txpool invalidBlockDetector.OnInvalidBlock -= OnInvalidBlock; - return txResults; + return block; + } + + public Task AddBlock(CancellationToken cancellationToken) + { + return AddBlock(AddBlockFlags.None, cancellationToken); } - public async Task AddBlockDoNotWaitForHead(bool mayMissTx, CancellationToken cancellationToken, params Transaction[] transactions) + public async Task AddBlockDoNotWaitForHead(bool mayMissTx, CancellationToken cancellationToken, params Transaction[] transactions) { AddBlockFlags flags = AddBlockFlags.DoNotWaitForHead; if (mayMissTx) flags |= AddBlockFlags.MayMissTx; return await AddBlock(flags, cancellationToken, transactions); } + public async Task AddBlockMayHaveExtraTx(bool mayMissTx, CancellationToken cancellationToken, params Transaction[] transactions) + { + AddBlockFlags flags = AddBlockFlags.MayHaveExtraTx; + if (mayMissTx) flags |= AddBlockFlags.MayMissTx; - public async Task AddBlockAndWaitForHead(bool mayMissTx, CancellationToken cancellationToken, params Transaction[] transactions) + return await AddBlock(flags, cancellationToken, transactions); + } + + public async Task AddBlockAndWaitForHead(bool mayMissTx, CancellationToken cancellationToken, params Transaction[] transactions) { AddBlockFlags flags = AddBlockFlags.None; if (mayMissTx) flags |= AddBlockFlags.MayMissTx; - await AddBlock(flags, cancellationToken, transactions); + return await AddBlock(flags, cancellationToken, transactions); } private static async Task WaitAsync(Task task, string error) diff --git a/src/Nethermind/Nethermind.Core.Test/BloomTests.cs b/src/Nethermind/Nethermind.Core.Test/BloomTests.cs index 231b6353f7b9..18b11fbfa932 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/Builders/AccessListBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/AccessListBuilder.cs index 2076ff7d95d9..6bd8b4fdd8d9 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/AccessListBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/AccessListBuilder.cs @@ -5,6 +5,7 @@ using Nethermind.Core.Eip2930; namespace Nethermind.Core.Test.Builders; + public class TestAccessListBuilder : BuilderBase { public TestAccessListBuilder() diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs index dfb6db340be3..a8b4d7c1dafb 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Linq; using Nethermind.Blockchain; using Nethermind.Core.Crypto; diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/BlockHeaderBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/BlockHeaderBuilder.cs index a9e138cdb40d..fbe217d95e68 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/BlockHeaderBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/BlockHeaderBuilder.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using Nethermind.Crypto; using Nethermind.Int256; @@ -160,6 +161,8 @@ public BlockHeaderBuilder WithExtraData(byte[] extraData) return this; } + public BlockHeaderBuilder WithExtraDataHex(string extraDataHex) => WithExtraData(Bytes.FromHexString(extraDataHex)); + public BlockHeaderBuilder WithMixHash(Hash256 mixHash) { TestObjectInternal.MixHash = mixHash; diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/BlockTreeBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/BlockTreeBuilder.cs index 676d91b36463..a27c7a8bae6e 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/BlockTreeBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/BlockTreeBuilder.cs @@ -26,13 +26,13 @@ namespace Nethermind.Core.Test.Builders { - public class BlockTreeBuilder : BuilderBase + public class BlockTreeBuilder(Block genesisBlock, ISpecProvider specProvider) : BuilderBase { - private readonly Block _genesisBlock; - private ISpecProvider _specProvider; + private ISpecProvider _specProvider = specProvider; private IReceiptStorage? _receiptStorage; private IEthereumEcdsa? _ecdsa; private Hash256? _stateRoot; + private Func? _stateRootGen; private Func>? _logCreationFunction; private bool _onlyHeaders; @@ -43,19 +43,6 @@ public BlockTreeBuilder(ISpecProvider specProvider) { } - public BlockTreeBuilder(Block genesisBlock, ISpecProvider specProvider) - { - BlocksDb = new TestMemDb(); - HeadersDb = new TestMemDb(); - BlockNumbersDb = new TestMemDb(); - BlockInfoDb = new TestMemDb(); - MetadataDb = new TestMemDb(); - BadBlocksDb = new TestMemDb(); - - _genesisBlock = genesisBlock; - _specProvider = specProvider; - } - public BlockTreeBuilder WithoutSettingHead { get @@ -106,8 +93,8 @@ protected override void BeforeReturn() public ISyncConfig SyncConfig { get; set; } = new SyncConfig(); - public IDb BlocksDb { get; set; } - public IDb BadBlocksDb { get; set; } + public IDb BlocksDb { get; set; } = new TestMemDb(); + public IDb BadBlocksDb { get; set; } = new TestMemDb(); private IBlockStore? _blockStore; public IBlockStore BlockStore @@ -122,8 +109,8 @@ public IBlockStore BlockStore } } - public IDb HeadersDb { get; set; } - public IDb BlockNumbersDb { get; set; } + public IDb HeadersDb { get; set; } = new TestMemDb(); + public IDb BlockNumbersDb { get; set; } = new TestMemDb(); private IHeaderStore? _headerStore; public IHeaderStore HeaderStore @@ -138,9 +125,9 @@ public IHeaderStore HeaderStore } } - public IDb BlockInfoDb { get; set; } + public IDb BlockInfoDb { get; set; } = new TestMemDb(); - public IDb MetadataDb { get; set; } + public IDb MetadataDb { get; set; } = new TestMemDb(); private IBadBlockStore? _badBlockStore; public IBadBlockStore BadBlockStore @@ -200,6 +187,12 @@ public BlockTreeBuilder WithStateRoot(Hash256 stateRoot) return this; } + public BlockTreeBuilder WithStateRoot(Func stateRootGen) + { + _stateRootGen = stateRootGen; + return this; + } + public BlockTreeBuilder OfChainLength(int chainLength, int splitVariant = 0, int splitFrom = 0, bool withWithdrawals = false, params Address[] blockBeneficiaries) { OfChainLength(out _, chainLength, splitVariant, splitFrom, withWithdrawals, blockBeneficiaries); @@ -208,8 +201,8 @@ public BlockTreeBuilder OfChainLength(int chainLength, int splitVariant = 0, int public BlockTreeBuilder OfChainLength(out Block headBlock, int chainLength, int splitVariant = 0, int splitFrom = 0, bool withWithdrawals = false, params Address[] blockBeneficiaries) { - Block current = _genesisBlock; - headBlock = _genesisBlock; + Block current = genesisBlock; + headBlock = genesisBlock; bool skipGenesis = BlockTree.Genesis is not null; for (int i = 0; i < chainLength; i++) @@ -245,31 +238,70 @@ public BlockTreeBuilder OfChainLength(out Block headBlock, int chainLength, int return this; } + public BlockTreeBuilder OfChainLengthWithSharedSplits(out Block headBlock, int chainLength, int splitVariant = 0, int splitFrom = 0, bool withWithdrawals = false, params Address[] blockBeneficiaries) + { + bool fromGenesis = splitFrom == 0; + Block current = fromGenesis + ? genesisBlock + : BlockTree.FindBlock(splitFrom, BlockTreeLookupOptions.RequireCanonical) ?? throw new ArgumentException("Cannot find split block"); + bool skipGenesis = BlockTree.Genesis is not null; + for (int i = 0; i < chainLength; i++) + { + Address beneficiary = blockBeneficiaries.Length == 0 ? Address.Zero : blockBeneficiaries[i % blockBeneficiaries.Length]; + if ((fromGenesis || i > 0) && !(current.IsGenesis && skipGenesis)) + { + if (_onlyHeaders) + { + BlockTree.SuggestHeader(current.Header); + } + else + { + AddBlockResult result = BlockTree.SuggestBlock(current); + Assert.That(result, Is.EqualTo(AddBlockResult.Added), $"Adding {current.ToString(Block.Format.Short)} at split variant {splitVariant}"); + BlockTree.UpdateMainChain(current); + } + } + + if (i < chainLength - 1) + { + current = CreateBlock(splitVariant, splitFrom, i, current, withWithdrawals, beneficiary); + } + } + + headBlock = current; + + return this; + } + private Block CreateBlock(int splitVariant, int splitFrom, int blockIndex, Block parent, bool withWithdrawals, Address beneficiary) { Block currentBlock; BlockBuilder currentBlockBuilder = Build.A.Block .WithNumber(blockIndex + 1) .WithParent(parent) - .WithWithdrawals(withWithdrawals ? new[] { TestItem.WithdrawalA_1Eth } : null) + .WithWithdrawals(withWithdrawals ? [TestItem.WithdrawalA_1Eth] : null) .WithBaseFeePerGas(withWithdrawals ? UInt256.One : UInt256.Zero) .WithBeneficiary(beneficiary); - if (_stateRoot != null) + if (_stateRoot is not null) { currentBlockBuilder.WithStateRoot(_stateRoot); } if (PostMergeBlockTree) + { currentBlockBuilder.WithPostMergeRules(); + } else + { currentBlockBuilder.WithDifficulty(BlockHeaderBuilder.DefaultDifficulty - (splitFrom > parent.Number ? 0 : (ulong)splitVariant)); + } if (_receiptStorage is not null && blockIndex % 3 == 0) { - Transaction[] transactions = new[] - { + Transaction[] transactions = + [ Build.A.Transaction .WithValue(1) .WithData(Rlp.Encode(blockIndex).Bytes) @@ -282,7 +314,7 @@ private Block CreateBlock(int splitVariant, int splitFrom, int blockIndex, Block .WithGasLimit(GasCostOf.Transaction * 2) .Signed(_ecdsa!, TestItem.PrivateKeyA, _specProvider.GetSpec(blockIndex + 1, null).IsEip155Enabled) .TestObject - }; + ]; currentBlock = currentBlockBuilder .WithTransactions(transactions) @@ -324,6 +356,10 @@ private Block CreateBlock(int splitVariant, int splitFrom, int blockIndex, Block .TestObject; } + if (_stateRootGen is not null) + { + currentBlock.Header.StateRoot = _stateRootGen(currentBlock); + } currentBlock.Header.AuRaStep = blockIndex; return currentBlock; @@ -331,7 +367,7 @@ private Block CreateBlock(int splitVariant, int splitFrom, int blockIndex, Block public BlockTreeBuilder WithOnlySomeBlocksProcessed(int chainLength, int processedChainLength) { - Block current = _genesisBlock; + Block current = genesisBlock; for (int i = 0; i < chainLength; i++) { BlockTree.SuggestBlock(current); diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/RandomExtensions.cs b/src/Nethermind/Nethermind.Core.Test/Builders/RandomExtensions.cs new file mode 100644 index 000000000000..17135ff14fa3 --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/Builders/RandomExtensions.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using Nethermind.Crypto; + +namespace Nethermind.Core.Test.Builders; + +public static class RandomExtensions +{ + public static T NextFrom(this Random random, IReadOnlyList values) => values[random.Next(values.Count)]; + + public static T NextFrom(this ICryptoRandom random, IReadOnlyList values) => values[random.NextInt(values.Count)]; +} diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.Tree.cs b/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.Tree.cs index a114492d72a3..2033e43a19f6 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.Tree.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.Tree.cs @@ -8,7 +8,6 @@ using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Serialization.Rlp; -using Nethermind.Evm.State; using Nethermind.State; using Nethermind.State.Snap; using Nethermind.Trie.Pruning; @@ -40,12 +39,12 @@ public static class Tree public static PathWithStorageSlot[] SlotsWithPaths = new PathWithStorageSlot[] { - new PathWithStorageSlot(new Hash256("0000000000000000000000000000000000000000000000000000000001101234"), Rlp.Encode(Bytes.FromHexString("0xab12000000000000000000000000000000000000000000000000000000000000000000000000000000")).Bytes), - new PathWithStorageSlot(new Hash256("0000000000000000000000000000000000000000000000000000000001112345"), Rlp.Encode(Bytes.FromHexString("0xab34000000000000000000000000000000000000000000000000000000000000000000000000000000")).Bytes), - new PathWithStorageSlot(new Hash256("0000000000000000000000000000000000000000000000000000000001113456"), Rlp.Encode(Bytes.FromHexString("0xab56000000000000000000000000000000000000000000000000000000000000000000000000000000")).Bytes), - new PathWithStorageSlot(new Hash256("0000000000000000000000000000000000000000000000000000000001114567"), Rlp.Encode(Bytes.FromHexString("0xab78000000000000000000000000000000000000000000000000000000000000000000000000000000")).Bytes), - new PathWithStorageSlot(new Hash256("0000000000000000000000000000000000000000000000000000000001123456"), Rlp.Encode(Bytes.FromHexString("0xab90000000000000000000000000000000000000000000000000000000000000000000000000000000")).Bytes), - new PathWithStorageSlot(new Hash256("0000000000000000000000000000000000000000000000000000000001123457"), Rlp.Encode(Bytes.FromHexString("0xab9a000000000000000000000000000000000000000000000000000000000000000000000000000000")).Bytes), + new PathWithStorageSlot(new Hash256("0000000000000000000000000000000000000000000000000000000001101234"), Rlp.Encode(Bytes.FromHexString("0xab12000000000000000000000000000000000000000000000000000000000000")).Bytes), + new PathWithStorageSlot(new Hash256("0000000000000000000000000000000000000000000000000000000001112345"), Rlp.Encode(Bytes.FromHexString("0xab34000000000000000000000000000000000000000000000000000000000000")).Bytes), + new PathWithStorageSlot(new Hash256("0000000000000000000000000000000000000000000000000000000001113456"), Rlp.Encode(Bytes.FromHexString("0xab56000000000000000000000000000000000000000000000000000000000000")).Bytes), + new PathWithStorageSlot(new Hash256("0000000000000000000000000000000000000000000000000000000001114567"), Rlp.Encode(Bytes.FromHexString("0xab78000000000000000000000000000000000000000000000000000000000000")).Bytes), + new PathWithStorageSlot(new Hash256("0000000000000000000000000000000000000000000000000000000001123456"), Rlp.Encode(Bytes.FromHexString("0xab90000000000000000000000000000000000000000000000000000000000000")).Bytes), + new PathWithStorageSlot(new Hash256("0000000000000000000000000000000000000000000000000000000001123457"), Rlp.Encode(Bytes.FromHexString("0xab9a000000000000000000000000000000000000000000000000000000000000")).Bytes), }; public static StateTree GetStateTree(ITrieStore? store = null) diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs index 895b8d73bbb5..d6963182642e 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs @@ -6,6 +6,7 @@ using CkzgLib; using Nethermind.Core.Crypto; using Nethermind.Core.Eip2930; +using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Crypto; using Nethermind.Int256; @@ -52,12 +53,14 @@ public TransactionBuilder To(Address? address) return this; } - public TransactionBuilder WithData(byte[] data) + public TransactionBuilder WithData(byte[]? data) { TestObjectInternal.Data = data; return this; } + public TransactionBuilder WithDataHex(string dataHex) => WithData(Bytes.FromHexString(dataHex)); + public TransactionBuilder WithCode(byte[] data) { TestObjectInternal.Data = data; @@ -257,11 +260,11 @@ public TransactionBuilder SignedAndResolved(IEthereumEcdsa ecdsa, PrivateKey return this; } - public TransactionBuilder SignedAndResolved(PrivateKey? privateKey = null) + public TransactionBuilder SignedAndResolved(PrivateKey? privateKey = null, bool isEip155Enabled = true) { privateKey ??= TestItem.IgnoredPrivateKey; EthereumEcdsa ecdsa = new(TestObjectInternal.ChainId ?? TestBlockchainIds.ChainId); - ecdsa.Sign(privateKey, TestObjectInternal, true); + ecdsa.Sign(privateKey, TestObjectInternal, isEip155Enabled); TestObjectInternal.SenderAddress = privateKey.Address; return this; } diff --git a/src/Nethermind/Nethermind.Core.Test/BytesTests.cs b/src/Nethermind/Nethermind.Core.Test/BytesTests.cs index b4ddca6e783b..ed7ccc0c2ac4 100644 --- a/src/Nethermind/Nethermind.Core.Test/BytesTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/BytesTests.cs @@ -19,11 +19,11 @@ public class BytesTests { [TestCase("0x", "0x", 0)] [TestCase(null, null, 0)] - [TestCase(null, "0x", 1)] - [TestCase("0x", null, -1)] + [TestCase(null, "0x", -1)] + [TestCase("0x", null, 1)] [TestCase("0x01", "0x01", 0)] - [TestCase("0x01", "0x0102", 1)] - [TestCase("0x0102", "0x01", -1)] + [TestCase("0x01", "0x0102", -1)] + [TestCase("0x0102", "0x01", 1)] public void Compares_bytes_properly(string? hexString1, string? hexString2, int expectedResult) { IComparer comparer = Bytes.Comparer; @@ -463,9 +463,143 @@ public void Xor(byte[] first, byte[] second, byte[] expected) } [Test] - public void NullableComparision() + public void NullableComparison() { Bytes.NullableEqualityComparer.Equals(null, null).Should().BeTrue(); } + + [Test] + public void FastHash_EmptyInput_ReturnsZero() + { + ReadOnlySpan empty = ReadOnlySpan.Empty; + empty.FastHash().Should().Be(0); + } + + [Test] + public void FastHash_SameInput_ReturnsSameHash() + { + byte[] input = new byte[100]; + TestContext.CurrentContext.Random.NextBytes(input); + + int hash1 = ((ReadOnlySpan)input).FastHash(); + int hash2 = ((ReadOnlySpan)input).FastHash(); + + hash1.Should().Be(hash2); + } + + [Test] + public void FastHash_DifferentInput_ReturnsDifferentHash() + { + byte[] input1 = new byte[100]; + byte[] input2 = new byte[100]; + TestContext.CurrentContext.Random.NextBytes(input1); + Array.Copy(input1, input2, input1.Length); + input2[50] ^= 0xFF; // Flip bits at position 50 + + int hash1 = ((ReadOnlySpan)input1).FastHash(); + int hash2 = ((ReadOnlySpan)input2).FastHash(); + + hash1.Should().NotBe(hash2); + } + + // Test cases for the fold-back bug fix: remaining in [49-63] after 64-byte initial load + // For len=113 to 127, remaining = len-64 = 49 to 63, which requires the last64 fold-back + [TestCase(113)] // remaining=49, boundary case for last64 + [TestCase(120)] // remaining=56, middle of the gap range + [TestCase(127)] // remaining=63, upper boundary + [TestCase(65)] // remaining=1, lower boundary for >64 path + [TestCase(80)] // remaining=16 + [TestCase(96)] // remaining=32 + [TestCase(112)] // remaining=48, boundary where last64 is NOT needed + public void FastHash_AllBytesAreHashed_FoldBackCoverage(int length) + { + byte[] input = new byte[length]; + TestContext.CurrentContext.Random.NextBytes(input); + + int originalHash = ((ReadOnlySpan)input).FastHash(); + + // Verify that changing any byte changes the hash + // This catches the gap bug where bytes[64-71] weren't being hashed + for (int i = 0; i < length; i++) + { + byte[] modified = (byte[])input.Clone(); + modified[i] ^= 0xFF; + + int modifiedHash = ((ReadOnlySpan)modified).FastHash(); + modifiedHash.Should().NotBe(originalHash, $"Changing byte at index {i} should change the hash for length {length}"); + } + } + + // Specifically test the gap range that was buggy: bytes[64-71] for len=120 + [Test] + public void FastHash_GapBytesAreHashed_Len120() + { + byte[] input = new byte[120]; + TestContext.CurrentContext.Random.NextBytes(input); + + int originalHash = ((ReadOnlySpan)input).FastHash(); + + // The bug was that bytes[64-71] weren't hashed for len=120 + // Test each byte in the gap + for (int i = 64; i < 72; i++) + { + byte[] modified = (byte[])input.Clone(); + modified[i] ^= 0xFF; + + int modifiedHash = ((ReadOnlySpan)modified).FastHash(); + modifiedHash.Should().NotBe(originalHash, $"Changing byte at index {i} (in gap range) should change the hash"); + } + } + + // Test medium-large case (33-64 bytes) with overlap to verify it works + [TestCase(50)] // Tests overlap in medium-large path + public void FastHash_MediumLarge_AllBytesContribute(int length) + { + byte[] input = new byte[length]; + TestContext.CurrentContext.Random.NextBytes(input); + + int originalHash = ((ReadOnlySpan)input).FastHash(); + + // Test ALL bytes to verify overlap handling works + for (int i = 0; i < length; i++) + { + byte[] modified = (byte[])input.Clone(); + modified[i] ^= 0xFF; + + int modifiedHash = ((ReadOnlySpan)modified).FastHash(); + modifiedHash.Should().NotBe(originalHash, $"Changing byte at index {i} should change the hash for length {length}"); + } + } + + [TestCase(1)] + [TestCase(7)] + [TestCase(8)] + [TestCase(15)] + [TestCase(16)] + [TestCase(31)] + [TestCase(32)] + [TestCase(33)] + [TestCase(64)] + [TestCase(128)] + [TestCase(256)] + [TestCase(500)] + public void FastHash_VariousLengths_AllBytesContribute(int length) + { + byte[] input = new byte[length]; + TestContext.CurrentContext.Random.NextBytes(input); + + int originalHash = ((ReadOnlySpan)input).FastHash(); + + // Test first, middle, and last bytes to ensure all contribute + int[] indicesToTest = [0, length / 2, length - 1]; + foreach (int i in indicesToTest) + { + byte[] modified = (byte[])input.Clone(); + modified[i] ^= 0xFF; + + int modifiedHash = ((ReadOnlySpan)modified).FastHash(); + modifiedHash.Should().NotBe(originalHash, $"Changing byte at index {i} should change the hash for length {length}"); + } + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Caching/ClockCacheTests.cs b/src/Nethermind/Nethermind.Core.Test/Caching/ClockCacheTests.cs index f4bdf703576e..0dd477bcaeb9 100644 --- a/src/Nethermind/Nethermind.Core.Test/Caching/ClockCacheTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Caching/ClockCacheTests.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; @@ -242,6 +242,19 @@ public void Can_delete() cache.Delete(_addresses[0]).Should().BeFalse(); } + [Test] + public void Delete_returns_value() + { + Cache cache = Create(); + cache.Set(_addresses[0], _accounts[0]); + + cache.Delete(_addresses[0], out Account? value).Should().BeTrue(); + value.Should().Be(_accounts[0]); + + cache.Delete(_addresses[0], out Account? noValue).Should().BeFalse(); + noValue.Should().BeNull(); + } + [Test] public void Clear_should_free_all_capacity() { diff --git a/src/Nethermind/Nethermind.Core.Test/Collections/ArrayPoolListRefTests.cs b/src/Nethermind/Nethermind.Core.Test/Collections/ArrayPoolListRefTests.cs new file mode 100644 index 000000000000..44d429539a88 --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/Collections/ArrayPoolListRefTests.cs @@ -0,0 +1,274 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Linq; +using FluentAssertions; +using Nethermind.Core.Collections; +using NUnit.Framework; + +namespace Nethermind.Core.Test.Collections; + +[Parallelizable(ParallelScope.All)] +public class ArrayPoolListRefTests +{ + [Test] + public void Empty_list() + { + using ArrayPoolListRef list = new(1024); + list.AsSpan().ToArray().Should().BeEquivalentTo(Array.Empty()); + list.Count.Should().Be(0); + list.Capacity.Should().Be(1024); + } + + [Test] + public void Should_not_hang_when_capacity_is_zero() + { + using ArrayPoolListRef list = new(0); + list.AsSpan().ToArray().Should().BeEquivalentTo(Array.Empty()); + list.Add(1); + list.Count.Should().Be(1); + list.Remove(1); + list.Count.Should().Be(0); + list.Add(1); + list.Count.Should().Be(1); + } + + [Test] + public void Add_should_work() + { + using ArrayPoolListRef list = new(1024); + list.AddRange(Enumerable.Range(0, 4)); + list.AsSpan().ToArray().Should().BeEquivalentTo(Enumerable.Range(0, 4)); + } + + [Test] + public void Add_should_expand() + { + using ArrayPoolListRef list = new(4); + list.AddRange(Enumerable.Range(0, 50)); + list.AsSpan().ToArray().Should().BeEquivalentTo(Enumerable.Range(0, 50)); + list.Count.Should().Be(50); + list.Capacity.Should().Be(64); + } + + [Test] + public void Clear_should_clear() + { + using ArrayPoolListRef list = new(4); + list.AddRange(Enumerable.Range(0, 50)); + list.Clear(); + list.AsSpan().ToArray().Should().BeEquivalentTo(Array.Empty()); + list.Count.Should().Be(0); + list.Capacity.Should().Be(64); + } + + [TestCase(0, ExpectedResult = true)] + [TestCase(20, ExpectedResult = true)] + [TestCase(100, ExpectedResult = false)] + [TestCase(-1, ExpectedResult = false)] + public bool Contains_should_check_ok(int item) + { + using ArrayPoolListRef list = new(4); + list.AddRange(Enumerable.Range(0, 50)); + return list.Contains(item); + } + + [Test] + public void Can_enumerate() + { + using ArrayPoolListRef list = new(4); + list.AddRange(Enumerable.Range(0, 50)); + list.ToArray().Should().BeEquivalentTo(Enumerable.Range(0, 50)); + } + + [TestCase(0, new[] { -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 })] + [TestCase(4, new[] { 0, 1, 2, 3, -1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 })] + [TestCase(16, new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, -1 })] + public void Insert_should_expand(int index, int[] expected) + { + using ArrayPoolListRef list = new(4); + list.AddRange(Enumerable.Range(0, 16)); + list.Insert(index, -1); + list.AsSpan().ToArray().Should().BeEquivalentTo(expected); + } + + [TestCase(10)] + [TestCase(-1)] + public void Insert_should_throw(int index) + { + using ArrayPoolListRef list = new(4); + list.AddRange(Enumerable.Range(0, 8)); + bool thrown = false; + try + { + list.Insert(index, -1); + } + catch (ArgumentOutOfRangeException) + { + thrown = true; + } + + thrown.Should().BeTrue(); + } + + [TestCase(0, ExpectedResult = 0)] + [TestCase(40, ExpectedResult = 40)] + [TestCase(50, ExpectedResult = -1)] + [TestCase(-1, ExpectedResult = -1)] + public int IndexOf_should_return_index(int item) + { + using ArrayPoolListRef list = new(4); + list.AddRange(Enumerable.Range(0, 50)); + return list.IndexOf(item); + } + + + [TestCase(0, true, new[] { 1, 2, 3, 4, 5, 6, 7 })] + [TestCase(7, true, new[] { 0, 1, 2, 3, 4, 5, 6 })] + [TestCase(8, false, new[] { 0, 1, 2, 3, 4, 5, 6, 7 })] + [TestCase(-1, false, new[] { 0, 1, 2, 3, 4, 5, 6, 7 })] + public void Remove_should_remove(int item, bool removed, int[] expected) + { + using ArrayPoolListRef list = new(4); + list.AddRange(Enumerable.Range(0, 8)); + list.Remove(item).Should().Be(removed); + list.AsSpan().ToArray().Should().BeEquivalentTo(expected); + } + + [TestCase(0, new[] { 1, 2, 3, 4, 5, 6, 7 })] + [TestCase(7, new[] { 0, 1, 2, 3, 4, 5, 6 })] + public void RemoveAt_should_remove(int item, int[] expected) + { + using ArrayPoolListRef list = new(4); + list.AddRange(Enumerable.Range(0, 8)); + list.RemoveAt(item); + list.AsSpan().ToArray().Should().BeEquivalentTo(expected); + } + + [TestCase(8, new[] { 0, 1, 2, 3, 4, 5, 6, 7 })] + [TestCase(-1, new[] { 0, 1, 2, 3, 4, 5, 6, 7 })] + public void RemoveAt_should_throw(int item, int[] expected) + { + using ArrayPoolListRef list = new(4); + list.AddRange(Enumerable.Range(0, 8)); + bool thrown = false; + try + { + list.RemoveAt(item); + } + catch (ArgumentOutOfRangeException) + { + thrown = true; + } + + thrown.Should().BeTrue(); + } + + [Test] + public void CopyTo_should_copy() + { + using ArrayPoolListRef list = new(4); + list.AddRange(Enumerable.Range(0, 50)); + int[] array = new int[51]; + list.CopyTo(array, 1); + array.Should().BeEquivalentTo(Enumerable.Range(0, 1).Concat(Enumerable.Range(0, 50))); + } + + [TestCase(0, ExpectedResult = 0)] + [TestCase(7, ExpectedResult = 7)] + public int Get_should_return(int item) + { + using ArrayPoolListRef list = new(4); + list.AddRange(Enumerable.Range(0, 8)); + return list[item]; + } + + [TestCase(8)] + [TestCase(-1)] + public void Get_should_throw(int item) + { + using ArrayPoolListRef list = new(4); + list.AddRange(Enumerable.Range(0, 8)); + bool thrown = false; + try + { + int _ = list[item]; + } + catch (ArgumentOutOfRangeException) + { + thrown = true; + } + + thrown.Should().BeTrue(); + } + + [TestCase(0, ExpectedResult = -1)] + [TestCase(7, ExpectedResult = -1)] + public int Set_should_set(int item) + { + ArrayPoolListRef list = new(4); + try + { + list.AddRange(Enumerable.Range(0, 8)); + list[item] = -1; + return list[item]; + } + finally + { + list.Dispose(); + } + } + + [TestCase(8)] + [TestCase(-1)] + public void Set_should_throw(int item) + { + ArrayPoolListRef list = new(4); + list.AddRange(Enumerable.Range(0, 8)); + bool thrown = false; + try + { + list[item] = 1; + } + catch (ArgumentOutOfRangeException) + { + thrown = true; + } + finally + { + list.Dispose(); + } + + thrown.Should().BeTrue(); + } + + [TestCase(1, 16)] + [TestCase(14, 16)] + [TestCase(15, 32)] + [TestCase(20, 32)] + [TestCase(100, 128)] + public void AddRange_should_expand(int items, int expectedCapacity) + { + using ArrayPoolListRef list = new(16, 0, 1); + list.AddRange(Enumerable.Range(2, items)); + list.AsSpan().ToArray().Should().BeEquivalentTo(Enumerable.Range(0, items + 2)); + list.Capacity.Should().Be(expectedCapacity); + } + + [Test] + public void Dispose_ShouldNotHaveAnEffect_OnEmptyPool() + { + var list = new ArrayPoolListRef(0); + list.Dispose(); + int _ = list.Count; + } + + [Test] + public void Can_resize_totally_empty_list() + { + using ArrayPoolListRef list = new(0); + list.Add(1); + list.Count.Should().Be(1); + } +} diff --git a/src/Nethermind/Nethermind.Core.Test/Collections/ArrayPoolListTests.cs b/src/Nethermind/Nethermind.Core.Test/Collections/ArrayPoolListTests.cs index 26abdb0f47f6..dc8faa920b43 100644 --- a/src/Nethermind/Nethermind.Core.Test/Collections/ArrayPoolListTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Collections/ArrayPoolListTests.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -76,6 +77,14 @@ public bool Contains_should_check_ok(int item) return list.Contains(item); } + [Test] + public void Can_enumerate() + { + using ArrayPoolList list = new(4); + list.AddRange(Enumerable.Range(0, 50)); + list.ToArray().Should().BeEquivalentTo(Enumerable.Range(0, 50)); + } + [TestCase(0, new[] { -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 })] [TestCase(4, new[] { 0, 1, 2, 3, -1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 })] [TestCase(16, new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, -1 })] @@ -328,6 +337,32 @@ public void Dispose_recursive() list.DisposeRecursive(); } + [Test] + public void RemoveAt_should_not_throw_when_capacity_equals_count() + { + ArrayPool pool = new ExactSizeArrayPool(); + using ArrayPoolList list = new(pool, 8, 8); + + for (int i = 0; i < list.Count; i++) + { + list[i] = i; + } + + Action act = () => list.RemoveAt(2); + + act.Should().NotThrow(); + list.Should().BeEquivalentTo(new[] { 0, 1, 3, 4, 5, 6, 7 }); + } + + private sealed class ExactSizeArrayPool : ArrayPool + { + public override T[] Rent(int minimumLength) => new T[minimumLength]; + + public override void Return(T[] array, bool clearArray = false) + { + } + } + #if DEBUG [Test] [Explicit("Crashes the test runner")] diff --git a/src/Nethermind/Nethermind.Core.Test/Collections/SeqlockCacheTests.cs b/src/Nethermind/Nethermind.Core.Test/Collections/SeqlockCacheTests.cs new file mode 100644 index 000000000000..ee076970d44b --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/Collections/SeqlockCacheTests.cs @@ -0,0 +1,428 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Nethermind.Core.Collections; +using Nethermind.Int256; +using NUnit.Framework; + +namespace Nethermind.Core.Test.Collections; + +public class SeqlockCacheTests +{ + private static StorageCell CreateKey(int seed) + { + byte[] addressBytes = new byte[20]; + new Random(seed).NextBytes(addressBytes); + return new StorageCell(new Address(addressBytes), new UInt256((ulong)seed)); + } + + private static byte[] CreateValue(int seed) + { + byte[] value = new byte[32]; + new Random(seed).NextBytes(value); + return value; + } + + [Test] + public void New_cache_returns_miss() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + + bool found = cache.TryGetValue(in key, out byte[]? value); + + found.Should().BeFalse(); + value.Should().BeNull(); + } + + [Test] + public void Set_then_get_returns_value() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] expected = CreateValue(1); + + cache.Set(in key, expected); + bool found = cache.TryGetValue(in key, out byte[]? value); + + found.Should().BeTrue(); + value.Should().BeSameAs(expected); + } + + [Test] + public void Set_overwrites_existing_value() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] first = CreateValue(1); + byte[] second = CreateValue(2); + + cache.Set(in key, first); + cache.Set(in key, second); + bool found = cache.TryGetValue(in key, out byte[]? value); + + found.Should().BeTrue(); + value.Should().BeSameAs(second); + } + + [Test] + public void Set_with_same_value_is_noop() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] expected = CreateValue(1); + + cache.Set(in key, expected); + cache.Set(in key, expected); // Same reference - should be fast-path no-op + bool found = cache.TryGetValue(in key, out byte[]? value); + + found.Should().BeTrue(); + value.Should().BeSameAs(expected); + } + + [Test] + public void Null_value_can_be_stored_and_retrieved() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + + cache.Set(in key, null); + bool found = cache.TryGetValue(in key, out byte[]? value); + + found.Should().BeTrue(); + value.Should().BeNull(); + } + + [Test] + public void GetOrAdd_returns_existing_value() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] expected = CreateValue(1); + + cache.Set(in key, expected); + byte[]? result = cache.GetOrAdd(in key, static (in StorageCell _) => new byte[32]); + + result.Should().BeSameAs(expected); + } + + [Test] + public void GetOrAdd_calls_factory_on_miss() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] factoryResult = CreateValue(1); + + byte[]? result = cache.GetOrAdd(in key, (in StorageCell _) => factoryResult); + + result.Should().BeSameAs(factoryResult); + + // Value should now be cached + bool found = cache.TryGetValue(in key, out byte[]? cached); + found.Should().BeTrue(); + cached.Should().BeSameAs(factoryResult); + } + + [Test] + public void GetOrAdd_with_func_returns_existing_value() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] expected = CreateValue(1); + + cache.Set(in key, expected); + byte[]? result = cache.GetOrAdd(in key, static (in _) => new byte[32]); + + result.Should().BeSameAs(expected); + } + + [Test] + public void GetOrAdd_with_func_calls_factory_on_miss() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] factoryResult = CreateValue(1); + + byte[]? result = cache.GetOrAdd(in key, (in _) => factoryResult); + + result.Should().BeSameAs(factoryResult); + } + + [Test] + public void Clear_invalidates_all_entries() + { + SeqlockCache cache = new(); + StorageCell key1 = CreateKey(1); + StorageCell key2 = CreateKey(2); + + cache.Set(in key1, CreateValue(1)); + cache.Set(in key2, CreateValue(2)); + + cache.Clear(); + + cache.TryGetValue(in key1, out _).Should().BeFalse(); + cache.TryGetValue(in key2, out _).Should().BeFalse(); + } + + [Test] + public void Clear_allows_new_entries() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] beforeClear = CreateValue(1); + byte[] afterClear = CreateValue(2); + + cache.Set(in key, beforeClear); + cache.Clear(); + cache.Set(in key, afterClear); + + bool found = cache.TryGetValue(in key, out byte[]? value); + found.Should().BeTrue(); + value.Should().BeSameAs(afterClear); + } + + [Test] + public void Multiple_clears_work() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + + for (int i = 0; i < 100; i++) + { + byte[] value = CreateValue(i); + cache.Set(in key, value); + cache.TryGetValue(in key, out byte[]? retrieved).Should().BeTrue(); + retrieved.Should().BeSameAs(value); + cache.Clear(); + cache.TryGetValue(in key, out _).Should().BeFalse(); + } + } + + [Test] + public void Different_keys_can_be_stored() + { + SeqlockCache cache = new(); + const int count = 100; + + StorageCell[] keys = new StorageCell[count]; + byte[][] values = new byte[count][]; + + for (int i = 0; i < count; i++) + { + keys[i] = CreateKey(i); + values[i] = CreateValue(i); + cache.Set(in keys[i], values[i]); + } + + // Note: This is a direct-mapped cache, so some entries may be evicted + // due to hash collisions. We just verify that at least some survive. + int hits = 0; + for (int i = 0; i < count; i++) + { + if (cache.TryGetValue(in keys[i], out byte[]? value) && ReferenceEquals(value, values[i])) + { + hits++; + } + } + + hits.Should().BeGreaterThan(0, "at least some entries should survive"); + } + + [Test] + public void Concurrent_reads_are_safe() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] expected = CreateValue(1); + cache.Set(in key, expected); + + const int threadCount = 8; + const int iterations = 10000; + int successCount = 0; + + Parallel.For(0, threadCount, _ => + { + for (int i = 0; i < iterations; i++) + { + if (cache.TryGetValue(in key, out byte[]? value) && ReferenceEquals(value, expected)) + { + Interlocked.Increment(ref successCount); + } + } + }); + + successCount.Should().Be(threadCount * iterations); + } + + [Test] + public void Concurrent_writes_do_not_corrupt() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + + const int threadCount = 8; + const int iterations = 1000; + byte[][] values = new byte[threadCount][]; + for (int i = 0; i < threadCount; i++) + { + values[i] = CreateValue(i); + } + + Parallel.For(0, threadCount, t => + { + for (int i = 0; i < iterations; i++) + { + cache.Set(in key, values[t]); + } + }); + + // After concurrent writes, the cache should contain one of the values + bool found = cache.TryGetValue(in key, out byte[]? result); + if (found) + { + // Value should be one of the values we wrote + bool isValid = false; + for (int i = 0; i < threadCount; i++) + { + if (ReferenceEquals(result, values[i])) + { + isValid = true; + break; + } + } + isValid.Should().BeTrue("cached value should be one of the written values"); + } + } + + [Test] + public void Concurrent_read_write_is_safe() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] value1 = CreateValue(1); + byte[] value2 = CreateValue(2); + + const int iterations = 10000; + bool stop = false; + + // Writer thread + Task writer = Task.Run(() => + { + for (int i = 0; i < iterations && !stop; i++) + { + cache.Set(in key, i % 2 == 0 ? value1 : value2); + } + }); + + // Reader thread + int validReads = 0; + int misses = 0; + Task reader = Task.Run(() => + { + for (int i = 0; i < iterations; i++) + { + if (cache.TryGetValue(in key, out byte[]? value)) + { + // Value should be either value1 or value2 + if (ReferenceEquals(value, value1) || ReferenceEquals(value, value2)) + { + Interlocked.Increment(ref validReads); + } + } + else + { + Interlocked.Increment(ref misses); + } + } + }); + + Task.WaitAll(writer, reader); + stop = true; + + // All reads should have returned valid values (or miss due to concurrent write) + (validReads + misses).Should().Be(iterations); + } + + [Test] + public void AddressAsKey_works_with_cache() + { + SeqlockCache cache = new(); + Address address = new Address("0x1234567890123456789012345678901234567890"); + AddressAsKey key = address; + Account account = new Account(100, 1); + + cache.Set(in key, account); + bool found = cache.TryGetValue(in key, out Account? result); + + found.Should().BeTrue(); + result.Should().BeSameAs(account); + } + + [Test] + public void Concurrent_set_same_value_fast_path_is_safe() + { + // Tests the fast-path optimization where Set skips write if value matches. + // This exercises the seqlock protocol in the fast-path to avoid torn reads. + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] value = CreateValue(1); + + cache.Set(in key, value); + + const int threadCount = 8; + const int iterations = 10000; + + // Multiple threads all trying to set the same key to the same value + // This hammers the fast-path check + Parallel.For(0, threadCount, _ => + { + for (int i = 0; i < iterations; i++) + { + cache.Set(in key, value); + } + }); + + // Value should still be retrievable and correct + bool found = cache.TryGetValue(in key, out byte[]? result); + found.Should().BeTrue(); + result.Should().BeSameAs(value); + } + + [Test] + public void Concurrent_set_alternating_values_is_safe() + { + // Tests concurrent writes with different values interleaved with same-value writes. + // Exercises both the fast-path (same value) and slow-path (different value). + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[][] values = new byte[4][]; + for (int i = 0; i < values.Length; i++) + { + values[i] = CreateValue(i); + } + + const int threadCount = 8; + const int iterations = 5000; + + Parallel.For(0, threadCount, t => + { + for (int i = 0; i < iterations; i++) + { + // Each thread cycles through values, creating both fast-path and slow-path scenarios + cache.Set(in key, values[(t + i) % values.Length]); + } + }); + + // After all writes, cache should contain one of the valid values + bool found = cache.TryGetValue(in key, out byte[]? result); + if (found) + { + bool isValid = Array.Exists(values, v => ReferenceEquals(v, result)); + isValid.Should().BeTrue("cached value should be one of the written values"); + } + } +} diff --git a/src/Nethermind/Nethermind.Core.Test/Container/ContainerBuilderExtensions.cs b/src/Nethermind/Nethermind.Core.Test/Container/ContainerBuilderExtensions.cs index d9b1d3bd8432..b4bfa48ce06e 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; namespace Nethermind.Core.Test.Container; @@ -83,6 +84,21 @@ public static ContainerBuilder AddSingletonWithAccessToPreviousRegistration(t public static ContainerBuilder WithGenesisPostProcessor(this ContainerBuilder builder, Action postProcessor) { - return builder.AddScoped((worldState) => new FunctionalGenesisPostProcessor(worldState, postProcessor)); + return builder.AddScoped((worldState) => + { + return new FunctionalGenesisPostProcessor((block) => + { + postProcessor(block, worldState); + }); + }); + } + + 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/Container/FunctionalGenesisPostProcessor.cs b/src/Nethermind/Nethermind.Core.Test/Container/FunctionalGenesisPostProcessor.cs index b1d45470dc6b..fbb50322f345 100644 --- a/src/Nethermind/Nethermind.Core.Test/Container/FunctionalGenesisPostProcessor.cs +++ b/src/Nethermind/Nethermind.Core.Test/Container/FunctionalGenesisPostProcessor.cs @@ -3,14 +3,13 @@ using System; using Nethermind.Blockchain; -using Nethermind.Evm.State; namespace Nethermind.Core.Test.Container; -public class FunctionalGenesisPostProcessor(IWorldState state, Action postProcessor) : IGenesisPostProcessor +public class FunctionalGenesisPostProcessor(Action postProcessor) : IGenesisPostProcessor { public void PostProcess(Block genesis) { - postProcessor.Invoke(genesis, state); + postProcessor.Invoke(genesis); } } diff --git a/src/Nethermind/Nethermind.Core.Test/Container/NethermindConstructorFinderTests.cs b/src/Nethermind/Nethermind.Core.Test/Container/NethermindConstructorFinderTests.cs index 6b65a546377e..109752546bf3 100644 --- a/src/Nethermind/Nethermind.Core.Test/Container/NethermindConstructorFinderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Container/NethermindConstructorFinderTests.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Autofac; using Autofac.Core; using Nethermind.Core.Container; diff --git a/src/Nethermind/Nethermind.Core.Test/Crypto/SignatureTests.cs b/src/Nethermind/Nethermind.Core.Test/Crypto/SignatureTests.cs index d810fd162480..939457a1f4dc 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 b13c69f5aff7..84370c569b63 100644 --- a/src/Nethermind/Nethermind.Core.Test/Db/TestMemDbProvider.cs +++ b/src/Nethermind/Nethermind.Core.Test/Db/TestMemDbProvider.cs @@ -23,7 +23,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/BlockDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs index c9a16906c344..2f4575e7c705 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; @@ -16,9 +18,9 @@ namespace Nethermind.Core.Test.Encoding; public class BlockDecoderTests { - private readonly Block[] _scenarios; + private static readonly Block[] _scenarios = BuildScenarios(); - public BlockDecoderTests() + private static Block[] BuildScenarios() { var transactions = new Transaction[100]; for (int i = 0; i < transactions.Length; i++) @@ -40,8 +42,8 @@ public BlockDecoderTests() .TestObject; } - _scenarios = new[] - { + return + [ Build.A.Block.WithNumber(1).TestObject, Build.A.Block .WithNumber(1) @@ -95,9 +97,14 @@ public BlockDecoderTests() .WithExcessBlobGas(ulong.MaxValue) .WithMixHash(Keccak.EmptyTreeHash) .TestObject - }; + ]; } + private static IEnumerable BlockScenarios() => _scenarios; + + private static IEnumerable BlockScenariosWithTxs() => + _scenarios.Where(static b => b.Transactions.Length > 0); + [Test] public void Can_do_roundtrip_null([Values(true, false)] bool valueDecoder) { @@ -122,17 +129,16 @@ public void Can_do_roundtrip_regression([Values(true, false)] bool valueDecoder) } [Test] - public void Can_do_roundtrip_scenarios([Values(true, false)] bool valueDecoder) + public void Can_do_roundtrip_scenarios( + [ValueSource(nameof(BlockScenarios))] Block block, + [Values(true, false)] bool valueDecoder) { BlockDecoder decoder = new(); - foreach (Block block in _scenarios) - { - Rlp encoded = decoder.Encode(block); - Rlp.ValueDecoderContext valueDecoderContext = new(encoded.Bytes); - Block? decoded = valueDecoder ? decoder.Decode(ref valueDecoderContext) : decoder.Decode(new RlpStream(encoded.Bytes)); - Rlp encoded2 = decoder.Encode(decoded); - Assert.That(encoded2.Bytes.ToHexString(), Is.EqualTo(encoded.Bytes.ToHexString())); - } + Rlp encoded = decoder.Encode(block); + Rlp.ValueDecoderContext valueDecoderContext = new(encoded.Bytes); + Block? decoded = valueDecoder ? decoder.Decode(ref valueDecoderContext) : decoder.Decode(new RlpStream(encoded.Bytes)); + Rlp encoded2 = decoder.Encode(decoded); + Assert.That(encoded2.Bytes.ToHexString(), Is.EqualTo(encoded.Bytes.ToHexString())); } [TestCase("0xf902cef9025ba055870e2f3ef77a9e6163ee5c005dc51d648a2eead382b9044b1a5ad2ee69b0c6a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0b77e3b74c6c8af85408677375183385a2e55446bd071bf193a4958f7417dc8fba056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800188016345785d8a0000800c80a0000000000000000000000000000000000000000000000000000000000000000088000000000000000007a0cc3b10b54dc4e97c01f1df20e8b95874cd5fe83bf6eae64935a16cb08db85fa98080a00000000000000000000000000000000000000000000000000000000000000000a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855c0c0f86ce08080946389e7f33ce3b1e94e4325ef02829cd12297ef7188ffffffffffffffffd80180948a0a19589531694250d570040a0c4b74576919b801d8028094000000000000000000000000000000000000100080d8038094a94f5374fce5edbc8e2a8697c15331677e6ebf0b80")] @@ -143,10 +149,83 @@ public void Write_rlp_of_blocks_to_file(string rlp) File.WriteAllBytes("chains\\block1.rlp".GetApplicationResourcePath(), Bytes.FromHexString(rlp)); } + [Test] + public void Encode_with_pre_encoded_transactions_produces_same_rlp( + [ValueSource(nameof(BlockScenariosWithTxs))] Block block) + { + byte[][] encodedTxs = new byte[block.Transactions.Length][]; + for (int i = 0; i < block.Transactions.Length; i++) + { + encodedTxs[i] = Rlp.Encode(block.Transactions[i], RlpBehaviors.SkipTypedWrapping).Bytes; + } + + BlockDecoder decoder = new(); + Rlp standard = decoder.Encode(block); + + Block blockWithEncoded = new(block.Header, block.Body) { EncodedTransactions = encodedTxs }; + Rlp fast = decoder.Encode(blockWithEncoded); + + Assert.That(fast.Bytes.ToHexString(), Is.EqualTo(standard.Bytes.ToHexString())); + } + + [Test] + public void Encode_with_pre_encoded_typed_transactions_produces_same_rlp() + { + Transaction[] transactions = + [ + Build.A.Transaction.WithNonce(1).WithType(TxType.Legacy).Signed().TestObject, + Build.A.Transaction.WithNonce(2).WithType(TxType.AccessList).Signed().TestObject, + Build.A.Transaction.WithNonce(3).WithType(TxType.EIP1559).Signed().TestObject, + ]; + + Block block = Build.A.Block + .WithNumber(1) + .WithBaseFeePerGas(1) + .WithTransactions(transactions) + .WithWithdrawals(2) + .WithBlobGasUsed(0) + .WithExcessBlobGas(0) + .WithMixHash(Keccak.EmptyTreeHash) + .TestObject; + + byte[][] encodedTxs = new byte[transactions.Length][]; + for (int i = 0; i < transactions.Length; i++) + { + encodedTxs[i] = Rlp.Encode(transactions[i], RlpBehaviors.SkipTypedWrapping).Bytes; + } + + BlockDecoder decoder = new(); + Rlp standard = decoder.Encode(block); + + Block blockWithEncoded = new(block.Header, block.Body) { EncodedTransactions = encodedTxs }; + Rlp fast = decoder.Encode(blockWithEncoded); + + Assert.That(fast.Bytes.ToHexString(), Is.EqualTo(standard.Bytes.ToHexString())); + } + [Test] public void Get_length_null() { BlockDecoder decoder = new(); Assert.That(decoder.GetLength(null, RlpBehaviors.None), Is.EqualTo(1)); } + + public static byte[][] MalformedInput = + [ + [0xFF, 0xFF], // Bug #001 original. + [0xF8, 0xFF], // Long list prefix, no length. + [0xB8, 0xFF], // Long string prefix, no length. + "\0\0"u8.ToArray() // Invalid prefix. + ]; + + /// + /// Fuzz-generated edge case: Very short truncated input. + /// + [TestCaseSource(nameof(MalformedInput))] + public void Rejects_malformed_input(byte[] input) + { + BlockDecoder decoder = new(); + RlpStream rlpStream = new(input); + Assert.Throws(() => decoder.Decode(rlpStream)); + } } diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/CompactReceiptStorageDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/CompactReceiptStorageDecoderTests.cs index 6c63361f2b89..3b12e39b849e 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 c60d4e764422..e4942c21b16c 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 61ceea3d2bf4..e50991882833 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/ExpressionExtensionsTests.cs b/src/Nethermind/Nethermind.Core.Test/ExpressionExtensionsTests.cs new file mode 100644 index 000000000000..7d7a7df5baa9 --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/ExpressionExtensionsTests.cs @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Linq.Expressions; +using Nethermind.Core.Extensions; +using NUnit.Framework; + +namespace Nethermind.Core.Test; + +public class ExpressionExtensionsTests +{ + private class Dummy + { + public int ValueProperty { get; set; } + public int ValueField = 0; + } + + [Test] + public void GetSetter_should_set_property_value() + { + Expression> expr = x => x.ValueProperty; + Action setter = expr.GetSetter(); + Dummy d = new Dummy(); + + setter(d, 42); + + Assert.That(d.ValueProperty, Is.EqualTo(42)); + } + + [Test] + public void GetSetter_should_set_field_value() + { + Expression> expr = x => x.ValueField; + Action setter = expr.GetSetter(); + Dummy d = new Dummy(); + + setter(d, 7); + + Assert.That(d.ValueField, Is.EqualTo(7)); + } +} diff --git a/src/Nethermind/Nethermind.Core.Test/FixedBlockChainHeadSpecProvider.cs b/src/Nethermind/Nethermind.Core.Test/FixedBlockChainHeadSpecProvider.cs index 30b0ca5f7e6c..49b43aaf5661 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/Json/ByteArrayConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs index d1413676fa33..757d490234c2 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs @@ -8,6 +8,8 @@ using System.Text.Json; using FluentAssertions; using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; using Nethermind.Serialization.Json; using NUnit.Framework; @@ -294,6 +296,87 @@ public void Fuzz_RandomHex_SegmentationInvariant() } } + [TestCase(new byte[] { 0xab, 0xcd }, true, true, "\"0xabcd\"")] + [TestCase(new byte[] { 0xab, 0xcd }, false, true, "\"0xabcd\"")] + [TestCase(new byte[] { 0x00, 0xab }, true, true, "\"0xab\"")] + [TestCase(new byte[] { 0x00, 0xab }, false, true, "\"0x00ab\"")] + [TestCase(new byte[] { 0x00, 0x00 }, true, true, "\"0x0\"")] + [TestCase(new byte[] { 0x00, 0x00 }, false, true, "\"0x0000\"")] + [TestCase(new byte[] { 0xab }, true, false, "\"ab\"")] + [TestCase(new byte[] { 0xab }, false, false, "\"ab\"")] + [TestCase(new byte[] { 0x0a }, true, true, "\"0xa\"")] + [TestCase(new byte[] { 0x0a }, false, true, "\"0x0a\"")] + public void Write_OutputFormat(byte[] input, bool skipLeadingZeros, bool addHexPrefix, string expected) + { + using System.IO.MemoryStream ms = new(); + using Utf8JsonWriter writer = new(ms); + ByteArrayConverter.Convert(writer, input, skipLeadingZeros, addHexPrefix); + writer.Flush(); + Encoding.UTF8.GetString(ms.ToArray()).Should().Be(expected); + } + + [Test] + public void Write_LargeOutput_UsesArrayPool() + { + // 200 bytes = 400 hex chars + "0x" prefix + quotes > 256 byte InlineArray threshold + byte[] input = new byte[200]; + for (int i = 0; i < input.Length; i++) input[i] = (byte)(i & 0xFF); + + using System.IO.MemoryStream ms = new(); + using Utf8JsonWriter writer = new(ms); + ByteArrayConverter.Convert(writer, input, skipLeadingZeros: false); + writer.Flush(); + string output = Encoding.UTF8.GetString(ms.ToArray()); + output.Should().StartWith("\"0x"); + output.Should().EndWith("\""); + output.Length.Should().Be(404); // 400 hex + 2 prefix + 2 quotes + } + + [Test] + public void WriteAsPropertyName_Format() + { + ByteArrayConverter converter = new(); + using System.IO.MemoryStream ms = new(); + using Utf8JsonWriter writer = new(ms); + writer.WriteStartObject(); + converter.WriteAsPropertyName(writer, new byte[] { 0xab, 0xcd }, JsonSerializerOptions.Default); + writer.WriteNumberValue(1); + writer.WriteEndObject(); + writer.Flush(); + Encoding.UTF8.GetString(ms.ToArray()).Should().Be("{\"0xabcd\":1}"); + } + + [Test] + public void WriteAsPropertyName_AllZeros() + { + ByteArrayConverter converter = new(); + using System.IO.MemoryStream ms = new(); + using Utf8JsonWriter writer = new(ms); + writer.WriteStartObject(); + converter.WriteAsPropertyName(writer, new byte[] { 0x00, 0x00 }, JsonSerializerOptions.Default); + writer.WriteNumberValue(1); + writer.WriteEndObject(); + writer.Flush(); + // skipLeadingZeros: false preserves all zeros + Encoding.UTF8.GetString(ms.ToArray()).Should().Be("{\"0x0000\":1}"); + } + + [Test] + public void Test_DictionaryKey() + { + var random = new CryptoRandom(); + var dictionary = new Dictionary + { + { Bytes.FromHexString("0x0"), null }, + { Bytes.FromHexString("0x1"), random.NextInt(int.MaxValue) }, + { Build.An.Address.TestObject.Bytes, random.NextInt(int.MaxValue) }, + { random.GenerateRandomBytes(10), random.NextInt(int.MaxValue) }, + { random.GenerateRandomBytes(32), random.NextInt(int.MaxValue) }, + }; + + TestConverter(dictionary, new ByteArrayConverter()); + } + private static ReadOnlySequence JsonForLiteral(string literal) => MakeSequence(Encoding.UTF8.GetBytes(literal)); diff --git a/src/Nethermind/Nethermind.Core.Test/Json/ConverterTestBase.cs b/src/Nethermind/Nethermind.Core.Test/Json/ConverterTestBase.cs index 7f38126cda19..8725b3750dba 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/ConverterTestBase.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/ConverterTestBase.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections; using System.Text.Json; using System.Text.Json.Serialization; using NUnit.Framework; @@ -10,7 +11,10 @@ namespace Nethermind.Core.Test.Json; public class ConverterTestBase { - protected void TestConverter(T? item, Func equalityComparer, JsonConverter converter) + protected void TestConverter(T? item, Func equalityComparer, JsonConverter converter) => + TestConverter(item, converter, equalityComparer); + + protected void TestConverter(TItem? item, JsonConverter converter, Func? equalityComparer = null) { var options = new JsonSerializerOptions { @@ -22,10 +26,15 @@ protected void TestConverter(T? item, Func equalityComparer, JsonCon string result = JsonSerializer.Serialize(item, options); - T? deserialized = JsonSerializer.Deserialize(result, options); + TItem? deserialized = JsonSerializer.Deserialize(result, options); #pragma warning disable CS8604 - Assert.That(equalityComparer(item, deserialized), Is.True); + if (equalityComparer is not null) + Assert.That(equalityComparer(item, deserialized), Is.True); + else if (item is IEnumerable itemE && deserialized is IEnumerable deserializedE) + Assert.That(deserializedE, Is.EquivalentTo(itemE)); + else + Assert.That(deserialized, Is.EqualTo(item)); #pragma warning restore CS8604 } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs index 1e6afeffa5a5..cd6fe2466ebf 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Text.Json; using Nethermind.Core.Crypto; @@ -22,5 +23,67 @@ public void Can_read_null() Hash256? result = JsonSerializer.Deserialize("null", options); Assert.That(result, Is.EqualTo(null)); } + + [Test] + public void Writes_zero_hash() + { + Hash256 hash = new(new byte[32]); + string result = JsonSerializer.Serialize(hash, options); + Assert.That(result, Is.EqualTo("\"0x0000000000000000000000000000000000000000000000000000000000000000\"")); + } + + [Test] + public void Writes_all_ones_hash() + { + byte[] bytes = new byte[32]; + Array.Fill(bytes, (byte)0xFF); + Hash256 hash = new(bytes); + string result = JsonSerializer.Serialize(hash, options); + Assert.That(result, Is.EqualTo("\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"")); + } + + [Test] + public void Writes_known_hash() + { + // Keccak256 of empty string + Hash256 hash = Keccak.OfAnEmptyString; + string result = JsonSerializer.Serialize(hash, options); + Assert.That(result, Is.EqualTo($"\"0x{hash.ToString(false)}\"")); + } + + [Test] + public void Writes_sequential_bytes() + { + byte[] bytes = new byte[32]; + for (int i = 0; i < 32; i++) bytes[i] = (byte)i; + Hash256 hash = new(bytes); + string result = JsonSerializer.Serialize(hash, options); + Assert.That(result, Is.EqualTo("\"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\"")); + } + + [Test] + public void Writes_roundtrip() + { + Hash256 hash = Keccak.Compute("test data"u8); + string json = JsonSerializer.Serialize(hash, options); + Hash256? deserialized = JsonSerializer.Deserialize(json, options); + Assert.That(deserialized, Is.EqualTo(hash)); + } + + [Test] + public void Writes_each_nibble_value() + { + // Ensure all hex chars 0-f appear correctly + byte[] bytes = new byte[32]; + for (int i = 0; i < 16; i++) + { + bytes[i * 2] = (byte)((i << 4) | i); // 0x00, 0x11, 0x22, ..., 0xff + bytes[i * 2 + 1] = (byte)((i << 4) | (15 - i)); // 0x0f, 0x1e, 0x2d, ... + } + Hash256 hash = new(bytes); + string result = JsonSerializer.Serialize(hash, options); + Hash256? roundtrip = JsonSerializer.Deserialize(result, options); + Assert.That(roundtrip, Is.EqualTo(hash)); + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs index f4e8088d4da1..413734f8a535 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs @@ -15,6 +15,7 @@ public class LongConverterTests : ConverterTestBase static readonly LongConverter converter = new(); static readonly JsonSerializerOptions options = new JsonSerializerOptions { Converters = { converter } }; + [Test] public void Test_roundtrip() { TestConverter(int.MaxValue, static (a, b) => a.Equals(b), converter); @@ -63,5 +64,46 @@ public void Throws_on_null() Assert.Throws( static () => JsonSerializer.Deserialize("null", options)); } + + [TestCase(0L, "\"0x0\"")] + [TestCase(1L, "\"0x1\"")] + [TestCase(15L, "\"0xf\"")] + [TestCase(16L, "\"0x10\"")] + [TestCase(255L, "\"0xff\"")] + [TestCase(256L, "\"0x100\"")] + [TestCase(0xabcdefL, "\"0xabcdef\"")] + [TestCase(0x1L, "\"0x1\"")] + [TestCase(0x10L, "\"0x10\"")] + [TestCase(0x100L, "\"0x100\"")] + [TestCase(0x1000L, "\"0x1000\"")] + [TestCase(0x10000L, "\"0x10000\"")] + [TestCase(0x100000L, "\"0x100000\"")] + [TestCase(0x1000000L, "\"0x1000000\"")] + [TestCase(0x10000000L, "\"0x10000000\"")] + [TestCase(int.MaxValue, "\"0x7fffffff\"")] + [TestCase(long.MaxValue, "\"0x7fffffffffffffff\"")] + [TestCase(-1L, "\"0xffffffffffffffff\"")] + [TestCase(-9223372036854775808L, "\"0x8000000000000000\"")] // long.MinValue + public void Writes_correct_hex(long value, string expected) + { + string result = JsonSerializer.Serialize(value, options); + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void Writes_hex_roundtrip_all_nibble_counts() + { + // Test every nibble count from 1 to 16 + for (int nibbles = 1; nibbles <= 16; nibbles++) + { + long value = nibbles <= 15 + ? 1L << ((nibbles - 1) * 4) + : unchecked((long)0x8000000000000000UL); + + string json = JsonSerializer.Serialize(value, options); + long deserialized = JsonSerializer.Deserialize(json, options); + Assert.That(deserialized, Is.EqualTo(value), $"Roundtrip failed for nibbles={nibbles}, value=0x{(ulong)value:x}"); + } + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/NullableByteReadOnlyMemoryConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/NullableByteReadOnlyMemoryConverterTests.cs index c18c7d4c4509..51f4f1c67e22 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/NullableByteReadOnlyMemoryConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/NullableByteReadOnlyMemoryConverterTests.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text.Json; using Nethermind.Serialization.Json; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs index 1d1cd86a8334..fdcd1e24d186 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs @@ -72,5 +72,32 @@ public void Throws_on_negative_numbers() Assert.Throws( static () => JsonSerializer.Deserialize("-1", options)); } + + [TestCase(0UL, "\"0x0\"")] + [TestCase(1UL, "\"0x1\"")] + [TestCase(15UL, "\"0xf\"")] + [TestCase(16UL, "\"0x10\"")] + [TestCase(255UL, "\"0xff\"")] + [TestCase(0xabcdefUL, "\"0xabcdef\"")] + [TestCase(0xffffffffUL, "\"0xffffffff\"")] + [TestCase(0x100000000UL, "\"0x100000000\"")] + [TestCase(ulong.MaxValue, "\"0xffffffffffffffff\"")] + public void Writes_correct_hex(ulong value, string expected) + { + string result = JsonSerializer.Serialize((ulong?)value, options); + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void Writes_hex_roundtrip_all_nibble_counts() + { + for (int nibbles = 1; nibbles <= 16; nibbles++) + { + ulong value = 1UL << ((nibbles - 1) * 4); + string json = JsonSerializer.Serialize((ulong?)value, options); + ulong? deserialized = JsonSerializer.Deserialize(json, options); + Assert.That(deserialized, Is.EqualTo(value), $"Roundtrip failed for nibbles={nibbles}, value=0x{value:x}"); + } + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs index 9bbe9f7e0e0b..25f4e9737c29 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs @@ -152,5 +152,140 @@ public void Throws_on_null() Assert.Throws( static () => JsonSerializer.Deserialize("null", options)); } + + [TestCase(0ul, 0ul, 0ul, 0ul, "\"0x0\"")] + [TestCase(1ul, 0ul, 0ul, 0ul, "\"0x1\"")] + [TestCase(0xful, 0ul, 0ul, 0ul, "\"0xf\"")] + [TestCase(0xfful, 0ul, 0ul, 0ul, "\"0xff\"")] + [TestCase(0xabcdeful, 0ul, 0ul, 0ul, "\"0xabcdef\"")] + [TestCase(ulong.MaxValue, 0ul, 0ul, 0ul, "\"0xffffffffffffffff\"")] + [TestCase(ulong.MaxValue, 1ul, 0ul, 0ul, "\"0x1ffffffffffffffff\"")] + [TestCase(0ul, 0ul, 1ul, 0ul, "\"0x100000000000000000000000000000000\"")] + [TestCase(0ul, 0ul, 0ul, 1ul, "\"0x1000000000000000000000000000000000000000000000000\"")] + [TestCase(ulong.MaxValue, ulong.MaxValue, ulong.MaxValue, ulong.MaxValue, "\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"")] + public void Writes_hex(ulong u0, ulong u1, ulong u2, ulong u3, string expected) + { + UInt256 value = new(u0, u1, u2, u3); + string result = JsonSerializer.Serialize(value, options); + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void Writes_hex_roundtrip_all_limb_boundaries() + { + // Test values at each limb boundary + UInt256[] values = + [ + UInt256.One, + new UInt256(ulong.MaxValue), + new UInt256(0, 1), + new UInt256(ulong.MaxValue, ulong.MaxValue), + new UInt256(0, 0, 1, 0), + new UInt256(ulong.MaxValue, ulong.MaxValue, ulong.MaxValue, 0), + new UInt256(0, 0, 0, 1), + UInt256.MaxValue, + ]; + + foreach (UInt256 value in values) + { + string json = JsonSerializer.Serialize(value, options); + UInt256 deserialized = JsonSerializer.Deserialize(json, options); + Assert.That(deserialized, Is.EqualTo(value), $"Roundtrip failed for {value}"); + } + } + + [Test] + public void Writes_zero_padded_hex() + { + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.ZeroPaddedHex; + try + { + string result = JsonSerializer.Serialize(UInt256.One, options); + Assert.That(result, Is.EqualTo("\"0x0000000000000000000000000000000000000000000000000000000000000001\"")); + + result = JsonSerializer.Serialize(UInt256.MaxValue, options); + Assert.That(result, Is.EqualTo("\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"")); + + result = JsonSerializer.Serialize(UInt256.Zero, options); + Assert.That(result, Is.EqualTo("\"0x0000000000000000000000000000000000000000000000000000000000000000\"")); + } + finally + { + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.Hex; + } + } + + [Test] + public void Writes_zero_padded_hex_roundtrip() + { + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.ZeroPaddedHex; + try + { + UInt256 value = new(0xdeadbeef, 0xcafebabe, 0x12345678, 0x9abcdef0); + string json = JsonSerializer.Serialize(value, options); + UInt256 deserialized = JsonSerializer.Deserialize(json, options); + Assert.That(deserialized, Is.EqualTo(value)); + } + finally + { + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.Hex; + } + } + + [Test] + public void Writes_property_name_hex() + { + using var stream = new System.IO.MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + + writer.WriteStartObject(); + converter.WriteAsPropertyName(writer, (UInt256)0xabcdef, options); + writer.WriteNumberValue(1); + writer.WriteEndObject(); + writer.Flush(); + + string result = System.Text.Encoding.UTF8.GetString(stream.ToArray()); + Assert.That(result, Is.EqualTo("{\"0xabcdef\":1}")); + } + + [Test] + public void Writes_property_name_zero() + { + using var stream = new System.IO.MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + + writer.WriteStartObject(); + converter.WriteAsPropertyName(writer, UInt256.Zero, options); + writer.WriteNumberValue(1); + writer.WriteEndObject(); + writer.Flush(); + + string result = System.Text.Encoding.UTF8.GetString(stream.ToArray()); + Assert.That(result, Is.EqualTo("{\"0x0\":1}")); + } + + [Test] + public void Writes_property_name_zero_padded_hex() + { + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.ZeroPaddedHex; + try + { + using var stream = new System.IO.MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + + writer.WriteStartObject(); + converter.WriteAsPropertyName(writer, UInt256.One, options); + writer.WriteNumberValue(1); + writer.WriteEndObject(); + writer.Flush(); + + string result = System.Text.Encoding.UTF8.GetString(stream.ToArray()); + Assert.That(result, Is.EqualTo("{\"0x0000000000000000000000000000000000000000000000000000000000000001\":1}")); + } + finally + { + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.Hex; + } + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/KeccakCacheTests.cs b/src/Nethermind/Nethermind.Core.Test/KeccakCacheTests.cs index f26d7820ac83..f6c96fb979c8 100644 --- a/src/Nethermind/Nethermind.Core.Test/KeccakCacheTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/KeccakCacheTests.cs @@ -117,5 +117,82 @@ public void Spin_through_all() KeccakCache.Compute(span).Should().Be(ValueKeccak.Compute(span)); } } + + [Test] + public void Hash256_32_byte_path() + { + // Tests the optimized 32-byte path (most common - Hash256/UInt256) + var random = new Random(42); + for (int i = 0; i < 1000; i++) + { + var bytes = new byte[32]; + random.NextBytes(bytes); + + ValueHash256 expected = ValueKeccak.Compute(bytes); + ValueHash256 actual = KeccakCache.Compute(bytes); + actual.Should().Be(expected); + + // Second call should hit cache + KeccakCache.Compute(bytes).Should().Be(expected); + } + } + + [Test] + public void Address_20_byte_path() + { + // Tests the optimized 20-byte path (Address) + var random = new Random(42); + for (int i = 0; i < 1000; i++) + { + var bytes = new byte[20]; + random.NextBytes(bytes); + + ValueHash256 expected = ValueKeccak.Compute(bytes); + ValueHash256 actual = KeccakCache.Compute(bytes); + actual.Should().Be(expected); + + // Second call should hit cache + KeccakCache.Compute(bytes).Should().Be(expected); + } + } + + [Test] + public void Concurrent_read_write_stress() + { + // Stress test the seqlock pattern with concurrent readers and writers + const int iterations = 100_000; + var bytes = new byte[32]; + new Random(123).NextBytes(bytes); + + // Prime the cache + KeccakCache.Compute(bytes); + + // Create different keys that hash to the same bucket (will cause cache eviction) + var collisions = new byte[4][]; + collisions[0] = bytes; + var random = new Random(456); + var bucket = KeccakCache.GetBucket(bytes); + int found = 1; + while (found < 4) + { + var candidate = new byte[32]; + random.NextBytes(candidate); + if (KeccakCache.GetBucket(candidate) == bucket) + { + collisions[found++] = candidate; + } + } + + var expectedValues = collisions.Select(c => ValueKeccak.Compute(c)).ToArray(); + + // Parallel readers and writers hammering the same bucket + Parallel.For(0, iterations, i => + { + int idx = i % 4; + var input = collisions[idx]; + var result = KeccakCache.Compute(input); + result.Should().Be(expectedValues[idx], $"iteration {i}, index {idx}"); + }); + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/KeccakTests.cs b/src/Nethermind/Nethermind.Core.Test/KeccakTests.cs index f1437d98f85a..1495f4dd26a2 100644 --- a/src/Nethermind/Nethermind.Core.Test/KeccakTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/KeccakTests.cs @@ -136,6 +136,28 @@ public void Sanity_checks(string hexString, string expected) stream.GetHash().Bytes.ToHexString().Should().Be(expected); } + [TestCase("0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001", Description = "Normal increment")] + [TestCase("0x00000000000000000000000000000000000000000000000000000000000000ff", "0x0000000000000000000000000000000000000000000000000000000000000100", Description = "Byte boundary carry")] + [TestCase("0x000000000000000000000000000000000000000000000000000000000000ffff", "0x0000000000000000000000000000000000000000000000000000000000010000", Description = "Multiple byte carry")] + [TestCase("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", Description = "Overflow returns max")] + public void IncrementPath_ReturnsExpected(string inputHex, string expectedHex) + { + ValueHash256 path = new(inputHex); + ValueHash256 result = path.IncrementPath(); + Assert.That(result, Is.EqualTo(new ValueHash256(expectedHex))); + } + + [TestCase("0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", Description = "Normal decrement")] + [TestCase("0x0000000000000000000000000000000000000000000000000000000000000100", "0x00000000000000000000000000000000000000000000000000000000000000ff", Description = "Byte boundary borrow")] + [TestCase("0x0000000000000000000000000000000000000000000000000000000000010000", "0x000000000000000000000000000000000000000000000000000000000000ffff", Description = "Multiple byte borrow")] + [TestCase("0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", Description = "Underflow returns zero")] + public void DecrementPath_ReturnsExpected(string inputHex, string expectedHex) + { + ValueHash256 path = new(inputHex); + ValueHash256 result = path.DecrementPath(); + Assert.That(result, Is.EqualTo(new ValueHash256(expectedHex))); + } + public static string[][] KeccakCases = { ["0x4","f343681465b9efe82c933c3e8748c70cb8aa06539c361de20f72eac04e766393"], diff --git a/src/Nethermind/Nethermind.Core.Test/MCSLockTests.cs b/src/Nethermind/Nethermind.Core.Test/MCSLockTests.cs index 0dbf3e1ea56e..16d4fac388eb 100644 --- a/src/Nethermind/Nethermind.Core.Test/MCSLockTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/MCSLockTests.cs @@ -59,6 +59,7 @@ public void MultipleThreads() } [Test] + [Retry(3)] public void LockFairnessTest() { int numberOfThreads = 10; diff --git a/src/Nethermind/Nethermind.Core.Test/MockSnapSyncPeer.cs b/src/Nethermind/Nethermind.Core.Test/MockSnapSyncPeer.cs index 831c8998980b..4dd2b596b808 100644 --- a/src/Nethermind/Nethermind.Core.Test/MockSnapSyncPeer.cs +++ b/src/Nethermind/Nethermind.Core.Test/MockSnapSyncPeer.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Nethermind.Blockchain.Synchronization; diff --git a/src/Nethermind/Nethermind.Core.Test/Modules/LocalChannelFactory.cs b/src/Nethermind/Nethermind.Core.Test/Modules/LocalChannelFactory.cs index b25817faa582..f931caedcc0f 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/PseudoNethermindRunner.cs b/src/Nethermind/Nethermind.Core.Test/Modules/PseudoNethermindRunner.cs index c3673880a4b4..6f74bb97939e 100644 --- a/src/Nethermind/Nethermind.Core.Test/Modules/PseudoNethermindRunner.cs +++ b/src/Nethermind/Nethermind.Core.Test/Modules/PseudoNethermindRunner.cs @@ -5,12 +5,9 @@ using System.Threading; using System.Threading.Tasks; using Autofac; -using Nethermind.Api; using Nethermind.Blockchain; using Nethermind.Consensus; using Nethermind.Consensus.Processing; -using Nethermind.Core.Events; -using Nethermind.Evm.State; using Nethermind.Network; using Nethermind.Network.Rlpx; using Nethermind.Synchronization; diff --git a/src/Nethermind/Nethermind.Core.Test/Modules/PseudoNetworkModule.cs b/src/Nethermind/Nethermind.Core.Test/Modules/PseudoNetworkModule.cs index f8dc845cba1a..a32fb6548490 100644 --- a/src/Nethermind/Nethermind.Core.Test/Modules/PseudoNetworkModule.cs +++ b/src/Nethermind/Nethermind.Core.Test/Modules/PseudoNetworkModule.cs @@ -2,14 +2,12 @@ // SPDX-License-Identifier: LGPL-3.0-only using Autofac; -using Autofac.Features.AttributeFilters; using Nethermind.Blockchain.Synchronization; using Nethermind.Consensus; using Nethermind.Logging; using Nethermind.Network; using Nethermind.Network.Config; using Nethermind.Network.Contract.P2P; -using Nethermind.Network.P2P.Subprotocols.Eth; using Nethermind.State; using Nethermind.State.SnapServer; using Nethermind.Stats.Model; @@ -31,7 +29,6 @@ protected override void Load(ContainerBuilder builder) .AddSingleton(NoPoS.Instance) .AddSingleton() - .AddSingleton() .AddSingleton(Policy.FullGossip) .AddComposite() @@ -42,7 +39,6 @@ protected override void Load(ContainerBuilder builder) { cfg .As() - .WithAttributeFiltering() .SingleInstance() .OnActivating((m) => { diff --git a/src/Nethermind/Nethermind.Core.Test/Modules/TestBlockProcessingModule.cs b/src/Nethermind/Nethermind.Core.Test/Modules/TestBlockProcessingModule.cs index 06098ae038cc..ada4a68da995 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 b9cd9fca88af..eb46500469bb 100644 --- a/src/Nethermind/Nethermind.Core.Test/Modules/TestEnvironmentModule.cs +++ b/src/Nethermind/Nethermind.Core.Test/Modules/TestEnvironmentModule.cs @@ -15,11 +15,9 @@ using Nethermind.Crypto; using Nethermind.Db; using Nethermind.Db.Blooms; -using Nethermind.Evm; using Nethermind.Logging; using Nethermind.Network; using Nethermind.Network.Config; -using Nethermind.Evm.State; using Nethermind.State; using Nethermind.Synchronization; using Nethermind.Synchronization.Test; @@ -42,9 +40,10 @@ 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()) + .AddKeyedSingleton(DbNames.DiscoveryV5Nodes, (_) => new MemDb()) .AddSingleton(networkConfig => new LocalChannelFactory(networkGroup ?? nameof(TestEnvironmentModule), networkConfig)) .AddSingleton() diff --git a/src/Nethermind/Nethermind.Core.Test/Modules/TestMergeModule.cs b/src/Nethermind/Nethermind.Core.Test/Modules/TestMergeModule.cs index 18347180f0fa..2a930771c265 100644 --- a/src/Nethermind/Nethermind.Core.Test/Modules/TestMergeModule.cs +++ b/src/Nethermind/Nethermind.Core.Test/Modules/TestMergeModule.cs @@ -11,7 +11,6 @@ using Nethermind.Consensus.Rewards; using Nethermind.Merge.Plugin; using Nethermind.Merge.Plugin.BlockProduction; -using Nethermind.Merge.Plugin.InvalidChainTracker; using Nethermind.TxPool; namespace Nethermind.Core.Test.Modules; @@ -43,6 +42,9 @@ protected override void Load(ContainerBuilder builder) .AddDecorator() .AddScoped() .AddDecorator() + + // Engine rpc + .AddSingleton() ; if (txPoolConfig.BlobsSupport.SupportsReorgs()) diff --git a/src/Nethermind/Nethermind.Core.Test/Modules/TestNethermindModule.cs b/src/Nethermind/Nethermind.Core.Test/Modules/TestNethermindModule.cs index 495c17095753..2bd914a09d30 100644 --- a/src/Nethermind/Nethermind.Core.Test/Modules/TestNethermindModule.cs +++ b/src/Nethermind/Nethermind.Core.Test/Modules/TestNethermindModule.cs @@ -8,6 +8,7 @@ using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Logging; +using Nethermind.Serialization.Json; using Nethermind.Specs; using Nethermind.Specs.ChainSpecStyle; using Nethermind.Specs.Forks; @@ -19,7 +20,7 @@ namespace Nethermind.Core.Test.Modules; /// component later anyway. /// /// -public class TestNethermindModule(IConfigProvider configProvider, ChainSpec chainSpec) : Module +public class TestNethermindModule(IConfigProvider configProvider, ChainSpec chainSpec, bool useTestSpecProvider = true) : Module { private readonly IReleaseSpec? _releaseSpec; @@ -47,6 +48,13 @@ public TestNethermindModule(ChainSpec chainSpec) : this(new ConfigProvider(), ch { } + public static TestNethermindModule CreateWithRealChainSpec() + { + var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance); + ChainSpec spec = loader.LoadEmbeddedOrFromFile("chainspec/foundation.json"); + return new TestNethermindModule(new ConfigProvider(), spec, useTestSpecProvider: false); + } + protected override void Load(ContainerBuilder builder) { base.Load(builder); @@ -55,7 +63,9 @@ protected override void Load(ContainerBuilder builder) builder .AddModule(new PseudoNethermindModule(chainSpec, configProvider, LimboLogs.Instance)) - .AddModule(new TestEnvironmentModule(TestItem.PrivateKeyA, Random.Shared.Next().ToString())) - .AddSingleton(_ => new TestSpecProvider(_releaseSpec ?? Osaka.Instance)); + .AddModule(new TestEnvironmentModule(TestItem.PrivateKeyA, Random.Shared.Next().ToString())); + + if (useTestSpecProvider) + builder.AddSingleton(_ => new TestSpecProvider(_releaseSpec ?? Osaka.Instance)); } } diff --git a/src/Nethermind/Nethermind.Core.Test/NSubstituteExtensions.cs b/src/Nethermind/Nethermind.Core.Test/NSubstituteExtensions.cs new file mode 100644 index 000000000000..854e6cf72258 --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/NSubstituteExtensions.cs @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using NSubstitute.Core; + +namespace Nethermind.Core.Test; + +public static class NSubstituteExtensions +{ + /// + /// Checks if a substitute received matching calls without throwing exceptions. + /// Suitable for polling scenarios with Is.True.After(). + /// + public static bool ReceivedCallsMatching( + this T substitute, + Action action, + int requiredNumberOfCalls = 1, + int? maxNumberOfCalls = null, + [CallerArgumentExpression(nameof(action))] string? expression = null) where T : class + { + if (maxNumberOfCalls < requiredNumberOfCalls) throw new ArgumentException($"{nameof(maxNumberOfCalls)} must be greater than or equal to {nameof(requiredNumberOfCalls)}", nameof(maxNumberOfCalls)); + maxNumberOfCalls ??= requiredNumberOfCalls; + ISubstitutionContext context = SubstitutionContext.Current; + ICallRouter callRouter = context.GetCallRouterFor(substitute); + + // Set up the call specification by invoking the action + action(substitute); + + // Get the pending specification that was just set up + IPendingSpecification pendingSpec = context.ThreadContext.PendingSpecification; + if (!pendingSpec.HasPendingCallSpecInfo()) return false; + + // Use a query to check if the call was received + PendingSpecificationInfo? callSpecInfo = context.ThreadContext.PendingSpecification.UseCallSpecInfo(); + int? matchCount = callSpecInfo?.Handle( + // Lambda 1: Handle call specification with Arg matchers + callSpec => callRouter.ReceivedCalls().Where(callSpec.IsSatisfiedBy).Count(), + // Lambda 2: Handle matching with concrete argument values + GetMatchCount); + + return matchCount.HasValue && CheckMatchCount(matchCount.Value); + + bool CheckMatchCount(int count) => count >= requiredNumberOfCalls && count <= maxNumberOfCalls; + + int GetMatchCount(ICall expectedCall) + { + IEnumerable receivedCalls = callRouter.ReceivedCalls(); + MethodInfo expectedMethod = expectedCall.GetMethodInfo(); + object?[] expectedArgs = expectedCall.GetArguments(); + + int matchCount = 0; + foreach (ICall call in receivedCalls) + { + // Match method name and arguments + if (call.GetMethodInfo() == expectedMethod) + { + object?[] callArgs = call.GetArguments(); + matchCount += expectedArgs.SequenceEqual(callArgs) ? 1 : 0; + } + } + + return matchCount; + } + } +} diff --git a/src/Nethermind/Nethermind.Core.Test/RipemdTests.cs b/src/Nethermind/Nethermind.Core.Test/RipemdTests.cs index 88403a11e132..b7c4e8115218 100644 --- a/src/Nethermind/Nethermind.Core.Test/RipemdTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/RipemdTests.cs @@ -9,7 +9,7 @@ namespace Nethermind.Core.Test [TestFixture] public class RipemdTests { - public const string RipemdOfEmptyString = "9c1185a5c5e9fc54612808977ee8f548b2258d31"; + public const string RipemdOfEmptyString = "0000000000000000000000009c1185a5c5e9fc54612808977ee8f548b2258d31"; [Test] public void Empty_byte_array() diff --git a/src/Nethermind/Nethermind.Core.Test/RlpTests.cs b/src/Nethermind/Nethermind.Core.Test/RlpTests.cs index b45b0c29fdfd..01320fdf3d38 100644 --- a/src/Nethermind/Nethermind.Core.Test/RlpTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/RlpTests.cs @@ -246,6 +246,16 @@ public void Long_and_big_integer_encoded_the_same(long value) Assert.That(rlpBigInt.Bytes, Is.EqualTo(rlpLong.Bytes)); } + [Test] + public void Encode_generic_with_Rlp_input_preserves_original_bytes() + { + Rlp original = Rlp.Encode(255L); + Rlp reEncoded = Rlp.Encode(original); + + Assert.That(reEncoded.Bytes, Is.EqualTo(original.Bytes)); + Assert.That(reEncoded, Is.SameAs(original)); + } + [TestCase(true)] [TestCase(false)] public void RlpContextWithSliceMemory_shouldNotCopyUnderlyingData(bool sliceValue) @@ -272,5 +282,41 @@ public void RlpContextWithSliceMemory_shouldNotCopyUnderlyingData(bool sliceValu isACopy.Should().NotBe(sliceValue); } } + + [TestCase(50)] + [TestCase(100)] + public void Over_limit_throws(int limit) + { + RlpStream stream = Prepare100BytesStream(); + RlpLimit rlpLimit = new(limit); + if (limit < 100) + { + Assert.Throws(() => stream.DecodeByteArray(rlpLimit)); + } + else + { + Assert.DoesNotThrow(() => stream.DecodeByteArray(rlpLimit)); + } + } + + [Test] + public void Not_enough_bytes_throws() + { + RlpStream stream = Prepare100BytesStream(); + stream.Data[1] = 101; // tamper with length, it is more than available bytes + Assert.Throws(() => stream.DecodeByteArray()); + } + + private static RlpStream Prepare100BytesStream() + { + byte[] randomBytes = new byte[100]; + Random.Shared.NextBytes(randomBytes); + + int requiredLength = Rlp.LengthOf(randomBytes); + RlpStream stream = new(requiredLength); + stream.Encode(randomBytes); + stream.Reset(); + return stream; + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/RunImmediatelyScheduler.cs b/src/Nethermind/Nethermind.Core.Test/RunImmediatelyScheduler.cs index 1056e8dbd396..cb81087435dc 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/TestBlockhashProvider.cs b/src/Nethermind/Nethermind.Core.Test/TestBlockhashProvider.cs index b7686edfc90b..7e7cef05157e 100644 --- a/src/Nethermind/Nethermind.Core.Test/TestBlockhashProvider.cs +++ b/src/Nethermind/Nethermind.Core.Test/TestBlockhashProvider.cs @@ -1,27 +1,26 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Threading; +using System.Threading.Tasks; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Evm; namespace Nethermind.Core.Test { - public class TestBlockhashProvider : IBlockhashProvider + public class TestBlockhashProvider(ISpecProvider specProvider) : IBlockhashProvider { - private readonly ISpecProvider _specProvider; - public TestBlockhashProvider(ISpecProvider specProvider) - { - _specProvider = specProvider; - } public Hash256 GetBlockhash(BlockHeader currentBlock, long number) - => GetBlockhash(currentBlock, number, _specProvider.GetSpec(currentBlock)); + => GetBlockhash(currentBlock, number, specProvider.GetSpec(currentBlock)); - public Hash256 GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec? spec) + public Hash256 GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec spec) { - return Keccak.Compute(spec!.IsBlockHashInStateAvailable + return Keccak.Compute(spec.IsBlockHashInStateAvailable ? (Eip2935Constants.RingBufferSize + number).ToString() - : (number).ToString()); + : number.ToString()); } + + public Task Prefetch(BlockHeader currentBlock, CancellationToken token) => Task.CompletedTask; } } diff --git a/src/Nethermind/Nethermind.Core.Test/TestFinalizedStateProvider.cs b/src/Nethermind/Nethermind.Core.Test/TestFinalizedStateProvider.cs new file mode 100644 index 000000000000..ecb60c3a560b --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/TestFinalizedStateProvider.cs @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; +using Nethermind.Trie.Pruning; + +namespace Nethermind.Core.Test; + +/// +/// Fake that simulate previous behaviour where it just check the +/// LatestCommittedBlockNumber minute depth. Not for prod use. +/// TrieStore must be set later. +/// +/// +public class TestFinalizedStateProvider(long depth) : IFinalizedStateProvider +{ + public TrieStore TrieStore { get; set; } = null!; + private BlockHeader? _manualFinalizedPoint = null; + + public long FinalizedBlockNumber + { + get + { + if (_manualFinalizedPoint is not null) + { + return _manualFinalizedPoint.Number; + } + return TrieStore.LatestCommittedBlockNumber - depth; + } + } + + public Hash256? GetFinalizedStateRootAt(long blockNumber) + { + if (_manualFinalizedPoint is not null && _manualFinalizedPoint.Number == blockNumber) + { + return _manualFinalizedPoint.StateRoot; + } + using var commitSets = TrieStore.CommitSetQueue.GetCommitSetsAtBlockNumber(blockNumber); + if (commitSets.Count != 1) return null; + return commitSets[0].StateRoot; + } + + public void SetFinalizedPoint(BlockHeader baseBlock) + { + _manualFinalizedPoint = baseBlock; + } +} diff --git a/src/Nethermind/Nethermind.Core.Test/TestHardwareInfo.cs b/src/Nethermind/Nethermind.Core.Test/TestHardwareInfo.cs index 59e15d1f33a3..63947aa97c0e 100644 --- a/src/Nethermind/Nethermind.Core.Test/TestHardwareInfo.cs +++ b/src/Nethermind/Nethermind.Core.Test/TestHardwareInfo.cs @@ -3,7 +3,8 @@ namespace Nethermind.Core.Test; -public class TestHardwareInfo(long availableMemory = 10000000) : IHardwareInfo +public class TestHardwareInfo(long availableMemory = 10000000, int? maxOpenFilesLimit = null) : IHardwareInfo { public long AvailableMemoryBytes => availableMemory; + public int? MaxOpenFilesLimit => maxOpenFilesLimit; } diff --git a/src/Nethermind/Nethermind.Core.Test/TestMemColumnDb.cs b/src/Nethermind/Nethermind.Core.Test/TestMemColumnDb.cs index 529df1fdfc16..e1df1c913bb0 100644 --- a/src/Nethermind/Nethermind.Core.Test/TestMemColumnDb.cs +++ b/src/Nethermind/Nethermind.Core.Test/TestMemColumnDb.cs @@ -1,15 +1,16 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; using Nethermind.Db; namespace Nethermind.Core.Test; public class TestMemColumnsDb : IColumnsDb - where TKey : notnull + where TKey : struct, Enum { - private readonly IDictionary _columnDbs = new Dictionary(); + private readonly IDictionary _columnDbs = new Dictionary(); public TestMemColumnsDb() { @@ -17,7 +18,7 @@ public TestMemColumnsDb() public TestMemColumnsDb(params TKey[] keys) { - foreach (var key in keys) + foreach (TKey key in keys) { GetColumnDb(key); } @@ -28,8 +29,31 @@ public TestMemColumnsDb(params TKey[] keys) public IColumnsWriteBatch StartWriteBatch() { + EnsureAllKey(); return new InMemoryColumnWriteBatch(this); } + + public IColumnDbSnapshot CreateSnapshot() + { + EnsureAllKey(); + return new Snapshot(_columnDbs); + } + public void Dispose() { } public void Flush(bool onlyWal = false) { } + + private void EnsureAllKey() + { + foreach (TKey key in Enum.GetValues()) + { + GetColumnDb(key); + } + } + + private class Snapshot(IDictionary columns) : IColumnDbSnapshot + { + public IReadOnlyKeyValueStore GetColumn(TKey key) => columns[key]; + + public void Dispose() { } + } } diff --git a/src/Nethermind/Nethermind.Core.Test/TestMemDb.cs b/src/Nethermind/Nethermind.Core.Test/TestMemDb.cs index 169a166c58c7..8f78ab3f8115 100644 --- a/src/Nethermind/Nethermind.Core.Test/TestMemDb.cs +++ b/src/Nethermind/Nethermind.Core.Test/TestMemDb.cs @@ -6,6 +6,8 @@ using System.Linq; using System.Runtime.CompilerServices; using FluentAssertions; +using Nethermind.Core.Collections; +using Nethermind.Core.Extensions; using Nethermind.Db; using Bytes = Nethermind.Core.Extensions.Bytes; @@ -14,7 +16,7 @@ namespace Nethermind.Core.Test; /// /// MemDB with additional tools for testing purposes since you can't use NSubstitute with refstruct /// -public class TestMemDb : MemDb, ITunableDb +public class TestMemDb : MemDb, ITunableDb, ISortedKeyValueStore { private readonly List<(byte[], ReadFlags)> _readKeys = new(); private readonly List<((byte[], byte[]?), WriteFlags)> _writes = new(); @@ -23,18 +25,15 @@ public class TestMemDb : MemDb, ITunableDb public Func? ReadFunc { get; set; } public Func? WriteFunc { get; set; } - public Action? RemoveFunc { get; set; } public bool WasFlushed => FlushCount > 0; - public int FlushCount { get; set; } = 0; + public int FlushCount { get; private set; } [MethodImpl(MethodImplOptions.Synchronized)] public override byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) { _readKeys.Add((key.ToArray(), flags)); - - if (ReadFunc is not null) return ReadFunc(key.ToArray()); - return base.Get(key, flags); + return ReadFunc is not null ? ReadFunc(key.ToArray()) : base.Get(key, flags); } [MethodImpl(MethodImplOptions.Synchronized)] @@ -46,71 +45,127 @@ public override void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags base.Set(key, value, flags); } - public override Span GetSpan(ReadOnlySpan key) - { - return Get(key); - } - [MethodImpl(MethodImplOptions.Synchronized)] public override void Remove(ReadOnlySpan key) { _removedKeys.Add(key.ToArray()); - - if (RemoveFunc is not null) - { - RemoveFunc.Invoke(key.ToArray()); - return; - } base.Remove(key); } - public void Tune(ITunableDb.TuneType type) - { - _tuneTypes.Add(type); - } - - public bool WasTunedWith(ITunableDb.TuneType type) - { - return _tuneTypes.Contains(type); - } + public void Tune(ITunableDb.TuneType type) => _tuneTypes.Add(type); + public bool WasTunedWith(ITunableDb.TuneType type) => _tuneTypes.Contains(type); - public void KeyWasRead(byte[] key, int times = 1) - { + public void KeyWasRead(byte[] key, int times = 1) => _readKeys.Count(it => Bytes.AreEqual(it.Item1, key)).Should().Be(times); - } - public void KeyWasReadWithFlags(byte[] key, ReadFlags flags, int times = 1) - { + public void KeyWasReadWithFlags(byte[] key, ReadFlags flags, int times = 1) => _readKeys.Count(it => Bytes.AreEqual(it.Item1, key) && it.Item2 == flags).Should().Be(times); - } - public void KeyWasWritten(byte[] key, int times = 1) - { + public void KeyWasWritten(byte[] key, int times = 1) => _writes.Count(it => Bytes.AreEqual(it.Item1.Item1, key)).Should().Be(times); - } - public void KeyWasWritten(Func<(byte[], byte[]?), bool> cond, int times = 1) - { + public void KeyWasWritten(Func<(byte[], byte[]?), bool> cond, int times = 1) => _writes.Count(it => cond.Invoke(it.Item1)).Should().Be(times); - } - public void KeyWasWrittenWithFlags(byte[] key, WriteFlags flags, int times = 1) - { + public void KeyWasWrittenWithFlags(byte[] key, WriteFlags flags, int times = 1) => _writes.Count(it => Bytes.AreEqual(it.Item1.Item1, key) && it.Item2 == flags).Should().Be(times); - } - public void KeyWasRemoved(Func cond, int times = 1) + public void KeyWasRemoved(Func cond, int times = 1) => _removedKeys.Count(cond).Should().Be(times); + public override IWriteBatch StartWriteBatch() => new InMemoryWriteBatch(this); + public override void Flush(bool onlyWal) => FlushCount++; + + public byte[]? FirstKey { - _removedKeys.Count(cond).Should().Be(times); + get + { + byte[]? min = null; + foreach (byte[] key in Keys) + { + if (min is null || Bytes.BytesComparer.Compare(key, min) < 0) + { + min = key; + } + } + + return min; + } } - public override IWriteBatch StartWriteBatch() + public byte[]? LastKey { - return new InMemoryWriteBatch(this); + get + { + byte[]? max = null; + foreach (byte[] key in Keys) + { + if (max is null || Bytes.BytesComparer.Compare(key, max) > 0) + { + max = key; + } + } + + return max; + } + } + public ISortedView GetViewBetween(ReadOnlySpan firstKeyInclusive, ReadOnlySpan lastKeyExclusive) + { + ArrayPoolList<(byte[], byte[]?)> sortedValue = new(1); + + foreach (KeyValuePair keyValuePair in GetAll()) + { + if (Bytes.BytesComparer.Compare(keyValuePair.Key, firstKeyInclusive) < 0) + { + continue; + } + + if (Bytes.BytesComparer.Compare(keyValuePair.Key, lastKeyExclusive) >= 0) + { + continue; + } + sortedValue.Add((keyValuePair.Key, keyValuePair.Value)); + } + + sortedValue.AsSpan().Sort((it1, it2) => Bytes.BytesComparer.Compare(it1.Item1, it2.Item1)); + return new FakeSortedView(sortedValue); } - public override void Flush(bool onlyWal) + private class FakeSortedView(ArrayPoolList<(byte[], byte[]?)> list) : ISortedView { - FlushCount++; + private int idx = -1; + + public void Dispose() + { + list.Dispose(); + } + + public bool StartBefore(ReadOnlySpan value) + { + if (list.Count == 0) return false; + + idx = 0; + while (idx < list.Count) + { + if (Bytes.BytesComparer.Compare(list[idx].Item1, value) >= 0) + { + idx--; + return true; + } + idx++; + } + + // All keys are less than value - position at last element (largest key <= value) + idx = list.Count - 1; + return true; + } + + public bool MoveNext() + { + idx++; + if (idx >= list.Count) return false; + return true; + } + + public ReadOnlySpan CurrentKey => list[idx].Item1; + public ReadOnlySpan CurrentValue => list[idx].Item2; } } diff --git a/src/Nethermind/Nethermind.Core.Test/TestRawTrieStore.cs b/src/Nethermind/Nethermind.Core.Test/TestRawTrieStore.cs index 850237ce9326..67e44e3e28f1 100644 --- a/src/Nethermind/Nethermind.Core.Test/TestRawTrieStore.cs +++ b/src/Nethermind/Nethermind.Core.Test/TestRawTrieStore.cs @@ -17,72 +17,26 @@ namespace Nethermind.Core.Test; /// /// /// -public class TestRawTrieStore(INodeStorage nodeStorage, bool isReadOnly = false) : IPruningTrieStore, IReadOnlyTrieStore +public class TestRawTrieStore(INodeStorage nodeStorage, bool isReadOnly = false) : RawTrieStore(nodeStorage), IPruningTrieStore { public TestRawTrieStore(IKeyValueStoreWithBatching kv) : this(new NodeStorage(kv)) { } - void IDisposable.Dispose() - { - } - - public ICommitter BeginCommit(Hash256? address, TrieNode? root, WriteFlags writeFlags) - { - if (isReadOnly) return NullCommitter.Instance; - return new RawScopedTrieStore.Committer(nodeStorage, address, writeFlags); - } - - public TrieNode FindCachedOrUnknown(Hash256? address, in TreePath path, Hash256 hash) - { - return new TrieNode(NodeType.Unknown, hash); - } - - public byte[]? LoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags) - { - byte[]? ret = nodeStorage.Get(address, path, hash, flags); - if (ret is null) throw new MissingTrieNodeException("Node missing", address, path, hash); - return ret; - } - - public byte[]? TryLoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags) - { - return nodeStorage.Get(address, path, hash, flags); - } - - public bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 keccak) - { - return nodeStorage.KeyExists(address, path, keccak); - } - - public INodeStorage.KeyScheme Scheme { get; } = nodeStorage.Scheme; - - public bool HasRoot(Hash256 stateRoot) - { - return nodeStorage.KeyExists(null, TreePath.Empty, stateRoot); - } - - public IDisposable BeginScope(BlockHeader? baseBlock) - { - return new Reactive.AnonymousDisposable(() => { }); - } - - public IScopedTrieStore GetTrieStore(Hash256? address) - { - return new RawScopedTrieStore(nodeStorage, address); - } + private readonly INodeStorage _nodeStorage = nodeStorage; - public IBlockCommitter BeginBlockCommit(long blockNumber) + public void PersistCache(CancellationToken cancellationToken) { - return NullCommitter.Instance; } - public void PersistCache(CancellationToken cancellationToken) + public override ICommitter BeginCommit(Hash256? address, TrieNode? root, WriteFlags writeFlags) { + if (isReadOnly) return NullCommitter.Instance; + return new RawScopedTrieStore.Committer(_nodeStorage, address, writeFlags); } public IReadOnlyTrieStore AsReadOnly() => - new TestRawTrieStore(nodeStorage, true); + new TestRawTrieStore(_nodeStorage, true); public event EventHandler? ReorgBoundaryReached { @@ -90,10 +44,11 @@ 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(); + public TrieStore.StableLockScope PrepareStableState(CancellationToken cancellationToken) { var scopeLockScope = _scopeLock.EnterScope(); diff --git a/src/Nethermind/Nethermind.Core.Test/TestTrieStoreFactory.cs b/src/Nethermind/Nethermind.Core.Test/TestTrieStoreFactory.cs index d1fd621fc475..2ed78ebf4f02 100644 --- a/src/Nethermind/Nethermind.Core.Test/TestTrieStoreFactory.cs +++ b/src/Nethermind/Nethermind.Core.Test/TestTrieStoreFactory.cs @@ -29,6 +29,9 @@ public static TestRawTrieStore Build(IKeyValueStoreWithBatching keyValueStore, I public static TrieStore Build(IKeyValueStoreWithBatching keyValueStore, IPruningStrategy pruningStrategy, IPersistenceStrategy persistenceStrategy, ILogManager logManager) { - return new TrieStore(new NodeStorage(keyValueStore), pruningStrategy, persistenceStrategy, _testPruningConfig, logManager); + TestFinalizedStateProvider finalizedStateProvider = new TestFinalizedStateProvider(_testPruningConfig.PruningBoundary); + TrieStore trieStore = new TrieStore(new NodeStorage(keyValueStore), pruningStrategy, persistenceStrategy, finalizedStateProvider, _testPruningConfig, logManager); + finalizedStateProvider.TrieStore = trieStore; + return trieStore; } } diff --git a/src/Nethermind/Nethermind.Core.Test/TestWorldStateFactory.cs b/src/Nethermind/Nethermind.Core.Test/TestWorldStateFactory.cs index 8b963082dcff..3f93e3d4ff9d 100644 --- a/src/Nethermind/Nethermind.Core.Test/TestWorldStateFactory.cs +++ b/src/Nethermind/Nethermind.Core.Test/TestWorldStateFactory.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Nethermind.Consensus.Validators; using Nethermind.Core.Test.Db; using Nethermind.Db; using Nethermind.Logging; @@ -14,44 +13,54 @@ namespace Nethermind.Core.Test; public static class TestWorldStateFactory { - public static IWorldState CreateForTest() + public static IWorldState CreateForTest(IDbProvider? dbProvider = null, ILogManager? logManager = null) { - return CreateForTest(TestMemDbProvider.Init(), LimboLogs.Instance); - } - - public static IWorldState CreateForTest(IDbProvider dbProvider, ILogManager logManager) - { - IPruningTrieStore trieStore = new TrieStore( + 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, Persist.EveryBlock, - new PruningConfig(), + finalizedStateProvider, + pruningConfig, LimboLogs.Instance); - return new WorldState(trieStore, dbProvider.CodeDb, logManager); + finalizedStateProvider.TrieStore = trieStore; + return new WorldState(new TrieStoreScopeProvider(trieStore, dbProvider.CodeDb, logManager), logManager); } public static (IWorldState, IStateReader) CreateForTestWithStateReader(IDbProvider? dbProvider = null, ILogManager? logManager = null) { if (dbProvider is null) dbProvider = TestMemDbProvider.Init(); if (logManager is null) logManager = LimboLogs.Instance; - IPruningTrieStore trieStore = new TrieStore( + + PruningConfig pruningConfig = new PruningConfig(); + TestFinalizedStateProvider finalizedStateProvider = new TestFinalizedStateProvider(pruningConfig.PruningBoundary); + TrieStore trieStore = new TrieStore( new NodeStorage(dbProvider.StateDb), No.Pruning, Persist.EveryBlock, - new PruningConfig(), + finalizedStateProvider, + pruningConfig, LimboLogs.Instance); - return (new WorldState(trieStore, dbProvider.CodeDb, logManager), new StateReader(trieStore, dbProvider.CodeDb, logManager)); + finalizedStateProvider.TrieStore = trieStore; + return (new WorldState(new TrieStoreScopeProvider(trieStore, dbProvider.CodeDb, logManager), logManager), new StateReader(trieStore, dbProvider.CodeDb, logManager)); } public static WorldStateManager CreateWorldStateManagerForTest(IDbProvider dbProvider, ILogManager logManager) { - IPruningTrieStore trieStore = new TrieStore( + PruningConfig pruningConfig = new PruningConfig(); + TestFinalizedStateProvider finalizedStateProvider = new TestFinalizedStateProvider(pruningConfig.PruningBoundary); + TrieStore trieStore = new TrieStore( new NodeStorage(dbProvider.StateDb), No.Pruning, Persist.EveryBlock, - new PruningConfig(), + finalizedStateProvider, + pruningConfig, LimboLogs.Instance); - var worldState = new WorldState(trieStore, dbProvider.CodeDb, logManager); + finalizedStateProvider.TrieStore = trieStore; + 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 ab7b335ff023..d0166e228f2e 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 23ba0f6de613..000000000000 --- 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 d20e96be38d2..80516f59c95c 100644 --- a/src/Nethermind/Nethermind.Core/Address.cs +++ b/src/Nethermind/Nethermind.Core/Address.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Text.Json.Serialization; - +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -20,7 +20,7 @@ namespace Nethermind.Core [JsonConverter(typeof(AddressConverter))] [TypeConverter(typeof(AddressTypeConverter))] [DebuggerDisplay("{ToString()}")] - public class Address : IEquatable
, IComparable
+ public sealed class Address : IEquatable
, IComparable
{ public const int Size = 20; private const int HexCharsCount = 2 * Size; // 5a4eab120fb44eb6684e5e32785702ff45ea344d @@ -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 { @@ -273,9 +273,11 @@ public ValueHash256 ToHash() return result; } + + internal long GetHashCode64() => SpanExtensions.FastHash64For20Bytes(ref MemoryMarshal.GetArrayDataReference(Bytes)); } - public readonly struct AddressAsKey(Address key) : IEquatable + public readonly struct AddressAsKey(Address key) : IEquatable, IHash64bit { private readonly Address _key = key; public Address Value => _key; @@ -289,6 +291,10 @@ public override string ToString() { return _key?.ToString() ?? ""; } + + public long GetHashCode64() => _key is not null ? _key.GetHashCode64() : 0; + + public bool Equals(in AddressAsKey other) => _key == other._key; } public ref struct AddressStructRef diff --git a/src/Nethermind/Nethermind.Core/Attributes/Metrics.cs b/src/Nethermind/Nethermind.Core/Attributes/Metrics.cs index 14baa42185e7..6806bcecd8db 100644 --- a/src/Nethermind/Nethermind.Core/Attributes/Metrics.cs +++ b/src/Nethermind/Nethermind.Core/Attributes/Metrics.cs @@ -59,6 +59,14 @@ public sealed class DetailedMetricAttribute : Attribute { } +/// +/// Mark a property that is used to enable detailed metrics (not a metric itself) +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +public sealed class DetailedMetricOnFlagAttribute : Attribute +{ +} + public record StringLabel(string label) : IMetricLabels { public string[] Labels => [label]; diff --git a/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs b/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs index 21c8ce26d026..b93ec4916d3e 100644 --- a/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs +++ b/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs @@ -2,10 +2,14 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; +using System.Buffers.Text; using System.IO; using System.Runtime.CompilerServices; using System.Security.Cryptography; +using System.Text; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; @@ -16,22 +20,57 @@ 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; + + // Manual HS256 validation limits + private const int SHA256HashBytes = 32; + private const int HS256SignatureSegmentLength = 44; // 43 Base64Url chars + dot separator + private const int StackBufferSize = 256; + private const int MaxManualJwtLength = StackBufferSize + HS256SignatureSegmentLength; // 300 + + private static readonly Task True = Task.FromResult(true); private static readonly Task False = Task.FromResult(false); + + // Known HS256 JWT header Base64Url encodings used by consensus clients + // {"alg":"HS256","typ":"JWT"} + private const string HeaderAlgTyp = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; + // {"typ":"JWT","alg":"HS256"} + private const string HeaderTypAlg = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"; + // {"alg":"HS256"} (some CLs omit typ) + private const string HeaderAlgOnly = "eyJhbGciOiJIUzI1NiJ9"; + private readonly JsonWebTokenHandler _handler = new(); - private readonly SecurityKey _securityKey; + private readonly byte[] _secretBytes; + private readonly TokenValidationParameters _tokenValidationParameters; private readonly ILogger _logger; private readonly ITimestamper _timestamper; - private const string JwtMessagePrefix = "Bearer "; - private const int JwtTokenTtl = 60; - private const int JwtSecretLength = 64; + + // Single entry cache: last successfully validated token (allocation-free) + // Write order: iat first, then token with Volatile.Write (release fence) + // Read order: token with Volatile.Read (acquire fence), then iat + private string? _cachedToken; + private long _cachedTokenIat; private JwtAuthentication(byte[] secret, ITimestamper timestamper, ILogger logger) { ArgumentNullException.ThrowIfNull(secret); + ArgumentNullException.ThrowIfNull(timestamper); - _securityKey = new SymmetricSecurityKey(secret); + _secretBytes = secret; + SecurityKey securityKey = new SymmetricSecurityKey(secret); _logger = logger; - _timestamper = timestamper ?? throw new ArgumentNullException(nameof(timestamper)); + _timestamper = timestamper; + _tokenValidationParameters = new TokenValidationParameters + { + IssuerSigningKey = securityKey, + RequireExpirationTime = false, + ValidateLifetime = true, + ValidateAudience = false, + ValidateIssuer = false, + LifetimeValidator = LifetimeValidator + }; } public static JwtAuthentication FromSecret(string secret, ITimestamper timestamper, ILogger logger) @@ -118,7 +157,22 @@ public Task Authenticate(string? token) return False; } - return AuthenticateCore(token); + // 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; + } + + // fast manual HS256 validation: avoids Microsoft.IdentityModel overhead + if (TryValidateManual(token, nowUnixSeconds, out bool accepted)) + { + return accepted ? True : False; + } + + // fallback to full library validation for unrecognized header formats + return AuthenticateCore(token, nowUnixSeconds); [MethodImpl(MethodImplOptions.NoInlining)] void WarnTokenNotFound() => _logger.Warn("Message authentication error: The token cannot be found."); @@ -127,77 +181,267 @@ public Task Authenticate(string? token) void TokenMalformed() => _logger.Warn($"Message authentication error: The token must start with '{JwtMessagePrefix}'."); } - private async Task AuthenticateCore(string token) + /// + /// Manual HS256 JWT validation for known header formats. + /// Returns true if handled (result in ), false to fall through to library. + /// + [SkipLocalsInit] + private bool TryValidateManual(string token, long nowUnixSeconds, out bool accepted) { - try + accepted = false; + // Extract raw JWT (after "Bearer ") + ReadOnlySpan jwt = token.AsSpan(JwtMessagePrefix.Length); + + // Bail early: signed part must fit in StackBufferSize, plus HS256SignatureSegmentLength for the signature. + if (jwt.Length > MaxManualJwtLength) + return false; + + // Known HS256 header lengths: 36 (AlgTyp, TypAlg) or 20 (AlgOnly). + // Check dot at known position and verify header in one shot — avoids IndexOf scan. + int firstDot; + if (jwt.Length > 36 && jwt[36] == '.' && + (jwt[..36].SequenceEqual(HeaderAlgTyp) || jwt[..36].SequenceEqual(HeaderTypAlg))) { - TokenValidationParameters tokenValidationParameters = new() - { - IssuerSigningKey = _securityKey, - RequireExpirationTime = false, - ValidateLifetime = true, - ValidateAudience = false, - ValidateIssuer = false, - LifetimeValidator = LifetimeValidator - }; + firstDot = 36; + } + else if (jwt.Length > 20 && jwt[20] == '.' && jwt[..20].SequenceEqual(HeaderAlgOnly)) + { + firstDot = 20; + } + else + { + return false; + } - ReadOnlyMemory tokenSlice = token.AsMemory(JwtMessagePrefix.Length); - JsonWebToken jwtToken = _handler.ReadJsonWebToken(tokenSlice); - TokenValidationResult result = await _handler.ValidateTokenAsync(jwtToken, tokenValidationParameters); + // HS256 sig = 43 Base64Url chars, so second dot is at jwt.Length - HS256SignatureSegmentLength. + // Computed directly — eliminates IndexOf scan over payload+signature. + int secondDot = jwt.Length - HS256SignatureSegmentLength; + if (secondDot <= firstDot || jwt[secondDot] != '.') + return false; + + ReadOnlySpan payload = jwt[(firstDot + 1)..secondDot]; + ReadOnlySpan signature = jwt[(secondDot + 1)..]; + + // Compute HMAC-SHA256 over "header.payload" (ASCII bytes). + // secondDot == char count == byte count (JWT is pure ASCII). + // Early length check guarantees secondDot <= 256. + ReadOnlySpan signedPart = jwt[..secondDot]; + Span signedBytes = stackalloc byte[StackBufferSize]; + signedBytes = signedBytes[..secondDot]; + + if (Ascii.FromUtf16(signedPart, signedBytes, out _) != OperationStatus.Done) + return false; + + Span computedHash = stackalloc byte[SHA256HashBytes]; + HMACSHA256.HashData(_secretBytes, signedBytes, computedHash); + + Span sigBytes = stackalloc byte[SHA256HashBytes]; + if (Base64Url.DecodeFromChars(signature, sigBytes, out _, out int sigBytesWritten) != OperationStatus.Done + || sigBytesWritten != SHA256HashBytes) + { + return true; + } + + if (!CryptographicOperations.FixedTimeEquals(computedHash, sigBytes)) + { + if (_logger.IsWarn) WarnInvalidSig(); + return true; + } + + if (!TryExtractClaims(payload, out long iat, out long exp)) + return false; // sig valid but can't parse claims — let library handle it + + if (exp > 0 && nowUnixSeconds >= exp) + { + if (_logger.IsWarn) WarnTokenExpiredExp(exp, nowUnixSeconds); + return true; + } + + // Overflow-safe absolute-difference check: casting to ulong maps negative values to + // large positives, so (ulong)(a - b + c) > (ulong)(2*c) is equivalent to |a - b| > c + // without needing Math.Abs (which can overflow on long.MinValue). + if ((ulong)(iat - nowUnixSeconds + JwtTokenTtl) > (ulong)(JwtTokenTtl * 2)) + { + if (_logger.IsWarn) WarnTokenExpiredIat(iat, nowUnixSeconds); + return true; + } + + CacheLastToken(token, iat); + accepted = true; + return true; + + + [MethodImpl(MethodImplOptions.NoInlining)] + void WarnTokenExpiredExp(long e, long now) => _logger.Warn($"Token expired. exp: {e}, now: {now}"); + + [MethodImpl(MethodImplOptions.NoInlining)] + void WarnTokenExpiredIat(long i, long now) => _logger.Warn($"Token expired. iat: {i}, now: {now}"); + + [MethodImpl(MethodImplOptions.NoInlining)] + void WarnInvalidSig() => _logger.Warn("Message authentication error: Invalid token signature."); + } + + /// + /// Extract "iat" (required) and "exp" (optional) integer claims from a JWT payload. + /// Uses a lightweight byte scanner instead of Utf8JsonReader to avoid: + /// - 552-byte stack frame (Utf8JsonReader struct ~200 bytes + locals) + /// - 432-byte prolog zeroing loop + /// - ArrayPool rent/return + try/finally + /// - 90 inlined reader methods bloating to 2979 bytes of native code + /// + [SkipLocalsInit] + private static bool TryExtractClaims(ReadOnlySpan payloadBase64Url, out long iat, out long exp) + { + iat = 0; + exp = 0; + + // Decode payload from Base64Url into a fixed stack buffer. + // Engine API payloads are tiny (~30 bytes). If decoded output exceeds + // StackBufferSize bytes, DecodeFromChars returns DestinationTooSmall → we reject. + Span decoded = stackalloc byte[StackBufferSize]; + if (Base64Url.DecodeFromChars(payloadBase64Url, decoded, out _, out int bytesWritten) != OperationStatus.Done) + return false; + + // Scan decoded UTF-8 bytes for "iat" and "exp" keys with integer values. + // JWT payloads are compact JSON objects: {"iat":NNNNN,"exp":NNNNN} + // The scanner finds quoted 3-letter keys and parses the integer after the colon. + ReadOnlySpan json = decoded[..bytesWritten]; + bool foundIat = false; - if (!result.IsValid) + for (int i = 0; i < json.Length - 4; i++) + { + if (json[i] != '"') continue; + + byte k1 = json[i + 1], k2 = json[i + 2], k3 = json[i + 3]; + if (json[i + 4] != '"') continue; + + if (k1 == 'i' && k2 == 'a' && k3 == 't') { - if (_logger.IsWarn) WarnInvalidResult(result.Exception); - return false; + foundIat = TryParseClaimValue(json, i + 5, out iat); } - - DateTime now = _timestamper.UtcNow; - if (Math.Abs(jwtToken.IssuedAt.ToUnixTimeSeconds() - now.ToUnixTimeSeconds()) <= JwtTokenTtl) + else if (k1 == 'e' && k2 == 'x' && k3 == 'p') { - if (_logger.IsTrace) Trace(jwtToken, now, tokenSlice); - return true; + TryParseClaimValue(json, i + 5, out exp); } + } - if (_logger.IsWarn) WarnTokenExpired(jwtToken, now); - return false; + return foundIat; + } + + /// + /// Parse an integer claim value starting at (expects ":digits"). + /// The (uint)pos < (uint)json.Length pattern collapses a two-condition bounds check + /// (pos >= 0 && pos < Length) into a single unsigned comparison, allowing the + /// JIT to eliminate the redundant range check on the subsequent indexer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryParseClaimValue(ReadOnlySpan json, int pos, out long value) + { + value = 0; + // Skip optional whitespace before colon + while ((uint)pos < (uint)json.Length && json[pos] == ' ') pos++; + if ((uint)pos >= (uint)json.Length || json[pos] != ':') return false; + pos++; + // Skip optional whitespace after colon + while ((uint)pos < (uint)json.Length && json[pos] == ' ') pos++; + // Parse unsigned integer digits + bool hasDigit = false; + while ((uint)pos < (uint)json.Length) + { + uint digit = (uint)(json[pos] - '0'); + if (digit > 9) break; + value = value * 10 + digit; + hasDigit = true; + pos++; + } + return hasDigit; + } + + /// + /// Library-based JWT validation for unrecognized header formats. + /// Checks for synchronous Task completion to avoid async state machine on the hot path. + /// + private Task AuthenticateCore(string token, long nowUnixSeconds) + { + try + { + ReadOnlyMemory tokenSlice = token.AsMemory(JwtMessagePrefix.Length); + JsonWebToken jwtToken = _handler.ReadJsonWebToken(tokenSlice); + Task task = _handler.ValidateTokenAsync(jwtToken, _tokenValidationParameters); + + // HS256 validation is CPU-bound → task is almost always already completed. + // Avoid async state machine overhead for the common synchronous path. + return task.IsCompletedSuccessfully + ? ValidateLibraryResult(task.GetAwaiter().GetResult(), token, jwtToken, nowUnixSeconds) ? True : False + : AwaitValidation(task, token, jwtToken, nowUnixSeconds); } catch (Exception ex) { - if (_logger.IsWarn) WarnAuthenticationError(ex); + if (_logger.IsWarn) WarnAuthError(ex); + return False; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + void WarnAuthError(Exception? ex) => _logger.Warn($"Message authentication error: {ex?.Message}"); + } + + private bool ValidateLibraryResult(TokenValidationResult result, string token, JsonWebToken jwtToken, long nowUnixSeconds) + { + if (!result.IsValid) + { + if (_logger.IsWarn) WarnInvalidResult(result.Exception); + return false; + } + + long issuedAtUnix = jwtToken.IssuedAt.ToUnixTimeSeconds(); + + // Unsigned range check: |iat - now| <= TTL without Math.Abs overflow guard + if ((ulong)(issuedAtUnix - nowUnixSeconds + JwtTokenTtl) > (ulong)(JwtTokenTtl * 2)) + { + if (_logger.IsWarn) WarnTokenExpired(issuedAtUnix, nowUnixSeconds); return false; } + CacheLastToken(token, issuedAtUnix); + if (_logger.IsTrace) TraceAuth(jwtToken, nowUnixSeconds, token); + return true; + [MethodImpl(MethodImplOptions.NoInlining)] void WarnInvalidResult(Exception? ex) { - if (ex is SecurityTokenDecryptionFailedException) + _logger.Warn(ex switch { - _logger.Warn("Message authentication error: The token cannot be decrypted."); - } - else if (ex is SecurityTokenReplayDetectedException) - { - _logger.Warn("Message authentication error: The token has been used multiple times."); - } - else if (ex is SecurityTokenInvalidSignatureException) - { - _logger.Warn("Message authentication error: Invalid token signature."); - } - else - { - WarnAuthenticationError(ex); - } + SecurityTokenDecryptionFailedException => "Message authentication error: The token cannot be decrypted.", + SecurityTokenReplayDetectedException => "Message authentication error: The token has been used multiple times.", + SecurityTokenInvalidSignatureException => "Message authentication error: Invalid token signature.", + _ => $"Message authentication error: {ex?.Message}" + }); } [MethodImpl(MethodImplOptions.NoInlining)] - void WarnAuthenticationError(Exception? ex) => _logger.Warn($"Message authentication error: {ex?.Message}"); + void WarnTokenExpired(long iat, long now) + => _logger.Warn($"Token expired. iat: {iat}, now: {now}"); [MethodImpl(MethodImplOptions.NoInlining)] - void WarnTokenExpired(JsonWebToken jwtToken, DateTime now) - => _logger.Warn($"Token expired. Now is {now}, token issued at {jwtToken.IssuedAt}"); + void TraceAuth(JsonWebToken jwt, long now, string tok) + => _logger.Trace($"Message authenticated. Token: {tok.AsMemory(JwtMessagePrefix.Length)}, iat: {jwt.IssuedAt}, time: {now}"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private async Task AwaitValidation(Task task, string token, JsonWebToken jwtToken, long nowUnixSeconds) + { + try + { + return ValidateLibraryResult(await task, token, jwtToken, nowUnixSeconds); + } + catch (Exception ex) + { + if (_logger.IsWarn) WarnAuthError(ex); + return false; + } [MethodImpl(MethodImplOptions.NoInlining)] - void Trace(JsonWebToken jwtToken, DateTime now, ReadOnlyMemory token) - => _logger.Trace($"Message authenticated. Token: {token}, iat: {jwtToken.IssuedAt}, time: {now}"); + void WarnAuthError(Exception? ex) => _logger.Warn($"Message authentication error: {ex?.Message}"); } private bool LifetimeValidator( @@ -210,6 +454,37 @@ private bool LifetimeValidator( return _timestamper.UnixTime.SecondsLong < expires.Value.ToUnixTimeSeconds(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CacheLastToken(string token, long issuedAtUnixSeconds) + { + // Write iat first (plain store), then token with release fence. + // Reader uses acquire fence on token, then reads iat — guarantees + // the iat visible is at least as fresh as the token that was read. + _cachedTokenIat = issuedAtUnixSeconds; + Volatile.Write(ref _cachedToken, token); + } + + private bool TryLastValidationFromCache(string token, long nowUnixSeconds) + { + // Acquire fence on token read; guarantees _cachedTokenIat is at least as fresh + string? cached = Volatile.Read(ref _cachedToken); + if (cached is null) + return false; + + if (!string.Equals(cached, token, StringComparison.Ordinal)) + return false; + + // Unsigned range check: |iat - now| <= TTL + if ((ulong)(_cachedTokenIat - nowUnixSeconds + JwtTokenTtl) > (ulong)(JwtTokenTtl * 2)) + { + Volatile.Write(ref _cachedToken, null); + return false; + } + + return true; + } + [GeneratedRegex("^(0x)?[0-9a-fA-F]{64}$")] private static partial Regex SecretRegex(); + } diff --git a/src/Nethermind/Nethermind.Core/AuthorizationTuple.cs b/src/Nethermind/Nethermind.Core/AuthorizationTuple.cs index c5f91203b699..910939293c5f 100644 --- a/src/Nethermind/Nethermind.Core/AuthorizationTuple.cs +++ b/src/Nethermind/Nethermind.Core/AuthorizationTuple.cs @@ -5,6 +5,7 @@ using Nethermind.Int256; namespace Nethermind.Core; + public class AuthorizationTuple( UInt256 chainId, Address codeAddress, diff --git a/src/Nethermind/Nethermind.Core/BaseFeeCalculator.cs b/src/Nethermind/Nethermind.Core/BaseFeeCalculator.cs index 42b25af8cb71..6987630b47f3 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/Block.cs b/src/Nethermind/Nethermind.Core/Block.cs index 8d53097d5198..5fd94383cdb6 100644 --- a/src/Nethermind/Nethermind.Core/Block.cs +++ b/src/Nethermind/Nethermind.Core/Block.cs @@ -126,6 +126,13 @@ public Transaction[] Transactions [JsonIgnore] public int? EncodedSize { get; set; } + /// + /// Pre-encoded transaction bytes in SkipTypedWrapping format (as received from CL). + /// Used to avoid re-encoding transactions when storing blocks. + /// + [JsonIgnore] + public byte[][]? EncodedTransactions { get; set; } + public override string ToString() => ToString(Format.Short); public string ToString(Format format) => format switch diff --git a/src/Nethermind/Nethermind.Core/BlockHeader.cs b/src/Nethermind/Nethermind.Core/BlockHeader.cs index 3183f88d2d3b..92bcc5d1eabe 100644 --- a/src/Nethermind/Nethermind.Core/BlockHeader.cs +++ b/src/Nethermind/Nethermind.Core/BlockHeader.cs @@ -44,7 +44,8 @@ public BlockHeader( ExcessBlobGas = excessBlobGas; } - public bool IsGenesis => Number == 0L; + public virtual long GenesisBlockNumber => 0; + public bool IsGenesis => Number == GenesisBlockNumber; public Hash256? ParentHash { get; set; } public Hash256? UnclesHash { get; set; } public Address? Author { get; set; } @@ -80,7 +81,6 @@ public BlockHeader( public bool HasTransactions => (TxRoot is not null && TxRoot != Keccak.EmptyTreeHash); - public string SealEngineType { get; set; } = Core.SealEngineType.Ethash; public bool IsPostMerge { get; set; } public string ToString(string indent) diff --git a/src/Nethermind/Nethermind.Core/BlockInfo.cs b/src/Nethermind/Nethermind.Core/BlockInfo.cs index d458e544ff0f..675639485a41 100644 --- a/src/Nethermind/Nethermind.Core/BlockInfo.cs +++ b/src/Nethermind/Nethermind.Core/BlockInfo.cs @@ -18,20 +18,13 @@ public enum BlockMetadata BeaconMainChain = 16 } - public class BlockInfo : IEquatable + public class BlockInfo(Hash256 blockHash, in UInt256 totalDifficulty, BlockMetadata metadata = BlockMetadata.None) : IEquatable { - public BlockInfo(Hash256 blockHash, in UInt256 totalDifficulty, BlockMetadata metadata = BlockMetadata.None) - { - BlockHash = blockHash; - TotalDifficulty = totalDifficulty; - Metadata = metadata; - } - - public UInt256 TotalDifficulty { get; set; } + public UInt256 TotalDifficulty { get; set; } = totalDifficulty; public bool WasProcessed { get; set; } - public Hash256 BlockHash { get; } + public Hash256 BlockHash { get; } = blockHash; public bool IsFinalized { @@ -70,7 +63,7 @@ public bool IsBeaconInfo get => (Metadata & (BlockMetadata.BeaconBody | BlockMetadata.BeaconHeader)) != 0; } - public BlockMetadata Metadata { get; set; } + public BlockMetadata Metadata { get; set; } = metadata; /// /// This property is not serialized diff --git a/src/Nethermind/Nethermind.Core/Bloom.cs b/src/Nethermind/Nethermind.Core/Bloom.cs index e3f2749e7ccc..cc5d3acce000 100644 --- a/src/Nethermind/Nethermind.Core/Bloom.cs +++ b/src/Nethermind/Nethermind.Core/Bloom.cs @@ -53,7 +53,10 @@ public Bloom(ReadOnlySpan bytes) bytes.CopyTo(Bytes); } + [JsonIgnore] public Span Bytes => _bloomData.AsSpan(); + [JsonIgnore] + public ReadOnlySpan ReadOnlyBytes => _bloomData.AsReadOnlySpan(); private Span ULongs => _bloomData.AsULongs(); public void Set(ReadOnlySpan sequence, Bloom? masterBloom = null) @@ -79,7 +82,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 +104,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 +115,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 +254,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 +262,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 c2b59492a3a5..f58df98a53a4 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/Buffers/ArrayMemoryManager.cs b/src/Nethermind/Nethermind.Core/Buffers/ArrayMemoryManager.cs new file mode 100644 index 000000000000..52c1bf4dd496 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Buffers/ArrayMemoryManager.cs @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; + +namespace Nethermind.Core.Buffers; + +/// +/// Simple MemoryManager that wraps a byte array without any pinning. +/// Used for in-memory stores where the array is managed and doesn't require special release handling. +/// +public sealed class ArrayMemoryManager(byte[] array) : MemoryManager +{ + protected override void Dispose(bool disposing) { } + + public override Span GetSpan() => array; + + public override MemoryHandle Pin(int elementIndex = 0) => default; + + public override void Unpin() { } +} diff --git a/src/Nethermind/Nethermind.Core/Buffers/CappedArray.cs b/src/Nethermind/Nethermind.Core/Buffers/CappedArray.cs index 504129823990..8b93eb277542 100644 --- a/src/Nethermind/Nethermind.Core/Buffers/CappedArray.cs +++ b/src/Nethermind/Nethermind.Core/Buffers/CappedArray.cs @@ -113,12 +113,10 @@ public readonly Span AsSpan(int start, int length) return AsSpan().ToArray(); } - public override string? ToString() - { - return typeof(T) == typeof(byte) ? - SpanExtensions.ToHexString(MemoryMarshal.AsBytes(AsSpan()), withZeroX: true) : + public override string? ToString() => + typeof(T) == typeof(byte) ? + MemoryMarshal.AsBytes(AsSpan()).ToHexString(withZeroX: true) : base.ToString(); - } public readonly ArraySegment AsArraySegment() { diff --git a/src/Nethermind/Nethermind.Core/DbSpanMemoryManager.cs b/src/Nethermind/Nethermind.Core/Buffers/DbSpanMemoryManager.cs similarity index 58% rename from src/Nethermind/Nethermind.Core/DbSpanMemoryManager.cs rename to src/Nethermind/Nethermind.Core/Buffers/DbSpanMemoryManager.cs index 48d881ae00c0..18a32b032a88 100644 --- a/src/Nethermind/Nethermind.Core/DbSpanMemoryManager.cs +++ b/src/Nethermind/Nethermind.Core/Buffers/DbSpanMemoryManager.cs @@ -10,24 +10,16 @@ namespace Nethermind.Core.Buffers; -public unsafe sealed class DbSpanMemoryManager : MemoryManager +public sealed unsafe class DbSpanMemoryManager(IReadOnlyKeyValueStore db, Span unmanagedSpan) : MemoryManager { - private readonly IReadOnlyKeyValueStore _db; - private void* _ptr; - private readonly int _length; - - public DbSpanMemoryManager(IReadOnlyKeyValueStore db, Span unmanagedSpan) - { - _db = db; - _ptr = Unsafe.AsPointer(ref MemoryMarshal.GetReference(unmanagedSpan)); - _length = unmanagedSpan.Length; - } + private void* _ptr = Unsafe.AsPointer(ref MemoryMarshal.GetReference(unmanagedSpan)); + private readonly int _length = unmanagedSpan.Length; protected override void Dispose(bool disposing) { if (_ptr is not null) { - _db.DangerousReleaseMemory(GetSpan()); + db.DangerousReleaseMemory(GetSpan()); } _ptr = null; @@ -53,13 +45,8 @@ public override MemoryHandle Pin(int elementIndex = 0) return new MemoryHandle(_ptr); } - public override void Unpin() - { - } + public override void Unpin() { } [DoesNotReturn, StackTraceHidden] - private static void ThrowDisposed() - { - throw new ObjectDisposedException(nameof(DbSpanMemoryManager)); - } + private static void ThrowDisposed() => throw new ObjectDisposedException(nameof(DbSpanMemoryManager)); } diff --git a/src/Nethermind/Nethermind.Core/Buffers/SpanSource.cs b/src/Nethermind/Nethermind.Core/Buffers/SpanSource.cs index b84e9b1d0ba0..a58ce5951999 100644 --- a/src/Nethermind/Nethermind.Core/Buffers/SpanSource.cs +++ b/src/Nethermind/Nethermind.Core/Buffers/SpanSource.cs @@ -110,21 +110,7 @@ public bool IsNullOrEmpty } } - public bool IsNotNullOrEmpty - { - get - { - var obj = _obj; - - if (obj == null) - return false; - - if (obj is byte[] array) - return array.Length != 0; - - return Unsafe.As(obj).Length != 0; - } - } + public bool IsNotNullOrEmpty => !IsNullOrEmpty; public static readonly SpanSource Empty = new([]); diff --git a/src/Nethermind/Nethermind.Core/Bytes32.cs b/src/Nethermind/Nethermind.Core/Bytes32.cs index 2b4edfd856d2..cbaacbf7b930 100644 --- a/src/Nethermind/Nethermind.Core/Bytes32.cs +++ b/src/Nethermind/Nethermind.Core/Bytes32.cs @@ -4,7 +4,6 @@ using System; using System.Buffers.Binary; using System.Diagnostics; -using System.Linq; using Nethermind.Core.Extensions; namespace Nethermind.Core; diff --git a/src/Nethermind/Nethermind.Core/Caching/ClockCache.cs b/src/Nethermind/Nethermind.Core/Caching/ClockCache.cs index a13c6eaef1fe..ee94fa68bedb 100644 --- a/src/Nethermind/Nethermind.Core/Caching/ClockCache.cs +++ b/src/Nethermind/Nethermind.Core/Caching/ClockCache.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; @@ -136,9 +136,15 @@ void ThrowInvalidOperationException() } } - public bool Delete(TKey key) + public bool Delete(TKey key) => Delete(key, out _); + + public bool Delete(TKey key, [NotNullWhen(true)] out TValue? value) { - if (MaxCapacity == 0) return false; + if (MaxCapacity == 0) + { + value = default; + return false; + } using var lockRelease = _lock.Acquire(); @@ -148,9 +154,11 @@ public bool Delete(TKey key) KeyToOffset[ov.Offset] = default; ClearAccessed(ov.Offset); FreeOffsets.Enqueue(ov.Offset); - return true; + value = ov.Value; + return ov.Value != null; } + value = default; return false; } diff --git a/src/Nethermind/Nethermind.Core/Caching/ISpanCache.cs b/src/Nethermind/Nethermind.Core/Caching/ISpanCache.cs deleted file mode 100644 index c7b56ebd2a8f..000000000000 --- a/src/Nethermind/Nethermind.Core/Caching/ISpanCache.cs +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; - -namespace Nethermind.Core.Caching -{ - /// - /// Its like `ICache` but you can index the key by span - /// - /// - /// - public interface ISpanCache - { - void Clear(); - TValue? Get(ReadOnlySpan key); - bool TryGet(ReadOnlySpan key, out TValue? value); - - /// - /// Sets value in the cache. - /// - /// - /// - /// True if key didn't exist in the cache, otherwise false. - bool Set(ReadOnlySpan key, TValue val); - - /// - /// Delete key from cache. - /// - /// - /// True if key existed in the cache, otherwise false. - bool Delete(ReadOnlySpan key); - bool Contains(ReadOnlySpan key); - int Count { get; } - } -} diff --git a/src/Nethermind/Nethermind.Core/Caching/LruCache.cs b/src/Nethermind/Nethermind.Core/Caching/LruCache.cs index 727f21d6827a..bc87e4e83bab 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 000000000000..520010fb55a4 --- /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/CappedArrayMemoryManager.cs b/src/Nethermind/Nethermind.Core/CappedArrayMemoryManager.cs deleted file mode 100644 index 1f499890f03c..000000000000 --- a/src/Nethermind/Nethermind.Core/CappedArrayMemoryManager.cs +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Buffers; - -namespace Nethermind.Core.Buffers; - -public class CappedArrayMemoryManager(CappedArray? data) : MemoryManager -{ - private readonly CappedArray _data = data ?? throw new ArgumentNullException(nameof(data)); - private bool _isDisposed; - - public override Span GetSpan() - { - ObjectDisposedException.ThrowIf(_isDisposed, this); - return _data.AsSpan(); - } - - public override MemoryHandle Pin(int elementIndex = 0) - { - ObjectDisposedException.ThrowIf(_isDisposed, this); - ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)elementIndex, (uint)_data.Length); - // Pinning is a no-op in this managed implementation - return default; - } - - public override void Unpin() - { - ObjectDisposedException.ThrowIf(_isDisposed, this); - // Unpinning is a no-op in this managed implementation - } - - protected override void Dispose(bool disposing) - { - _isDisposed = true; - } - - protected override bool TryGetArray(out ArraySegment segment) - { - ObjectDisposedException.ThrowIf(_isDisposed, this); - segment = _data.AsArraySegment(); - return true; - } -} diff --git a/src/Nethermind/Nethermind.Core/ChainLevelInfo.cs b/src/Nethermind/Nethermind.Core/ChainLevelInfo.cs index a6066a9702a5..166c676bb75a 100644 --- a/src/Nethermind/Nethermind.Core/ChainLevelInfo.cs +++ b/src/Nethermind/Nethermind.Core/ChainLevelInfo.cs @@ -9,20 +9,14 @@ namespace Nethermind.Core { [DebuggerDisplay("Main: {HasBlockOnMainChain}, Blocks: {BlockInfos.Length}")] - public class ChainLevelInfo // TODO: move to blockchain namespace + public class ChainLevelInfo(bool hasBlockInMainChain, params BlockInfo[] blockInfos) { - public ChainLevelInfo(bool hasBlockInMainChain, params BlockInfo[] blockInfos) - { - HasBlockOnMainChain = hasBlockInMainChain; - BlockInfos = blockInfos; - } - private const int NotFound = -1; public bool HasNonBeaconBlocks => BlockInfos.Any(static b => (b.Metadata & (BlockMetadata.BeaconHeader | BlockMetadata.BeaconBody)) == 0); public bool HasBeaconBlocks => BlockInfos.Any(static b => (b.Metadata & (BlockMetadata.BeaconHeader | BlockMetadata.BeaconBody)) != 0); - public bool HasBlockOnMainChain { get; set; } - public BlockInfo[] BlockInfos { get; set; } + public bool HasBlockOnMainChain { get; set; } = hasBlockInMainChain; + public BlockInfo[] BlockInfos { get; set; } = blockInfos; public BlockInfo? MainChainBlock => HasBlockOnMainChain ? BlockInfos[0] : null; // ToDo we need to rethink this code diff --git a/src/Nethermind/Nethermind.Core/Collections/ArrayListCore.cs b/src/Nethermind/Nethermind.Core/Collections/ArrayListCore.cs new file mode 100644 index 000000000000..518fb7512842 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Collections/ArrayListCore.cs @@ -0,0 +1,300 @@ +using System; +using System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Nethermind.Core.Collections; + +internal static class ArrayPoolListCore +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GuardResize( + ArrayPool pool, + ref T[] array, + ref int capacity, + int count, + int itemsToAdd = 1) + { + int newCount = count + itemsToAdd; + + if (capacity == 0) + { + array = pool.Rent(newCount); + capacity = array.Length; + } + else if (newCount > capacity) + { + int newCapacity = capacity * 2; + if (newCapacity == 0) newCapacity = 1; + while (newCount > newCapacity) + { + newCapacity *= 2; + } + + T[] newArray = pool.Rent(newCapacity); + Array.Copy(array, 0, newArray, 0, count); + T[] oldArray = Interlocked.Exchange(ref array, newArray); + capacity = newArray.Length; + ClearToCount(oldArray, count); + pool.Return(oldArray); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ClearToCount(T[] array, int count) + { + if (count > 0 && RuntimeHelpers.IsReferenceOrContainsReferences()) + { + Array.Clear(array, 0, count); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ClearTail(T[] array, int newCount, int oldCount) + { + if (RuntimeHelpers.IsReferenceOrContainsReferences() && newCount < oldCount) + { + Array.Clear(array, newCount, oldCount - newCount); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Add( + ArrayPool pool, + ref T[] array, + ref int capacity, + ref int count, + T item) + { + GuardResize(pool, ref array, ref capacity, count); + array[count++] = item; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AddRange( + ArrayPool pool, + ref T[] array, + ref int capacity, + ref int count, + ReadOnlySpan items) + { + GuardResize(pool, ref array, ref capacity, count, items.Length); + items.CopyTo(array.AsSpan(count, items.Length)); + count += items.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clear(T[] array, ref int count) + { + ClearToCount(array, count); + count = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReduceCount( + ArrayPool pool, + ref T[] array, + ref int capacity, + ref int count, + int newCount) + { + int oldCount = count; + if (newCount == oldCount) return; + + if (newCount > oldCount) + ThrowOnlyReduce(newCount, oldCount); + + count = newCount; + + if (newCount < capacity / 2) + { + T[] newArray = pool.Rent(newCount); + array.AsSpan(0, newCount).CopyTo(newArray); + T[] oldArray = Interlocked.Exchange(ref array, newArray); + capacity = newArray.Length; + ClearToCount(oldArray, oldCount); + pool.Return(oldArray); + } + else + { + ClearTail(array, newCount, oldCount); + } + + [DoesNotReturn] + [StackTraceHidden] + static void ThrowOnlyReduce(int newCount, int oldCount) + { + throw new ArgumentException($"Count can only be reduced. {newCount} is larger than {oldCount}", + nameof(count)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Sort(T[] array, int count, Comparison comparison) + { + ArgumentNullException.ThrowIfNull(comparison); + if (count > 1) array.AsSpan(0, count).Sort(comparison); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Reverse(T[] array, int count) + { + array.AsSpan(0, count).Reverse(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Contains(T[] array, T item, int count) => IndexOf(array, count, item) >= 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOf(T[] array, int count, T item) + { + int indexOf = Array.IndexOf(array, item); + return indexOf < count ? indexOf : -1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CopyTo(T[] array, int count, T[] destination, int index) + { + array.AsMemory(0, count).CopyTo(destination.AsMemory(index)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool GuardIndex(int index, int count, bool shouldThrow = true, bool allowEqualToCount = false) + { + if ((uint)index > (uint)count || (!allowEqualToCount && index == count)) + { + if (shouldThrow) ThrowArgumentOutOfRangeException(); + return false; + } + + return true; + + [DoesNotReturn] + [StackTraceHidden] + static void ThrowArgumentOutOfRangeException() + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + } + + public static T? RemoveLast(T[] array, ref int count) + { + if (count > 0) + { + int index = count - 1; + T item = array[index]; + RemoveAt(array, ref count, index, true); + return item; + } + + return default; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool RemoveAt(T[] array, ref int count, int index, bool shouldThrow) + { + bool isValid = GuardIndex(index, count, shouldThrow); + if (isValid) + { + int start = index + 1; + if (start < count) + { + array.AsMemory(start, count - start).CopyTo(array.AsMemory(index)); + } + + count--; + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + array[count] = default!; + } + } + + return isValid; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Remove(T[] array, ref int count, T item) => RemoveAt(array, ref count, IndexOf(array, count, item), false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Insert( + ArrayPool pool, + ref T[] array, + ref int capacity, + ref int count, + int index, + T item) + { + GuardResize(pool, ref array, ref capacity, count); + GuardIndex(index, count, shouldThrow: true, allowEqualToCount: true); + array.AsMemory(index, count - index).CopyTo(array.AsMemory(index + 1)); + array[index] = item; + count++; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Truncate(int newLength, T[] array, ref int count) + { + GuardIndex(newLength, count, shouldThrow: true, allowEqualToCount: true); + ClearTail(array, newLength, count); + count = newLength; + } + + // Expose Get/Set and GetRef consistent with the original + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetRef(T[] array, int index, int count) + { + GuardIndex(index, count); + return ref array[index]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Get(T[] array, int index, int count) + { + GuardIndex(index, count); + return array[index]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Set(T[] array, int index, int count, T value) + { + GuardIndex(index, count); + array[index] = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Dispose( + ArrayPool pool, + ref T[] array, + ref int count, + ref int capacity) + { + T[]? localArray = array; + array = null!; + + if (localArray is not null) + { + ClearToCount(localArray, count); + pool.Return(localArray); + } + + count = 0; + capacity = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Dispose( + ArrayPool pool, + ref T[] array, + ref int count, + ref int capacity, + ref bool disposed) + { + if (!disposed && capacity != 0) + { + disposed = true; + Dispose(pool, ref array, ref count, ref capacity); + } + } +} diff --git a/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs b/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs index 440bb3554c2c..86920561f1e7 100644 --- a/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs +++ b/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs @@ -8,7 +8,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using System.Threading; namespace Nethermind.Core.Collections; @@ -18,6 +17,7 @@ public sealed class ArrayPoolList : IList, IList, IOwnedReadOnlyList private T[] _array; private int _capacity; private bool _disposed; + private int _count = 0; public ArrayPoolList(int capacity) : this(ArrayPool.Shared, capacity) { } @@ -42,18 +42,17 @@ public ArrayPoolList(ArrayPool arrayPool, int capacity, int startingCount = 0 } _capacity = _array.Length; - Count = startingCount; + _count = startingCount; } - ReadOnlySpan IOwnedReadOnlyList.AsSpan() - { - return AsSpan(); - } + public int Count => _count; + + ReadOnlySpan IOwnedReadOnlyList.AsSpan() => AsSpan(); public PooledArrayEnumerator GetEnumerator() { GuardDispose(); - return new PooledArrayEnumerator(_array, Count); + return new PooledArrayEnumerator(_array, _count); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -65,6 +64,7 @@ private void GuardDispose() } [DoesNotReturn] + [StackTraceHidden] static void ThrowObjectDisposed() { throw new ObjectDisposedException(nameof(ArrayPoolList)); @@ -77,8 +77,8 @@ static void ThrowObjectDisposed() public void Add(T item) { - GuardResize(); - _array[Count++] = item; + GuardDispose(); + ArrayPoolListCore.Add(_arrayPool, ref _array, ref _capacity, ref _count, item); } int IList.Add(object? value) @@ -87,27 +87,25 @@ int IList.Add(object? value) Add((T)value!); - return Count - 1; + return _count - 1; } - public void AddRange(ReadOnlySpan items) + public void AddRange(params ReadOnlySpan items) { - GuardResize(items.Length); - items.CopyTo(_array.AsSpan(Count, items.Length)); - Count += items.Length; + GuardDispose(); + ArrayPoolListCore.AddRange(_arrayPool, ref _array, ref _capacity, ref _count, items); } public void Clear() { - ClearToCount(_array); - Count = 0; + GuardDispose(); + ArrayPoolListCore.Clear(_array, ref _count); } public bool Contains(T item) { GuardDispose(); - int indexOf = Array.IndexOf(_array, item); - return indexOf >= 0 && indexOf < Count; + return ArrayPoolListCore.Contains(_array, item, _count); } bool IList.Contains(object? value) => IsCompatibleObject(value) && Contains((T)value!); @@ -115,73 +113,36 @@ public bool Contains(T item) public void CopyTo(T[] array, int arrayIndex) { GuardDispose(); - _array.AsMemory(0, Count).CopyTo(array.AsMemory(arrayIndex)); + ArrayPoolListCore.CopyTo(_array, _count, array, arrayIndex); } - void ICollection.CopyTo(Array array, int index) + void ICollection.CopyTo(Array? array, int index) { - if ((array is not null) && (array.Rank != 1)) - throw new ArgumentException("Only single dimensional arrays are supported.", nameof(array)); - - GuardDispose(); + if (array is not null && array.Rank != 1) + ThrowMultiDimensionalArray(); - Array.Copy(_array, 0, array!, index, Count); - } - - public int Count { get; private set; } = 0; - public void ReduceCount(int count) - { GuardDispose(); - var oldCount = Count; - if (count == oldCount) return; - if (count > oldCount) - { - ThrowOnlyReduce(count); - } - - Count = count; - if (count < _capacity / 2) - { - // Reduced to less than half of the capacity, resize the array. - T[] newArray = _arrayPool.Rent(count); - _array.AsSpan(0, count).CopyTo(newArray); - T[] oldArray = Interlocked.Exchange(ref _array, newArray); - _capacity = newArray.Length; - ClearToCount(oldArray); - _arrayPool.Return(oldArray); - } - else if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - // Release any references to the objects in the array that are no longer in use. - Array.Clear(_array, count, oldCount - count); - } + Array.Copy(_array, 0, array!, index, _count); - void ThrowOnlyReduce(int count) + [DoesNotReturn] + [StackTraceHidden] + static void ThrowMultiDimensionalArray() { - throw new ArgumentException($"Count can only be reduced. {count} is larger than {Count}", nameof(count)); + throw new ArgumentException("Only single dimensional arrays are supported.", nameof(array)); } } - private void ClearToCount(T[] array) + public void ReduceCount(int count) { - int count = Count; - // Release any references to the objects in the array so can be GC'd. - if (count > 0 && RuntimeHelpers.IsReferenceOrContainsReferences()) - { - Array.Clear(array, 0, count); - } + GuardDispose(); + ArrayPoolListCore.ReduceCount(_arrayPool, ref _array, ref _capacity, ref _count, count); } public void Sort(Comparison comparison) { - ArgumentNullException.ThrowIfNull(comparison); GuardDispose(); - - if (Count > 1) - { - _array.AsSpan(0, Count).Sort(comparison); - } + ArrayPoolListCore.Sort(_array, _count, comparison); } public int Capacity => _capacity; @@ -199,19 +160,15 @@ public void Sort(Comparison comparison) public int IndexOf(T item) { GuardDispose(); - int indexOf = Array.IndexOf(_array, item); - return indexOf < Count ? indexOf : -1; + return ArrayPoolListCore.IndexOf(_array, _count, item); } int IList.IndexOf(object? value) => IsCompatibleObject(value) ? IndexOf((T)value!) : -1; public void Insert(int index, T item) { - GuardResize(); - GuardIndex(index, allowEqualToCount: true); - _array.AsMemory(index, Count - index).CopyTo(_array.AsMemory(index + 1)); - _array[index] = item; - Count++; + GuardDispose(); + ArrayPoolListCore.Insert(_arrayPool, ref _array, ref _capacity, ref _count, index, item); } void IList.Insert(int index, object? value) @@ -221,87 +178,47 @@ void IList.Insert(int index, object? value) Insert(index, (T)value!); } - private void GuardResize(int itemsToAdd = 1) + public bool Remove(T item) { GuardDispose(); - int newCount = Count + itemsToAdd; - if (_capacity == 0) - { - _array = _arrayPool.Rent(newCount); - _capacity = _array.Length; - } - else if (newCount > _capacity) - { - int newCapacity = _capacity * 2; - if (newCapacity == 0) newCapacity = 1; - while (newCount > newCapacity) - { - newCapacity *= 2; - } - T[] newArray = _arrayPool.Rent(newCapacity); - _array.CopyTo(newArray, 0); - T[] oldArray = Interlocked.Exchange(ref _array, newArray); - _capacity = newArray.Length; - ClearToCount(oldArray); - _arrayPool.Return(oldArray); - } + return ArrayPoolListCore.Remove(_array, ref _count, item); } - public bool Remove(T item) => RemoveAtInternal(IndexOf(item), false); - void IList.Remove(object? value) { if (IsCompatibleObject(value)) Remove((T)value!); } - public void RemoveAt(int index) => RemoveAtInternal(index, true); - - private bool RemoveAtInternal(int index, bool shouldThrow) + public void RemoveAt(int index) { - bool isValid = GuardIndex(index, shouldThrow); - if (isValid) - { - int start = index + 1; - if (start < Count) - { - _array.AsMemory(start, Count - index).CopyTo(_array.AsMemory(index)); - } - - Count--; - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - _array[Count] = default!; - } - } - - return isValid; + GuardDispose(); + ArrayPoolListCore.RemoveAt(_array, ref _count, index, true); } public void Truncate(int newLength) { GuardDispose(); - GuardIndex(newLength, allowEqualToCount: true); - Count = newLength; + ArrayPoolListCore.Truncate(newLength, _array, ref _count); } public ref T GetRef(int index) { - GuardIndex(index); - return ref _array[index]; + GuardDispose(); + return ref ArrayPoolListCore.GetRef(_array, index, _count); } public T this[int index] { get { - GuardIndex(index); - return _array[index]; + GuardDispose(); + return ArrayPoolListCore.Get(_array, index, _count); } set { - GuardIndex(index); - _array[index] = value; + GuardDispose(); + ArrayPoolListCore.Set(_array, index, _count, value); } } @@ -311,56 +228,18 @@ public T this[int index] set { ThrowHelper.IfNullAndNullsAreIllegalThenThrow(value, nameof(value)); - this[index] = (T)value!; } } - private bool GuardIndex(int index, bool shouldThrow = true, bool allowEqualToCount = false) - { - GuardDispose(); - int count = Count; - if ((uint)index > (uint)count || (!allowEqualToCount && index == count)) - { - if (shouldThrow) - { - ThrowArgumentOutOfRangeException(); - } - return false; - } - - return true; - - [DoesNotReturn] - static void ThrowArgumentOutOfRangeException() - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - } private static bool IsCompatibleObject(object? value) => value is T || value is null && default(T) is null; public static ArrayPoolList Empty() => new(0); - - public void Dispose() { - // Noop for empty array as sometimes this is used as part of an empty shared response. - if (_capacity == 0) return; - - if (!_disposed) - { - _disposed = true; - T[]? array = _array; - _array = null!; - if (array is not null) - { - ClearToCount(array); - _arrayPool.Return(array); - } - Count = 0; - } + ArrayPoolListCore.Dispose(_arrayPool, ref _array, ref _count, ref _capacity, ref _disposed); #if DEBUG GC.SuppressFinalize(this); @@ -379,9 +258,29 @@ public void Dispose() } #endif - public Span AsSpan() => _array.AsSpan(0, Count); - public Memory AsMemory() => new(_array, 0, Count); - public ReadOnlyMemory AsReadOnlyMemory() => new(_array, 0, Count); - public T[] UnsafeGetInternalArray() => _array; - public void Reverse() => AsSpan().Reverse(); + public Span AsSpan() + { + GuardDispose(); + return _array.AsSpan(0, _count); + } + public Memory AsMemory() + { + GuardDispose(); + return new(_array, 0, _count); + } + public ReadOnlyMemory AsReadOnlyMemory() + { + GuardDispose(); + return new(_array, 0, _count); + } + public T[] UnsafeGetInternalArray() + { + GuardDispose(); + return _array; + } + public void Reverse() + { + GuardDispose(); + ArrayPoolListCore.Reverse(_array, _count); + } } diff --git a/src/Nethermind/Nethermind.Core/Collections/ArrayPoolListRef.cs b/src/Nethermind/Nethermind.Core/Collections/ArrayPoolListRef.cs new file mode 100644 index 000000000000..88253b5beb74 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Collections/ArrayPoolListRef.cs @@ -0,0 +1,97 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Nethermind.Core.Collections; + +public ref struct ArrayPoolListRef +{ + private T[] _array; + private int _capacity; + private int _count; + + public ArrayPoolListRef(int capacity, IEnumerable items) : this(capacity) => AddRange(items); + public ArrayPoolListRef(int capacity, params ReadOnlySpan items) : this(capacity) => AddRange(items); + public ArrayPoolListRef(ReadOnlySpan span) : this(span.Length) => AddRange(span); + + public ArrayPoolListRef(int capacity, int startingCount = 0) + { + if (capacity != 0) + { + _array = ArrayPool.Shared.Rent(capacity); + _array.AsSpan(0, startingCount).Clear(); + } + else + { + _array = []; + } + + _capacity = _array.Length; + _count = startingCount; + } + + public readonly int Count => _count; + public readonly int Capacity => _capacity; + public void Add(T item) => ArrayPoolListCore.Add(ArrayPool.Shared, ref _array, ref _capacity, ref _count, item); + public void AddRange(params T[] items) => AddRange(items.AsSpan()); + public void AddRange(params ReadOnlySpan items) => ArrayPoolListCore.AddRange(ArrayPool.Shared, ref _array, ref _capacity, ref _count, items); + + public void AddRange(params IEnumerable items) + { + switch (items) + { + case T[] array: + AddRange((ReadOnlySpan)array); + break; + case List listItems: + AddRange(CollectionsMarshal.AsSpan(listItems)); + break; + default: + { + foreach (T item in items) + { + Add(item); + } + + break; + } + } + } + + public void Insert(int index, T item) => ArrayPoolListCore.Insert(ArrayPool.Shared, ref _array, ref _capacity, ref _count, index, item); + public bool Remove(T item) => ArrayPoolListCore.Remove(_array, ref _count, item); + public T? RemoveLast() => ArrayPoolListCore.RemoveLast(_array, ref _count); + public void RemoveAt(int index) => ArrayPoolListCore.RemoveAt(_array, ref _count, index, shouldThrow: true); + public void Clear() => ArrayPoolListCore.Clear(_array, ref _count); + public void ReduceCount(int newCount) => ArrayPoolListCore.ReduceCount(ArrayPool.Shared, ref _array, ref _capacity, ref _count, newCount); + public void Truncate(int newLength) => ArrayPoolListCore.Truncate(newLength, _array, ref _count); + public readonly void Sort(Comparison comparison) => ArrayPoolListCore.Sort(_array, _count, comparison); + public readonly void Reverse() => ArrayPoolListCore.Reverse(_array, _count); + public readonly ref T GetRef(int index) => ref ArrayPoolListCore.GetRef(_array, index, _count); + public readonly Span AsSpan() => _array.AsSpan(0, _count); + public readonly Memory AsMemory() => new(_array, 0, _count); + public readonly ReadOnlyMemory AsReadOnlyMemory() => new(_array, 0, _count); + + public readonly T this[int index] + { + get => ArrayPoolListCore.Get(_array, index, _count); + set => ArrayPoolListCore.Set(_array, index, _count, value); + } + + public void Dispose() => ArrayPoolListCore.Dispose(ArrayPool.Shared, ref _array, ref _count, ref _capacity); + public readonly PooledArrayEnumerator GetEnumerator() => new(_array, _count); + public readonly bool Contains(T item) => ArrayPoolListCore.Contains(_array, item, _count); + public readonly int IndexOf(T item) => ArrayPoolListCore.IndexOf(_array, _count, item); + public readonly void CopyTo(T[] array, int arrayIndex) => ArrayPoolListCore.CopyTo(_array, _count, array, arrayIndex); + + public readonly ArrayPoolListRef Select(Func selector) + { + ArrayPoolListRef result = new(_count); + foreach (T item in AsSpan()) result.Add(selector(item)); + return result; + } + + public readonly T[] ToArray() => AsSpan().ToArray(); + public readonly T[] UnsafeGetInternalArray() => _array; +} diff --git a/src/Nethermind/Nethermind.Core/Collections/CollectionExtensions.cs b/src/Nethermind/Nethermind.Core/Collections/CollectionExtensions.cs index 4a5f5f476a39..596af6faa9cf 100644 --- a/src/Nethermind/Nethermind.Core/Collections/CollectionExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Collections/CollectionExtensions.cs @@ -16,24 +16,26 @@ public static class CollectionExtensions public static void AddRange(this ICollection list, IEnumerable items) { - if (items is T[] array) + switch (items) { - list.AddRange(array); - } - else if (items is IList listItems) - { - list.AddRange(listItems); - } - else if (items is IReadOnlyList readOnlyList) - { - list.AddRange(readOnlyList); - } - else - { - foreach (T item in items) - { - list.Add(item); - } + case T[] array: + list.AddRange(array); + break; + case IList listItems: + list.AddRange(listItems); + break; + case IReadOnlyList readOnlyList: + list.AddRange(readOnlyList); + break; + default: + { + foreach (T item in items) + { + list.Add(item); + } + + break; + } } } @@ -47,6 +49,14 @@ public static void AddRange(this ICollection list, IList items) } } + public static void AddOrUpdateRange(this IDictionary dict, IEnumerable> items) + { + foreach (KeyValuePair kv in items) + { + dict[kv.Key] = kv.Value; + } + } + [OverloadResolutionPriority(1)] public static void AddRange(this ICollection list, IReadOnlyList items) { diff --git a/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs b/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs index e036bc568d54..9fdf91723f45 100644 --- a/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs @@ -1,8 +1,11 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; +using Nethermind.Core.Resettables; namespace Nethermind.Core.Collections; @@ -13,4 +16,49 @@ public static void Increment(this Dictionary dictionary, TKey k ref int res = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out bool _); res++; } + + extension(Dictionary dictionary) where TKey : notnull + { + public ref TValue GetOrAdd(TKey key, Func factory, out bool exists) + { + ref TValue? existing = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out exists); + + if (!exists) + existing = factory(key); + + return ref existing!; + } + + public ref TValue GetOrAdd(TKey key, Func factory) => ref dictionary.GetOrAdd(key, factory, out _); + + } + + /// The dictionary whose values will be returned and cleared. + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary, which must implement . + extension(IDictionary dictionary) where TValue : class, IReturnable + { + /// + /// Returns all values in the dictionary to their pool by calling on each value, + /// then clears the dictionary. + /// + /// + /// Use this method when you need to both return pooled objects and clear the dictionary in one operation. + /// + public void ResetAndClear() + { + foreach (TValue value in dictionary.Values) + { + value.Return(); + } + dictionary.Clear(); + } + } + + extension(Dictionary.AlternateLookup dictionary) + where TKey : notnull where TAlternateKey : notnull, allows ref struct + { + public bool TryRemove(TAlternateKey key, [MaybeNullWhen(false)] out TValue value) => + dictionary.Remove(key, out _, out value); + } } diff --git a/src/Nethermind/Nethermind.Core/Collections/HashHelpers.cs b/src/Nethermind/Nethermind.Core/Collections/HashHelpers.cs deleted file mode 100644 index e998398d5e13..000000000000 --- a/src/Nethermind/Nethermind.Core/Collections/HashHelpers.cs +++ /dev/null @@ -1,132 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.Serialization; -using System.Threading; - -namespace Nethermind.Core.Collections -{ - /// - /// Adapted from .net source code. - /// - internal static class HashHelpers - { - public const uint HashCollisionThreshold = 100; - - // This is the maximum prime smaller than Array.MaxLength. - public const int MaxPrimeArrayLength = 0x7FFFFFC3; - - public const int HashPrime = 101; - - // Table of prime numbers to use as hash table sizes. - // A typical resize algorithm would pick the smallest prime number in this array - // that is larger than twice the previous capacity. - // Suppose our Hashtable currently has capacity x and enough elements are added - // such that a resize needs to occur. Resizing first computes 2x then finds the - // first prime in the table greater than 2x, i.e. if primes are ordered - // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. - // Doubling is important for preserving the asymptotic complexity of the - // hashtable operations such as add. Having a prime guarantees that double - // hashing does not lead to infinite loops. IE, your hash function will be - // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime. - // We prefer the low computation costs of higher prime numbers over the increased - // memory allocation of a fixed prime number i.e. when right sizing a HashSet. - private static readonly int[] s_primes = - { - 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, - 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, - 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, - 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, - 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 - }; - - public static bool IsPrime(int candidate) - { - if ((candidate & 1) != 0) - { - int limit = (int)Math.Sqrt(candidate); - for (int divisor = 3; divisor <= limit; divisor += 2) - { - if ((candidate % divisor) == 0) - return false; - } - return true; - } - return candidate == 2; - } - - public static int GetPrime(int min) - { - if (min < 0) - throw new ArgumentException("Hashtable's capacity overflowed and went negative. Check load factor, capacity and the current size of the table."); - - foreach (int prime in s_primes) - { - if (prime >= min) - return prime; - } - - // Outside of our predefined table. Compute the hard way. - for (int i = (min | 1); i < int.MaxValue; i += 2) - { - if (IsPrime(i) && ((i - 1) % HashPrime != 0)) - return i; - } - return min; - } - - // Returns size of hashtable to grow to. - public static int ExpandPrime(int oldSize) - { - int newSize = 2 * oldSize; - - // Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow. - // Note that this check works even when _items.Length overflowed thanks to the (uint) cast - if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) - { - Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength"); - return MaxPrimeArrayLength; - } - - return GetPrime(newSize); - } - - /// Returns approximate reciprocal of the divisor: ceil(2**64 / divisor). - /// This should only be used on 64-bit. - public static ulong GetFastModMultiplier(uint divisor) => - ulong.MaxValue / divisor + 1; - - /// Performs a mod operation using the multiplier pre-computed with . - /// This should only be used on 64-bit. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint FastMod(uint value, uint divisor, ulong multiplier) - { - // We use modified Daniel Lemire's fastmod algorithm (https://github.com/dotnet/runtime/pull/406), - // which allows to avoid the long multiplication if the divisor is less than 2**31. - Debug.Assert(divisor <= int.MaxValue); - - // This is equivalent of (uint)Math.BigMul(multiplier * value, divisor, out _). This version - // is faster than BigMul currently because we only need the high bits. - uint highbits = (uint)(((((multiplier * value) >> 32) + 1) * divisor) >> 32); - - Debug.Assert(highbits == value % divisor); - return highbits; - } - - private static ConditionalWeakTable? s_serializationInfoTable; - - public static ConditionalWeakTable SerializationInfoTable - { - get - { - if (s_serializationInfoTable is null) - Interlocked.CompareExchange(ref s_serializationInfoTable, new ConditionalWeakTable(), null); - - return s_serializationInfoTable; - } - } - } -} diff --git a/src/Nethermind/Nethermind.Core/Collections/IHash64bit.cs b/src/Nethermind/Nethermind.Core/Collections/IHash64bit.cs new file mode 100644 index 000000000000..746146576cf4 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Collections/IHash64bit.cs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Core.Collections; + +/// +/// Provides a 64-bit hash code for high-performance caching with reduced collision probability. +/// +/// +/// Types implementing this interface can be used with caches that require extended hash bits +/// for collision resistance (for example, Seqlock-based caches that use additional bits beyond +/// the standard hash code for bucket indexing and collision detection). +/// The 64-bit hash should have good distribution across all bits. +/// +public interface IHash64bit +{ + /// + /// Returns a 64-bit hash code for the current instance. + /// + /// A 64-bit hash code with good distribution across all bits. + long GetHashCode64(); + bool Equals(in TKey other); +} diff --git a/src/Nethermind/Nethermind.Core/Collections/IHashSetEnumerableCollection.cs b/src/Nethermind/Nethermind.Core/Collections/IHashSetEnumerableCollection.cs index 0eab213101bc..31044fc6720c 100644 --- a/src/Nethermind/Nethermind.Core/Collections/IHashSetEnumerableCollection.cs +++ b/src/Nethermind/Nethermind.Core/Collections/IHashSetEnumerableCollection.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; namespace Nethermind.Core.Collections; diff --git a/src/Nethermind/Nethermind.Core/Collections/InsertionBehavior.cs b/src/Nethermind/Nethermind.Core/Collections/InsertionBehavior.cs deleted file mode 100644 index 90f4431ec102..000000000000 --- a/src/Nethermind/Nethermind.Core/Collections/InsertionBehavior.cs +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -internal enum InsertionBehavior : byte -{ - None, - OverwriteExisting, - ThrowOnExisting, -} diff --git a/src/Nethermind/Nethermind.Core/Collections/LinkedHashSet.cs b/src/Nethermind/Nethermind.Core/Collections/LinkedHashSet.cs index 85930124744a..047d96412e18 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/SeqlockCache.cs b/src/Nethermind/Nethermind.Core/Collections/SeqlockCache.cs new file mode 100644 index 000000000000..6ce764fa46b6 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Collections/SeqlockCache.cs @@ -0,0 +1,400 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.X86; +using System.Threading; + +namespace Nethermind.Core.Collections; + +/// +/// A high-performance 2-way skew-associative cache using a seqlock-style header per entry. +/// +/// Design goals: +/// - Lock-free reads (seqlock pattern) - readers never take locks. +/// - Best-effort writes - writers skip on contention. +/// - O(1) logical Clear() via a global epoch (no per-entry zeroing). +/// - 2-way skew-associative: each way uses independent hash bits for set indexing, +/// breaking correlation between ways ("power of two choices"). Keys that collide +/// in way 0 scatter to different sets in way 1, virtually eliminating conflict misses. +/// +/// Hash bit partitioning (64-bit hash): +/// Bits 0-13: way 0 set index (14 bits) +/// Bits 14-41: hash signature stored in header (28 bits) +/// Bits 42-55: way 1 set index (14 bits, independent from way 0) +/// +/// Header layout (64-bit): +/// [Lock:1][Epoch:26][Hash:28][Seq:8][Occ:1] +/// - Lock (bit 63): set during writes - readers retry/miss +/// - Epoch (bits 37-62): global epoch tag - changes on Clear() +/// - Hash (bits 9-36): per-bucket hash signature (28 bits) +/// - Seq (bits 1- 8): per-entry sequence counter (8 bits) - increments on every successful write +/// - Occ (bit 0): occupied flag - set when slot contains valid data (value may still be null) +/// +/// Array layout: [way0_set0..way0_set16383, way1_set0..way1_set16383] (split, not interleaved). +/// +/// The key type (struct implementing IHash64bit) +/// The value type (reference type, nullable allowed) +public sealed class SeqlockCache + where TKey : struct, IHash64bit + where TValue : class? +{ + /// + /// Number of sets. Must be a power of 2 for mask operations. + /// 16384 sets × 2 ways = 32768 total entries. + /// + private const int Sets = 1 << 14; // 16384 + private const int SetMask = Sets - 1; + + // Header bit layout: + // [Lock:1][Epoch:26][Hash:28][Seq:8][Occ:1] + + private const long LockMarker = unchecked((long)0x8000_0000_0000_0000); // bit 63 + + private const int EpochShift = 37; + private const long EpochMask = 0x7FFF_FFE0_0000_0000; // bits 37-62 (26 bits) + + private const long HashMask = 0x0000_0001_FFFF_FE00; // bits 9-36 (28 bits) + + private const long SeqMask = 0x0000_0000_0000_01FE; // bits 1-8 (8 bits) + private const long SeqInc = 0x0000_0000_0000_0002; // +1 in seq field + + private const long OccupiedBit = 1L; // bit 0 + + // Mask of all "identity" bits for an entry, excluding Lock and Seq. + private const long TagMask = EpochMask | HashMask | OccupiedBit; + + // Mask for checking if an entry is live in the current epoch. + private const long EpochOccMask = EpochMask | OccupiedBit; + + // With 14-bit set index (bits 0-13) for way 0, hash signature needs bits 14+. + // HashShift=5 maps header bits 9-36 to original bits 14-41, avoiding overlap with both ways. + private const int HashShift = 5; + + // Way 1 uses bits 42-55 of the original hash (completely independent from way 0's bits 0-13). + private const int Way1Shift = 42; + + /// + /// Array of entries: [way0_set0..way0_setN, way1_set0..way1_setN]. + /// Split layout ensures each way is a contiguous block for better prefetch behavior. + /// + private readonly Entry[] _entries; + + /// + /// Current epoch counter (unshifted, informational / debugging). + /// + private long _epoch; + + /// + /// Pre-shifted epoch tag: (_epoch << EpochShift) & EpochMask. + /// Readers use this directly to avoid shift/mask in the hot path. + /// + private long _shiftedEpoch; + + public SeqlockCache() + { + _entries = new Entry[Sets << 1]; // Sets * 2 + _epoch = 0; + _shiftedEpoch = 0; + } + + /// + /// Tries to get a value from the cache using a seqlock pattern (lock-free reads). + /// Checks both ways of the target set for the key. + /// + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe bool TryGetValue(in TKey key, out TValue? value) + { + long hashCode = key.GetHashCode64(); + int idx0 = (int)hashCode & SetMask; + int idx1 = Sets + ((int)(hashCode >> Way1Shift) & SetMask); + + long epochTag = Volatile.Read(ref _shiftedEpoch); + long hashPart = (hashCode >> HashShift) & HashMask; + long expectedTag = epochTag | hashPart | OccupiedBit; + + ref Entry entries = ref MemoryMarshal.GetArrayDataReference(_entries); + + // Prefetch way 1 while we check way 0 — hides L2/L3 latency for skew layout. + if (Sse.IsSupported) + { + Sse.PrefetchNonTemporal(Unsafe.AsPointer(ref Unsafe.Add(ref entries, idx1))); + } + + // === Way 0 === + ref Entry e0 = ref Unsafe.Add(ref entries, idx0); + long h1 = Volatile.Read(ref e0.HashEpochSeqLock); + + if ((h1 & (TagMask | LockMarker)) == expectedTag) + { + ref readonly TKey storedKey = ref e0.Key; + TValue? storedValue = e0.Value; + + long h2 = Volatile.Read(ref e0.HashEpochSeqLock); + if (h1 == h2 && storedKey.Equals(in key)) + { + value = storedValue; + return true; + } + } + + // === Way 1 === + ref Entry e1 = ref Unsafe.Add(ref entries, idx1); + long w1 = Volatile.Read(ref e1.HashEpochSeqLock); + + if ((w1 & (TagMask | LockMarker)) == expectedTag) + { + ref readonly TKey storedKey = ref e1.Key; + TValue? storedValue = e1.Value; + + long w2 = Volatile.Read(ref e1.HashEpochSeqLock); + if (w1 == w2 && storedKey.Equals(in key)) + { + value = storedValue; + return true; + } + } + + value = default; + return false; + } + + /// + /// Delegate-based factory that avoids copying large keys (passes by in). + /// Prefer this over Func<TKey, TValue?> when TKey is big (eg 48 bytes). + /// + public delegate TValue? ValueFactory(in TKey key); + + /// + /// Gets a value from the cache, or adds it using the factory if not present. + /// + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TValue? GetOrAdd(in TKey key, ValueFactory valueFactory) + { + long hashCode = key.GetHashCode64(); + int idx0 = (int)hashCode & SetMask; + int idx1 = Sets + ((int)(hashCode >> Way1Shift) & SetMask); + long hashPart = (hashCode >> HashShift) & HashMask; + + if (TryGetValueCore(in key, idx0, idx1, hashPart, out TValue? value)) + { + return value; + } + + return GetOrAddMiss(in key, valueFactory, idx0, idx1, hashPart); + } + + /// + /// Cold path for GetOrAdd: invokes factory and stores the result. + /// Kept out-of-line so the hot path (cache hit) compiles to a lean method body + /// with minimal register saves and stack frame. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private TValue? GetOrAddMiss(in TKey key, ValueFactory valueFactory, int idx0, int idx1, long hashPart) + { + TValue? value = valueFactory(in key); + SetCore(in key, value, idx0, idx1, hashPart); + return value; + } + + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe bool TryGetValueCore(in TKey key, int idx0, int idx1, long hashPart, out TValue? value) + { + long epochTag = Volatile.Read(ref _shiftedEpoch); + long expectedTag = epochTag | hashPart | OccupiedBit; + + ref Entry entries = ref MemoryMarshal.GetArrayDataReference(_entries); + + if (Sse.IsSupported) + { + Sse.PrefetchNonTemporal(Unsafe.AsPointer(ref Unsafe.Add(ref entries, idx1))); + } + + // Way 0 + ref Entry e0 = ref Unsafe.Add(ref entries, idx0); + long h1 = Volatile.Read(ref e0.HashEpochSeqLock); + + if ((h1 & (TagMask | LockMarker)) == expectedTag) + { + ref readonly TKey storedKey = ref e0.Key; + TValue? storedValue = e0.Value; + + long h2 = Volatile.Read(ref e0.HashEpochSeqLock); + if (h1 == h2 && storedKey.Equals(in key)) + { + value = storedValue; + return true; + } + } + + // Way 1 + ref Entry e1 = ref Unsafe.Add(ref entries, idx1); + long w1 = Volatile.Read(ref e1.HashEpochSeqLock); + + if ((w1 & (TagMask | LockMarker)) == expectedTag) + { + ref readonly TKey storedKey = ref e1.Key; + TValue? storedValue = e1.Value; + + long w2 = Volatile.Read(ref e1.HashEpochSeqLock); + if (w1 == w2 && storedKey.Equals(in key)) + { + value = storedValue; + return true; + } + } + + value = default; + return false; + } + + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetCore(in TKey key, TValue? value, int idx0, int idx1, long hashPart) + { + long epochTag = Volatile.Read(ref _shiftedEpoch); + long tagToStore = epochTag | hashPart | OccupiedBit; + long epochOccTag = epochTag | OccupiedBit; + + ref Entry entries = ref MemoryMarshal.GetArrayDataReference(_entries); + ref Entry e0 = ref Unsafe.Add(ref entries, idx0); + + long h0 = Volatile.Read(ref e0.HashEpochSeqLock); + + // === Way 0: check for matching key === + if (h0 >= 0 && (h0 & TagMask) == tagToStore) + { + ref readonly TKey k0 = ref e0.Key; + TValue? v0 = e0.Value; + + long h0_2 = Volatile.Read(ref e0.HashEpochSeqLock); + if (h0 == h0_2 && k0.Equals(in key)) + { + if (ReferenceEquals(v0, value)) return; // fast-path: same key+value, no-op + WriteEntry(ref e0, h0_2, in key, value, tagToStore); + return; + } + h0 = h0_2; + } + + // === Way 1: check for matching key === + ref Entry e1 = ref Unsafe.Add(ref entries, idx1); + long h1 = Volatile.Read(ref e1.HashEpochSeqLock); + + if (h1 >= 0 && (h1 & TagMask) == tagToStore) + { + ref readonly TKey k1 = ref e1.Key; + TValue? v1 = e1.Value; + + long h1_2 = Volatile.Read(ref e1.HashEpochSeqLock); + if (h1 == h1_2 && k1.Equals(in key)) + { + if (ReferenceEquals(v1, value)) return; // fast-path: same key+value, no-op + WriteEntry(ref e1, h1_2, in key, value, tagToStore); + return; + } + h1 = h1_2; + } + + // === Key not in either way. Evict into an available slot. === + // Priority: stale/empty unlocked > live (alternating by hash bit) > any unlocked > skip. + // The decision tree selects which way to evict into, then issues a single WriteEntry call. + bool h0Live = h0 >= 0 && (h0 & EpochOccMask) == epochOccTag; + bool h1Live = h1 >= 0 && (h1 & EpochOccMask) == epochOccTag; + + bool pick0; + if (!h0Live && h0 >= 0) pick0 = true; + else if (!h1Live && h1 >= 0) pick0 = false; + else if (h0Live && h1Live) pick0 = (hashPart & (1L << 9)) != 0; + else if (h0 >= 0) pick0 = true; + else if (h1 >= 0) pick0 = false; + else return; // both locked, skip + + WriteEntry( + ref pick0 ? ref e0 : ref e1, + pick0 ? h0 : h1, + in key, value, tagToStore); + } + + /// + /// Sets a key-value pair in the cache. + /// Checks both ways of the target set for an existing key match before evicting. + /// + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Set(in TKey key, TValue? value) + { + long hashCode = key.GetHashCode64(); + int idx0 = (int)hashCode & SetMask; + int idx1 = Sets + ((int)(hashCode >> Way1Shift) & SetMask); + long hashPart = (hashCode >> HashShift) & HashMask; + + SetCore(in key, value, idx0, idx1, hashPart); + } + + /// + /// Attempts a CAS-guarded write to a single entry. + /// Kept out-of-line: the CAS atomic dominates latency, so call overhead is invisible, + /// while de-duplication reclaims ~350 bytes of inlined copies across SetCore call sites. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private static void WriteEntry(ref Entry entry, long existing, in TKey key, TValue? value, long tagToStore) + { + if (existing < 0) return; // locked + + long newSeq = ((existing & SeqMask) + SeqInc) & SeqMask; + long lockedHeader = tagToStore | newSeq | LockMarker; + + if (Interlocked.CompareExchange(ref entry.HashEpochSeqLock, lockedHeader, existing) != existing) + { + return; + } + + entry.Key = key; + entry.Value = value; + + Volatile.Write(ref entry.HashEpochSeqLock, tagToStore | newSeq); + } + + /// + /// Clears all cached entries by incrementing the global epoch tag (O(1)). + /// Entries with stale epochs are treated as empty on subsequent lookups. + /// + public void Clear() + { + long oldShifted = Volatile.Read(ref _shiftedEpoch); + + while (true) + { + long oldEpoch = (oldShifted & EpochMask) >> EpochShift; + long newEpoch = oldEpoch + 1; + long newShifted = (newEpoch << EpochShift) & EpochMask; + + long prev = Interlocked.CompareExchange(ref _shiftedEpoch, newShifted, oldShifted); + if (prev == oldShifted) + { + Volatile.Write(ref _epoch, newEpoch); + return; + } + + oldShifted = prev; + } + } + + /// + /// Cache entry struct. + /// Header is a single 64-bit field to keep the seqlock control word in one atomic unit. + /// + [StructLayout(LayoutKind.Sequential)] + private struct Entry + { + public long HashEpochSeqLock; // [Lock|Epoch|Hash|Seq|Occ] + public TKey Key; + public TValue? Value; + } +} diff --git a/src/Nethermind/Nethermind.Core/Collections/SortedRealList.cs b/src/Nethermind/Nethermind.Core/Collections/SortedRealList.cs deleted file mode 100644 index 02d66af6e651..000000000000 --- a/src/Nethermind/Nethermind.Core/Collections/SortedRealList.cs +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; - -namespace Nethermind.Core.Collections -{ - public class SortedRealList : SortedList, IList> where TKey : notnull - { - // Constructs a new sorted list. The sorted list is initially empty and has - // a capacity of zero. Upon adding the first element to the sorted list the - // capacity is increased to DefaultCapacity, and then increased in multiples of two as - // required. The elements of the sorted list are ordered according to the - // IComparable interface, which must be implemented by the keys of - // all entries added to the sorted list. - public SortedRealList() { } - - // Constructs a new sorted list. The sorted list is initially empty and has - // a capacity of zero. Upon adding the first element to the sorted list the - // capacity is increased to 16, and then increased in multiples of two as - // required. The elements of the sorted list are ordered according to the - // IComparable interface, which must be implemented by the keys of - // all entries added to the sorted list. - // - public SortedRealList(int capacity) : base(capacity) { } - - // Constructs a new sorted list with a given IComparer - // implementation. The sorted list is initially empty and has a capacity of - // zero. Upon adding the first element to the sorted list the capacity is - // increased to 16, and then increased in multiples of two as required. The - // elements of the sorted list are ordered according to the given - // IComparer implementation. If comparer is null, the - // elements are compared to each other using the IComparable - // interface, which in that case must be implemented by the keys of all - // entries added to the sorted list. - // - public SortedRealList(IComparer? comparer) : base(comparer) - { - } - - // Constructs a new sorted dictionary with a given IComparer - // implementation and a given initial capacity. The sorted list is - // initially empty, but will have room for the given number of elements - // before any reallocations are required. The elements of the sorted list - // are ordered according to the given IComparer implementation. If - // comparer is null, the elements are compared to each other using - // the IComparable interface, which in that case must be implemented - // by the keys of all entries added to the sorted list. - // - public SortedRealList(int capacity, IComparer? comparer) : base(capacity, comparer) { } - - // Constructs a new sorted list containing a copy of the entries in the - // given dictionary. The elements of the sorted list are ordered according - // to the IComparable interface, which must be implemented by the - // keys of all entries in the given dictionary as well as keys - // subsequently added to the sorted list. - // - public SortedRealList(IDictionary dictionary) : base(dictionary) { } - - // Constructs a new sorted list containing a copy of the entries in the - // given dictionary. The elements of the sorted list are ordered according - // to the given IComparer implementation. If comparer is - // null, the elements are compared to each other using the - // IComparable interface, which in that case must be implemented - // by the keys of all entries in the given dictionary as well as keys - // subsequently added to the sorted list. - // - public SortedRealList(IDictionary dictionary, IComparer? comparer) : base(dictionary, comparer) { } - - public int IndexOf(KeyValuePair item) => IndexOfKey(item.Key); - - public void Insert(int index, KeyValuePair item) => this.TryAdd(item.Key, item.Value); - - public KeyValuePair this[int index] - { - get => new(Keys[index], Values[index]); - set => this.TryAdd(value.Key, value.Value); - } - } -} diff --git a/src/Nethermind/Nethermind.Core/Collections/StackList.cs b/src/Nethermind/Nethermind.Core/Collections/StackList.cs index 2170d94de1b6..1537fc69d04e 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/KeyedMapperRegistrationSource.cs b/src/Nethermind/Nethermind.Core/Container/KeyedMapperRegistrationSource.cs index 17cb9c3f5cfe..049eb15b3775 100644 --- a/src/Nethermind/Nethermind.Core/Container/KeyedMapperRegistrationSource.cs +++ b/src/Nethermind/Nethermind.Core/Container/KeyedMapperRegistrationSource.cs @@ -16,10 +16,16 @@ namespace Nethermind.Core.Container; /// Utility that map between two type that act on keyed service. /// /// +/// Set to true so that container take ownership of the returned object. /// /// -public class KeyedMapperRegistrationSource(Func mapper, bool isNewObject) : IRegistrationSource where TFrom : notnull +public class KeyedMapperRegistrationSource(Func mapper, bool isNewObject) : IRegistrationSource where TFrom : notnull { + + public KeyedMapperRegistrationSource(Func mapper, bool isNewObject) : this((_, from) => mapper(from), isNewObject) + { + } + public IEnumerable RegistrationsFor(Service service, Func> registrationAccessor) { if (service is not KeyedService keyedService || keyedService.ServiceType != typeof(TTo)) @@ -33,7 +39,7 @@ public IEnumerable RegistrationsFor(Service service, Fun new DelegateActivator(keyedService.ServiceType, (c, p) => { TFrom from = c.ResolveKeyed(keyedService.ServiceKey); - return mapper(from)!; + return mapper(keyedService.ServiceKey, from)!; }), new RootScopeLifetime(), InstanceSharing.Shared, diff --git a/src/Nethermind/Nethermind.Core/Container/OrderedComponentsContainerBuilderExtensions.cs b/src/Nethermind/Nethermind.Core/Container/OrderedComponentsContainerBuilderExtensions.cs index be286cedbe80..e4f64bfb89cf 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 c7ce4ab861d8..224300328fa8 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. @@ -509,6 +522,11 @@ public static ContainerBuilder AddKeyedAdapter(this ContainerBuilder return builder.AddSource(new KeyedMapperRegistrationSource(mapper, false)); } + public static ContainerBuilder AddKeyedAdapter(this ContainerBuilder builder, Func mapper) where TFrom : notnull + { + return builder.AddSource(new KeyedMapperRegistrationSource(mapper, false)); + } + public static ContainerBuilder AddKeyedAdapterWithNewService(this ContainerBuilder builder, Func mapper) where TFrom : notnull { return builder.AddSource(new KeyedMapperRegistrationSource(mapper, true)); diff --git a/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs b/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs index 6a1314f80e2f..4b8566b3e4ba 100644 --- a/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs +++ b/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs @@ -7,16 +7,13 @@ using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Text.Json.Serialization; - using Nethermind.Core.Extensions; using Nethermind.Int256; -using Nethermind.Serialization.Json; namespace Nethermind.Core.Crypto { [DebuggerStepThrough] [DebuggerDisplay("{ToString()}")] - [JsonConverter(typeof(ValueHash256Converter))] public readonly struct ValueHash256 : IEquatable, IComparable, IEquatable { // Ensure that hashes are different for every run of the node and every node, so if are any hash collisions on @@ -29,7 +26,9 @@ namespace Nethermind.Core.Crypto public const int MemorySize = 32; public static int Length => MemorySize; + [JsonIgnore] public Span BytesAsSpan => MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in _bytes), 1)); + [JsonIgnore] public ReadOnlySpan Bytes => MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in _bytes), 1)); public static implicit operator ValueHash256?(Hash256? keccak) => keccak?.ValueHash256; @@ -117,7 +116,6 @@ public readonly struct Hash256AsKey(Hash256 key) : IEquatable, ICo public int CompareTo(Hash256AsKey other) => _key.CompareTo(other._key); } - [JsonConverter(typeof(Hash256Converter))] [DebuggerStepThrough] public sealed class Hash256 : IEquatable, IComparable { @@ -134,6 +132,7 @@ public sealed class Hash256 : IEquatable, IComparable public ref readonly ValueHash256 ValueHash256 => ref _hash256; + [JsonIgnore] public Span Bytes => MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in _hash256), 1)); public Hash256(string hexString) diff --git a/src/Nethermind/Nethermind.Core/Crypto/Hash256Converter.cs b/src/Nethermind/Nethermind.Core/Crypto/Hash256Converter.cs deleted file mode 100644 index ed0de28c7ea8..000000000000 --- a/src/Nethermind/Nethermind.Core/Crypto/Hash256Converter.cs +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Text.Json.Serialization; -using System.Text.Json; -using Nethermind.Core.Crypto; - -namespace Nethermind.Serialization.Json; - -public class Hash256Converter : JsonConverter -{ - public override Hash256? Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) - { - byte[]? bytes = ByteArrayConverter.Convert(ref reader); - return bytes is null ? null : new Hash256(bytes); - } - - public override void Write( - Utf8JsonWriter writer, - Hash256 keccak, - JsonSerializerOptions options) - { - ByteArrayConverter.Convert(writer, keccak.Bytes, skipLeadingZeros: false); - } - - // Methods needed to ser/de dictionary keys - public override Hash256 ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - byte[]? bytes = ByteArrayConverter.Convert(ref reader); - return bytes is null ? null! : new Hash256(bytes); - } - - public override void WriteAsPropertyName(Utf8JsonWriter writer, Hash256 value, JsonSerializerOptions options) - { - writer.WritePropertyName(value.ToString()); - } -} diff --git a/src/Nethermind/Nethermind.Core/Crypto/KeccakCache.cs b/src/Nethermind/Nethermind.Core/Crypto/KeccakCache.cs index 8574ebd169f9..b9168719c049 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,11 +16,11 @@ 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. +/// Uses seqlock pattern for lock-free reads: read sequence, speculatively read data, verify sequence unchanged. +/// Requires a single CAS to lock on writes and to unlock. /// On a lock failure, it just moves on with execution. -/// When a potential hit happens, the value of the result and the value of the key are copied on the stack to release the lock ASAP. /// public static unsafe class KeccakCache { @@ -31,10 +32,13 @@ public static unsafe class KeccakCache private const int BucketMask = 0x0001_FFFF; private const uint HashMask = unchecked((uint)~BucketMask); private const uint LockMarker = 0x0000_8000; + private const uint VersionMask = 0x0000_7F80; // Bits 7-14: 8-bit version counter (0-255) + private const uint VersionIncrement = 0x0000_0080; // Bit 7: increment step for version + private const uint HashLengthMask = HashMask | 0x7F; // Hash (bits 17-31) + Length (bits 0-6) 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,55 +73,81 @@ 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; - // Compare with volatile read and then try to lock with CAS - if (Volatile.Read(ref e.Combined) == combined && - Interlocked.CompareExchange(ref e.Combined, combined | LockMarker, combined) == combined) - { - // Combined is equal to existing, meaning that it was not locked, and both the length and the hash were equal. - - // Take local copy of the payload and hash, to release the lock as soon as possible and make a key comparison without holding it. - // Local copy of 8+16+64 payload bytes. - Payload copy = e.Value; - // Copy Keccak256 directly to the local return variable, since we will overwrite if no match anyway. - keccak256 = e.Keccak256; - - // Release the lock, by setting back to the combined value. - Volatile.Write(ref e.Combined, combined); + // Seqlock pattern: read sequence, speculatively read data, verify sequence unchanged. + // This is lock-free for reads - no CAS required unless we need to write. + uint seq1 = Volatile.Read(ref e.Combined); - // Lengths are equal, the input length can be used without any additional operation. + // Early exit: lock held or hash/length mismatch (ignoring version bit). + if ((seq1 & LockMarker) == 0 && (seq1 & HashLengthMask) == combined) + { + // Fast path for 32-byte input - only copy the 32 bytes we need, not full 92-byte Payload if (input.Length == InputLengthOfKeccak) { - // Hashing UInt256 or Hash256 which is Vector256 - if (Unsafe.As>(ref copy.Aligned32) == - Unsafe.As>(ref MemoryMarshal.GetReference(input))) + // Speculative reads - copy only Vector256 at Aligned32 and the keccak result + Vector256 copyVec = Unsafe.As>(ref e.Value.Aligned32); + ValueHash256 cachedKeccak = e.Keccak256; + + // ARM memory barrier: ensure speculative reads complete before seq2. + // On x86/x64 (TSO), loads are never reordered - JIT eliminates this entirely. + if (!Sse.IsSupported) + Thread.MemoryBarrier(); + + // Re-read sequence - if changed, a write occurred during our reads + if (seq1 == Volatile.Read(ref e.Combined) && + copyVec == Unsafe.As>(ref MemoryMarshal.GetReference(input))) { - // Current keccak256 is correct hash. + keccak256 = cachedKeccak; return; } } else if (input.Length == InputLengthOfAddress) { - // Hashing Address - ref byte bytes1 = ref MemoryMarshal.GetReference(input); - // 20 bytes which is uint+Vector128 - if (Unsafe.As(ref copy.Start) == Unsafe.As(ref bytes1) && - Unsafe.As>(ref copy.Aligned32) == - Unsafe.As>(ref Unsafe.Add(ref bytes1, sizeof(uint)))) + // Speculative reads for 20-byte address - copy uint + Vector128 + uint copyStart = Unsafe.As(ref e.Value.Start); + Vector128 copyAligned = Unsafe.As>(ref e.Value.Aligned32); + ValueHash256 cachedKeccak = e.Keccak256; + + // ARM memory barrier (see above) + if (!Sse.IsSupported) + Thread.MemoryBarrier(); + + ref byte inputRef = ref MemoryMarshal.GetReference(input); + // Re-read sequence and compare + if (seq1 == Volatile.Read(ref e.Combined) && + copyStart == Unsafe.As(ref inputRef) && + copyAligned == Unsafe.As>(ref Unsafe.Add(ref inputRef, sizeof(uint)))) { - // Current keccak256 is correct hash. + keccak256 = cachedKeccak; return; } } - else if (MemoryMarshal.CreateReadOnlySpan(ref copy.Start, input.Length).SequenceEqual(input)) + else { - // Non 32 byte or 20 byte input; call SequenceEqual. - // Current keccak256 is correct hash. - return; + // Uncommon path: copy full Payload for other lengths + Payload copy = e.Value; + ValueHash256 cachedKeccak = e.Keccak256; + + // ARM memory barrier (see above) + if (!Sse.IsSupported) + Thread.MemoryBarrier(); + + if (seq1 == Volatile.Read(ref e.Combined) && + MemoryMarshal.CreateReadOnlySpan(ref copy.Start, input.Length).SequenceEqual(input)) + { + keccak256 = cachedKeccak; + return; + } } } @@ -125,8 +155,13 @@ public static void ComputeTo(ReadOnlySpan input, out ValueHash256 keccak25 uint existing = Volatile.Read(ref e.Combined); + // Increment 8-bit version counter (wraps after 256) to detect ABA in seqlock readers. + // 256 writes needed to wrap = ~8-13μs, far exceeding ~50ns read window. + uint newVersion = ((existing & VersionMask) + VersionIncrement) & VersionMask; + uint toStore = combined | newVersion; + // Try to set to the combined locked state, if not already locked. - if ((existing & LockMarker) == 0 && Interlocked.CompareExchange(ref e.Combined, combined | LockMarker, existing) == existing) + if ((existing & LockMarker) == 0 && Interlocked.CompareExchange(ref e.Combined, toStore | LockMarker, existing) == existing) { e.Keccak256 = keccak256; @@ -152,8 +187,8 @@ public static void ComputeTo(ReadOnlySpan input, out ValueHash256 keccak25 input.CopyTo(MemoryMarshal.CreateSpan(ref e.Value.Start, input.Length)); } - // Release the lock, by setting back to the combined value. - Volatile.Write(ref e.Combined, combined); + // Release the lock, by setting to combined with new version (without lock). + Volatile.Write(ref e.Combined, toStore); } return; @@ -174,10 +209,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 9dfd33acf43c..35673cec481c 100644 --- a/src/Nethermind/Nethermind.Core/Crypto/KeccakHash.cs +++ b/src/Nethermind/Nethermind.Core/Crypto/KeccakHash.cs @@ -342,7 +342,7 @@ public static uint[] ComputeUIntsToUint(Span input, int size) public static uint[] ComputeBytesToUint(ReadOnlySpan input, int size) { uint[] output = new uint[size / sizeof(uint)]; - ComputeHash(input, MemoryMarshal.Cast(output)); + ComputeHash(input, MemoryMarshal.Cast(output.AsSpan())); return output; } @@ -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 c76225ffdc8c..d6bcbc9cb648 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/Crypto/Root.cs b/src/Nethermind/Nethermind.Core/Crypto/Root.cs index d4622c9e9635..5936e4ef5264 100644 --- a/src/Nethermind/Nethermind.Core/Crypto/Root.cs +++ b/src/Nethermind/Nethermind.Core/Crypto/Root.cs @@ -3,7 +3,6 @@ using System; using System.Buffers.Binary; -using System.Linq; using System.Runtime.InteropServices; using Nethermind.Int256; using Nethermind.Core.Extensions; diff --git a/src/Nethermind/Nethermind.Core/Crypto/Signature.cs b/src/Nethermind/Nethermind.Core/Crypto/Signature.cs index 35834c23904b..61f152101c09 100644 --- a/src/Nethermind/Nethermind.Core/Crypto/Signature.cs +++ b/src/Nethermind/Nethermind.Core/Crypto/Signature.cs @@ -5,6 +5,7 @@ using System.Buffers; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using System.Text.Json.Serialization; using Nethermind.Core.Attributes; using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -58,6 +59,7 @@ public Signature(string hexString) : this(Core.Extensions.Bytes.FromHexString(hexString)) { } + [JsonIgnore] public Span Bytes => MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref _signature, 1)); public override Memory Memory => CreateMemory(64); @@ -70,8 +72,10 @@ public Signature(string hexString) public static byte GetRecoveryId(ulong v) => v <= VOffset + 1 ? (byte)(v - VOffset) : (byte)(1 - v % 2); public Memory R => Memory.Slice(0, 32); + [JsonIgnore] public ReadOnlySpan RAsSpan => Bytes.Slice(0, 32); public Memory S => Memory.Slice(32, 32); + [JsonIgnore] public ReadOnlySpan SAsSpan => Bytes.Slice(32, 32); [Todo("Change signature to store 65 bytes and just slice it for normal Bytes.")] diff --git a/src/Nethermind/Nethermind.Core/Eip7702Constants.cs b/src/Nethermind/Nethermind.Core/Eip7702Constants.cs index e81d50106601..c57dd7a6f21d 100644 --- a/src/Nethermind/Nethermind.Core/Eip7702Constants.cs +++ b/src/Nethermind/Nethermind.Core/Eip7702Constants.cs @@ -5,6 +5,7 @@ using System; namespace Nethermind.Core; + public static class Eip7702Constants { private readonly static byte[] _delegationHeader = [0xef, 0x01, 0x00]; diff --git a/src/Nethermind/Nethermind.Core/ExecutionRequest/ExecutionRequestExtensions.cs b/src/Nethermind/Nethermind.Core/ExecutionRequest/ExecutionRequestExtensions.cs index 7b7350b8a04a..0d7de632258f 100644 --- a/src/Nethermind/Nethermind.Core/ExecutionRequest/ExecutionRequestExtensions.cs +++ b/src/Nethermind/Nethermind.Core/ExecutionRequest/ExecutionRequestExtensions.cs @@ -2,8 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; -using System.Linq; using System.Runtime.CompilerServices; using System.Security.Cryptography; using Nethermind.Core.Collections; @@ -38,7 +36,7 @@ public static Hash256 CalculateHashFromFlatEncodedRequests(byte[][]? flatEncoded throw new ArgumentException("Flat encoded requests must be an array"); } - using ArrayPoolList concatenatedHashes = new(Hash256.Size * MaxRequestsCount); + using ArrayPoolListRef concatenatedHashes = new(Hash256.Size * MaxRequestsCount); foreach (byte[] requests in flatEncodedRequests) { if (requests.Length <= 1) continue; @@ -57,7 +55,7 @@ public static ArrayPoolList GetFlatEncodedRequests( ExecutionRequest[] consolidationRequests ) { - var result = new ArrayPoolList(MaxRequestsCount); + ArrayPoolList result = new(MaxRequestsCount); if (depositRequests.Length > 0) { @@ -78,7 +76,7 @@ ExecutionRequest[] consolidationRequests static byte[] FlatEncodeRequests(ExecutionRequest[] requests, int bufferSize, byte type) { - using ArrayPoolList buffer = new(bufferSize + 1) { type }; + using ArrayPoolListRef buffer = new(bufferSize + 1, type); foreach (ExecutionRequest request in requests) { diff --git a/src/Nethermind/Nethermind.Core/Extensions/ByteArrayExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/ByteArrayExtensions.cs index 94c1c12f86a7..362afa9809ef 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 4527b52a94f6..573d658ef22a 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 128268d8243b..4fe4d044ee55 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs @@ -73,64 +73,17 @@ public override int Compare(byte[]? x, byte[]? y) if (x is null) { - return y is null ? 0 : 1; + return y is null ? 0 : -1; } - if (y is null) - { - return -1; - } - - if (x.Length == 0) - { - return y.Length == 0 ? 0 : 1; - } - - for (int i = 0; i < x.Length; i++) - { - if (y.Length <= i) - { - return -1; - } - - int result = x[i].CompareTo(y[i]); - if (result != 0) - { - return result; - } - } + if (y is null) return 1; - return y.Length > x.Length ? 1 : 0; + return x.SequenceCompareTo(y); } public static int Compare(ReadOnlySpan x, ReadOnlySpan y) { - if (Unsafe.AreSame(ref MemoryMarshal.GetReference(x), ref MemoryMarshal.GetReference(y)) && - x.Length == y.Length) - { - return 0; - } - - if (x.Length == 0) - { - return y.Length == 0 ? 0 : 1; - } - - for (int i = 0; i < x.Length; i++) - { - if (y.Length <= i) - { - return -1; - } - - int result = x[i].CompareTo(y[i]); - if (result != 0) - { - return result; - } - } - - return y.Length > x.Length ? 1 : 0; + return x.SequenceCompareTo(y); } } @@ -262,7 +215,7 @@ public static byte[] PadLeft(this ReadOnlySpan bytes, int length, byte pad return result; } - public static byte[] PadRight(this byte[] bytes, int length) + public static byte[] PadRight(this byte[] bytes, int length, byte padding = 0) { if (bytes.Length == length) { @@ -276,6 +229,12 @@ public static byte[] PadRight(this byte[] bytes, int length) byte[] result = new byte[length]; Buffer.BlockCopy(bytes, 0, result, 0, bytes.Length); + + if (padding != 0) + { + result.AsSpan(bytes.Length, length - bytes.Length).Fill(padding); + } + return result; } @@ -716,7 +675,7 @@ public static void OutputBytesToByteHex(this ReadOnlySpan bytes, Span(ref output) = (byte)(val >> 8); @@ -773,7 +732,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); @@ -1129,7 +1088,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/DateTimeExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/DateTimeExtensions.cs index d85f145cdfa4..36b87f71ab62 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/DateTimeExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/DateTimeExtensions.cs @@ -4,6 +4,7 @@ using System; namespace Nethermind.Core.Extensions; + public static class DateTimeExtensions { public static long ToUnixTimeSeconds(this DateTime date) diff --git a/src/Nethermind/Nethermind.Core/Extensions/DbDriveInfoProvider.cs b/src/Nethermind/Nethermind.Core/Extensions/DbDriveInfoProvider.cs index 9b4a072053a1..5c4faefae3d0 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/DbDriveInfoProvider.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/DbDriveInfoProvider.cs @@ -15,7 +15,8 @@ public static IDriveInfo[] GetDriveInfos(this IFileSystem fileSystem, string dbP static IDriveInfo? FindDriveForDirectory(IDriveInfo[] drives, DirectoryInfo dir) { string dPath = dir.LinkTarget ?? dir.FullName; - IEnumerable candidateDrives = drives.Where(drive => dPath.StartsWith(drive.RootDirectory.FullName)); + IEnumerable candidateDrives = + drives.Where(drive => dPath.StartsWith(drive.RootDirectory.FullName)); IDriveInfo? result = null; foreach (IDriveInfo driveInfo in candidateDrives) { @@ -30,35 +31,48 @@ public static IDriveInfo[] GetDriveInfos(this IFileSystem fileSystem, string dbP } DirectoryInfo topDir = new(dbPath); - if (topDir.Exists) + if (!topDir.Exists) { - HashSet driveInfos = new(); - //the following processing is to overcome specific behaviour on linux where creating DriveInfo for multiple paths on same logical drive - //gives instances with these paths (and not logical drive) - IDriveInfo[] allDrives = fileSystem.DriveInfo.GetDrives(); - IDriveInfo? topLevelDrive = FindDriveForDirectory(allDrives, topDir); - if (topLevelDrive is not null) - { - driveInfos.Add(topLevelDrive); - } + if (!CreateDirectorySilent(topDir)) + return []; + } + HashSet driveInfos = new(); + //the following processing is to overcome specific behaviour on linux where creating DriveInfo for multiple paths on same logical drive + //gives instances with these paths (and not logical drive) + IDriveInfo[] allDrives = fileSystem.DriveInfo.GetDrives(); + IDriveInfo? topLevelDrive = FindDriveForDirectory(allDrives, topDir); + if (topLevelDrive is not null) + { + driveInfos.Add(topLevelDrive); + } - foreach (DirectoryInfo di in topDir.EnumerateDirectories()) + foreach (DirectoryInfo di in topDir.EnumerateDirectories()) + { + //only want to handle symlinks - otherwise will be on same drive as parent + if (di.LinkTarget is not null) { - //only want to handle symlinks - otherwise will be on same drive as parent - if (di.LinkTarget is not null) + IDriveInfo? matchedDrive = FindDriveForDirectory(allDrives, topDir); + if (matchedDrive is not null) { - IDriveInfo? matchedDrive = FindDriveForDirectory(allDrives, topDir); - if (matchedDrive is not null) - { - driveInfos.Add(matchedDrive); - } + driveInfos.Add(matchedDrive); } } - - return driveInfos.ToArray(); } - return []; + return driveInfos.ToArray(); + } + + private static bool CreateDirectorySilent(DirectoryInfo directoryInfo) + { + try + { + directoryInfo.Create(); + } + catch (IOException) + { + // ignored + } + return directoryInfo.Exists; } } } diff --git a/src/Nethermind/Nethermind.Core/Extensions/EnumerableExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/EnumerableExtensions.cs index 4686955c4cda..0a0cf4faf049 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/EnumerableExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/EnumerableExtensions.cs @@ -16,6 +16,9 @@ public static ISet AsSet(this IEnumerable enumerable) => public static ArrayPoolList ToPooledList(this IEnumerable enumerable, int count) => new(count, enumerable); public static ArrayPoolList ToPooledList(this IReadOnlyCollection collection) => new(collection.Count, collection); public static ArrayPoolList ToPooledList(this ReadOnlySpan span) => new(span); + public static ArrayPoolListRef ToPooledListRef(this IEnumerable enumerable, int count) => new(count, enumerable); + public static ArrayPoolListRef ToPooledListRef(this IReadOnlyCollection collection) => new(collection.Count, collection); + public static ArrayPoolListRef ToPooledListRef(this ReadOnlySpan span) => new(span); public static IEnumerable Shuffle(this IEnumerable enumerable, Random rng, int maxSize = 100) { diff --git a/src/Nethermind/Nethermind.Core/Extensions/ExpressionExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/ExpressionExtensions.cs index 8a77c191d892..8265e5d83943 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/ExpressionExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/ExpressionExtensions.cs @@ -48,7 +48,37 @@ public static Action GetSetter(this Expression>( + assignExpr, + parameterT, + parameterTProperty + ); + + return setter.Compile(); + } + throw new NotSupportedException($"Member {typeof(T).Name}{memberExpression.Member.Name} is not a property."); } } diff --git a/src/Nethermind/Nethermind.Core/Extensions/Hash256Extensions.cs b/src/Nethermind/Nethermind.Core/Extensions/Hash256Extensions.cs new file mode 100644 index 000000000000..38ba8e57666b --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Extensions/Hash256Extensions.cs @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core.Crypto; + +namespace Nethermind.Core.Extensions; + +public static class Hash256Extensions +{ + public static ValueHash256 IncrementPath(this in ValueHash256 hash) + { + ValueHash256 result = hash; + Span bytes = result.BytesAsSpan; + + for (int i = 31; i >= 0; i--) + { + if (bytes[i] < 0xFF) + { + bytes[i]++; + return result; + } + bytes[i] = 0x00; + } + + // Overflow - return max (shouldn't happen in practice) + result = ValueKeccak.Zero; + result.BytesAsSpan.Fill(0xFF); + return result; + } + + public static ValueHash256 DecrementPath(this in ValueHash256 hash) + { + ValueHash256 result = hash; + Span bytes = result.BytesAsSpan; + + for (int i = 31; i >= 0; i--) + { + if (bytes[i] > 0) + { + bytes[i]--; + return result; + } + bytes[i] = 0xFF; + } + + // Underflow - return zero (shouldn't happen in practice) + return ValueKeccak.Zero; + } +} diff --git a/src/Nethermind/Nethermind.Core/Extensions/HexConverter.cs b/src/Nethermind/Nethermind.Core/Extensions/HexConverter.cs index 29e3bae674d5..c1289558fe76 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/HexConverter.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/HexConverter.cs @@ -256,42 +256,70 @@ public static bool TryDecodeFromUtf8(ReadOnlySpan hexString, Span re /// /// Loops in 2 byte chunks and decodes them into 1 byte chunks. + /// Unrolled 2x to allow parallel lookups without register spilling. /// + [SkipLocalsInit] internal static bool TryDecodeFromUtf8_Scalar(ReadOnlySpan hex, Span bytes, bool isOdd) { Debug.Assert((hex.Length / 2) + (hex.Length % 2) == bytes.Length, "Target buffer not right-sized for provided characters"); - int i = 0; - int j = 0; - int byteLo = 0; - int byteHi = 0; + ref byte hexRef = ref MemoryMarshal.GetReference(hex); + ref byte bytesRef = ref MemoryMarshal.GetReference(bytes); + ref byte lookupRef = ref MemoryMarshal.GetReference(CharToHexLookup); + nuint i = 0; + nuint j = 0; + nuint bytesLength = (nuint)bytes.Length; if (isOdd) { - byteLo = FromChar((char)hex[i++]); - bytes[j++] = (byte)byteLo; + int n0 = Unsafe.Add(ref lookupRef, Unsafe.Add(ref hexRef, i++)); + if (n0 == 0xFF) + return false; + Unsafe.Add(ref bytesRef, j++) = (byte)n0; } - while (j < bytes.Length) + // Unrolled 2x: process 4 hex chars (2 output bytes) per iteration + // 4 independent lookups fit in available registers without spilling + while (j + 2 <= bytesLength) { - byteLo = FromChar((char)hex[i + 1]); - byteHi = FromChar((char)hex[i]); + // 4 independent lookups - can execute in parallel on OoO CPUs + int n0 = Unsafe.Add(ref lookupRef, Unsafe.Add(ref hexRef, i)); + int n1 = Unsafe.Add(ref lookupRef, Unsafe.Add(ref hexRef, i + 1)); + int n2 = Unsafe.Add(ref lookupRef, Unsafe.Add(ref hexRef, i + 2)); + int n3 = Unsafe.Add(ref lookupRef, Unsafe.Add(ref hexRef, i + 3)); + + // Single validity check for all 4 nibbles + if (((n0 | n1 | n2 | n3) & 0xF0) != 0) + return false; + + // Combine nibbles into bytes and store + Unsafe.Add(ref bytesRef, j) = (byte)((n0 << 4) | n1); + Unsafe.Add(ref bytesRef, j + 1) = (byte)((n2 << 4) | n3); + + i += 4; + j += 2; + } - // byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern - // is if either byteHi or byteLo was not a hex character. - if ((byteLo | byteHi) == 0xFF) - break; + // Handle remaining 0-1 bytes + if (j < bytesLength) + { + int n0 = Unsafe.Add(ref lookupRef, Unsafe.Add(ref hexRef, i)); + int n1 = Unsafe.Add(ref lookupRef, Unsafe.Add(ref hexRef, i + 1)); - bytes[j++] = (byte)((byteHi << 4) | byteLo); - i += 2; + if (((n0 | n1) & 0xF0) != 0) + return false; + + Unsafe.Add(ref bytesRef, j) = (byte)((n0 << 4) | n1); } - return (byteLo | byteHi) != 0xFF; + return true; } /// /// Loops in 16 byte chunks and decodes them into 8 byte chunks. + /// Unrolled 2x to process 32 hex bytes per iteration when possible. /// + [SkipLocalsInit] internal static bool TryDecodeFromUtf8_Vector128(ReadOnlySpan hex, Span bytes) { Debug.Assert(Ssse3.IsSupported || AdvSimd.Arm64.IsSupported); @@ -299,78 +327,143 @@ internal static bool TryDecodeFromUtf8_Vector128(ReadOnlySpan hex, Span= Vector128.Count); nuint offset = 0; - nuint lengthSubTwoVector128 = (nuint)hex.Length - ((nuint)Vector128.Count); + nuint hexLength = (nuint)hex.Length; ref byte srcRef = ref MemoryMarshal.GetReference(hex); ref byte destRef = ref MemoryMarshal.GetReference(bytes); - do + Vector128 shuf = Vector128.Create((byte)0, 2, 4, 6, 8, 10, 12, 14, 0, 0, 0, 0, 0, 0, 0, 0); + + // 2x unrolled loop: process 32 hex bytes -> 16 output bytes per iteration + while (offset + (nuint)Vector128.Count * 2 <= hexLength) + { + Vector128 vec0 = Vector128.LoadUnsafe(ref srcRef, offset); + Vector128 vec1 = Vector128.LoadUnsafe(ref srcRef, offset + (nuint)Vector128.Count); + + // Process vec0 + Vector128 t1_0 = vec0 + Vector128.Create((byte)(0xFF - '9')); + Vector128 t2_0 = SubtractSaturate(t1_0, Vector128.Create((byte)6)); + Vector128 t3_0 = (vec0 & Vector128.Create((byte)0xDF)) - Vector128.Create((byte)'A'); + Vector128 t4_0 = AddSaturate(t3_0, Vector128.Create((byte)10)); + Vector128 nibbles0 = Vector128.Min(t2_0 - Vector128.Create((byte)0xF0), t4_0); + + // Process vec1 + Vector128 t1_1 = vec1 + Vector128.Create((byte)(0xFF - '9')); + Vector128 t2_1 = SubtractSaturate(t1_1, Vector128.Create((byte)6)); + Vector128 t3_1 = (vec1 & Vector128.Create((byte)0xDF)) - Vector128.Create((byte)'A'); + Vector128 t4_1 = AddSaturate(t3_1, Vector128.Create((byte)10)); + Vector128 nibbles1 = Vector128.Min(t2_1 - Vector128.Create((byte)0xF0), t4_1); + + // Combined validity check + Vector128 invalid0 = (vec0 & Vector128.Create((byte)0x80)) | AddSaturate(nibbles0, Vector128.Create((byte)(127 - 15))); + Vector128 invalid1 = (vec1 & Vector128.Create((byte)0x80)) | AddSaturate(nibbles1, Vector128.Create((byte)(127 - 15))); + if ((invalid0 | invalid1).ExtractMostSignificantBits() != 0) + return false; + + // Convert nibbles to bytes + Vector128 output0, output1; + if (Ssse3.IsSupported) + { + output0 = Ssse3.MultiplyAddAdjacent(nibbles0, Vector128.Create((short)0x0110).AsSByte()).AsByte(); + output1 = Ssse3.MultiplyAddAdjacent(nibbles1, Vector128.Create((short)0x0110).AsSByte()).AsByte(); + } + else + { + Vector128 even0 = AdvSimd.Arm64.TransposeEven(nibbles0, Vector128.Zero).AsInt16(); + Vector128 odd0 = AdvSimd.Arm64.TransposeOdd(nibbles0, Vector128.Zero).AsInt16(); + even0 = AdvSimd.ShiftLeftLogical(even0, 4).AsInt16(); + output0 = AdvSimd.AddSaturate(even0, odd0).AsByte(); + + Vector128 even1 = AdvSimd.Arm64.TransposeEven(nibbles1, Vector128.Zero).AsInt16(); + Vector128 odd1 = AdvSimd.Arm64.TransposeOdd(nibbles1, Vector128.Zero).AsInt16(); + even1 = AdvSimd.ShiftLeftLogical(even1, 4).AsInt16(); + output1 = AdvSimd.AddSaturate(even1, odd1).AsByte(); + } + + output0 = Vector128.Shuffle(output0, shuf); + output1 = Vector128.Shuffle(output1, shuf); + + Unsafe.WriteUnaligned(ref Unsafe.Add(ref destRef, offset / 2), output0.AsUInt64().ToScalar()); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref destRef, offset / 2 + 8), output1.AsUInt64().ToScalar()); + + offset += (nuint)Vector128.Count * 2; + } + + // Remainder: 1x loop for remaining 16-31 bytes + while (offset + (nuint)Vector128.Count <= hexLength) { Vector128 vec = Vector128.LoadUnsafe(ref srcRef, offset); - // Based on "Algorithm #3" https://github.com/WojciechMula/toys/blob/master/simd-parse-hex/geoff_algorithm.cpp - // by Geoff Langdale and Wojciech Mula - // Move digits '0'..'9' into range 0xf6..0xff. Vector128 t1 = vec + Vector128.Create((byte)(0xFF - '9')); - // And then correct the range to 0xf0..0xf9. - // All other bytes become less than 0xf0. Vector128 t2 = SubtractSaturate(t1, Vector128.Create((byte)6)); - // Convert into uppercase 'a'..'f' => 'A'..'F' and - // move hex letter 'A'..'F' into range 0..5. Vector128 t3 = (vec & Vector128.Create((byte)0xDF)) - Vector128.Create((byte)'A'); - // And correct the range into 10..15. - // The non-hex letters bytes become greater than 0x0f. Vector128 t4 = AddSaturate(t3, Vector128.Create((byte)10)); - // Convert '0'..'9' into nibbles 0..9. Non-digit bytes become - // greater than 0x0f. Finally choose the result: either valid nibble (0..9/10..15) - // or some byte greater than 0x0f. Vector128 nibbles = Vector128.Min(t2 - Vector128.Create((byte)0xF0), t4); - // Any high bit is a sign that input is not a valid hex data - if (!AllCharsInVectorAreAscii(vec) || - AddSaturate(nibbles, Vector128.Create((byte)(127 - 15))).ExtractMostSignificantBits() != 0) - { - // Input is either non-ASCII or invalid hex data - break; - } + + Vector128 invalid = (vec & Vector128.Create((byte)0x80)) | AddSaturate(nibbles, Vector128.Create((byte)(127 - 15))); + if (invalid.ExtractMostSignificantBits() != 0) + return false; + Vector128 output; if (Ssse3.IsSupported) { - output = Ssse3.MultiplyAddAdjacent(nibbles, - Vector128.Create((short)0x0110).AsSByte()).AsByte(); + output = Ssse3.MultiplyAddAdjacent(nibbles, Vector128.Create((short)0x0110).AsSByte()).AsByte(); } else { - // Workaround for missing MultiplyAddAdjacent on ARM Vector128 even = AdvSimd.Arm64.TransposeEven(nibbles, Vector128.Zero).AsInt16(); Vector128 odd = AdvSimd.Arm64.TransposeOdd(nibbles, Vector128.Zero).AsInt16(); even = AdvSimd.ShiftLeftLogical(even, 4).AsInt16(); output = AdvSimd.AddSaturate(even, odd).AsByte(); } - // Accumulate output in lower INT64 half and take care about endianness - output = Vector128.Shuffle(output, Vector128.Create((byte)0, 2, 4, 6, 8, 10, 12, 14, 0, 0, 0, 0, 0, 0, 0, 0)); - // Store 8 bytes in dest by given offset + + output = Vector128.Shuffle(output, shuf); Unsafe.WriteUnaligned(ref Unsafe.Add(ref destRef, offset / 2), output.AsUInt64().ToScalar()); - offset += (nuint)Vector128.Count * 2; - if (offset == (nuint)hex.Length) + offset += (nuint)Vector128.Count; + } + + // Handle trailing < 16 bytes with overlap + if (offset < hexLength) + { + offset = hexLength - (nuint)Vector128.Count; + Vector128 vec = Vector128.LoadUnsafe(ref srcRef, offset); + + Vector128 t1 = vec + Vector128.Create((byte)(0xFF - '9')); + Vector128 t2 = SubtractSaturate(t1, Vector128.Create((byte)6)); + Vector128 t3 = (vec & Vector128.Create((byte)0xDF)) - Vector128.Create((byte)'A'); + Vector128 t4 = AddSaturate(t3, Vector128.Create((byte)10)); + Vector128 nibbles = Vector128.Min(t2 - Vector128.Create((byte)0xF0), t4); + + Vector128 invalid = (vec & Vector128.Create((byte)0x80)) | AddSaturate(nibbles, Vector128.Create((byte)(127 - 15))); + if (invalid.ExtractMostSignificantBits() != 0) + return false; + + Vector128 output; + if (Ssse3.IsSupported) { - return true; + output = Ssse3.MultiplyAddAdjacent(nibbles, Vector128.Create((short)0x0110).AsSByte()).AsByte(); } - // Overlap with the current chunk for trailing elements - if (offset > lengthSubTwoVector128) + else { - offset = lengthSubTwoVector128; + Vector128 even = AdvSimd.Arm64.TransposeEven(nibbles, Vector128.Zero).AsInt16(); + Vector128 odd = AdvSimd.Arm64.TransposeOdd(nibbles, Vector128.Zero).AsInt16(); + even = AdvSimd.ShiftLeftLogical(even, 4).AsInt16(); + output = AdvSimd.AddSaturate(even, odd).AsByte(); } + + output = Vector128.Shuffle(output, shuf); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref destRef, offset / 2), output.AsUInt64().ToScalar()); } - while (true); - // Invalid input. - return false; + return true; } /// /// Loops in 32 byte chunks and decodes them into 16 byte chunks. + /// Unrolled 2x to process 64 hex bytes per iteration when possible. /// + [SkipLocalsInit] internal static bool TryDecodeFromUtf8_Vector256(ReadOnlySpan hex, Span bytes) { Debug.Assert(Avx2.IsSupported); @@ -378,66 +471,103 @@ internal static bool TryDecodeFromUtf8_Vector256(ReadOnlySpan hex, Span= Vector256.Count); nuint offset = 0; - nuint lengthSubTwoVector256 = (nuint)hex.Length - ((nuint)Vector256.Count); + nuint hexLength = (nuint)hex.Length; ref byte srcRef = ref MemoryMarshal.GetReference(hex); ref byte destRef = ref MemoryMarshal.GetReference(bytes); - do + // 2x unrolled loop: process 64 hex bytes -> 32 output bytes per iteration + // Use addition instead of subtraction to avoid unsigned underflow + while (offset + (nuint)Vector256.Count * 2 <= hexLength) + { + Vector256 vec0 = Vector256.LoadUnsafe(ref srcRef, offset); + Vector256 vec1 = Vector256.LoadUnsafe(ref srcRef, offset + (nuint)Vector256.Count); + + // Process vec0: hex decode algorithm + Vector256 t1_0 = vec0 + Vector256.Create((byte)(0xFF - '9')); + Vector256 t2_0 = Avx2.SubtractSaturate(t1_0, Vector256.Create((byte)6)); + Vector256 t3_0 = (vec0 & Vector256.Create((byte)0xDF)) - Vector256.Create((byte)'A'); + Vector256 t4_0 = Avx2.AddSaturate(t3_0, Vector256.Create((byte)10)); + Vector256 nibbles0 = Vector256.Min(t2_0 - Vector256.Create((byte)0xF0), t4_0); + + // Process vec1: hex decode algorithm + Vector256 t1_1 = vec1 + Vector256.Create((byte)(0xFF - '9')); + Vector256 t2_1 = Avx2.SubtractSaturate(t1_1, Vector256.Create((byte)6)); + Vector256 t3_1 = (vec1 & Vector256.Create((byte)0xDF)) - Vector256.Create((byte)'A'); + Vector256 t4_1 = Avx2.AddSaturate(t3_1, Vector256.Create((byte)10)); + Vector256 nibbles1 = Vector256.Min(t2_1 - Vector256.Create((byte)0xF0), t4_1); + + // Combined validity check for both vectors + Vector256 invalid0 = (vec0 & Vector256.Create((byte)0x80)) | Avx2.AddSaturate(nibbles0, Vector256.Create((byte)(127 - 15))); + Vector256 invalid1 = (vec1 & Vector256.Create((byte)0x80)) | Avx2.AddSaturate(nibbles1, Vector256.Create((byte)(127 - 15))); + if ((invalid0 | invalid1).ExtractMostSignificantBits() != 0) + return false; + + // Convert nibbles to bytes and shuffle + Vector256 output0 = Avx2.MultiplyAddAdjacent(nibbles0, Vector256.Create((short)0x0110).AsSByte()).AsByte(); + Vector256 output1 = Avx2.MultiplyAddAdjacent(nibbles1, Vector256.Create((short)0x0110).AsSByte()).AsByte(); + Vector256 shuf = Vector256.Create((byte)0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + output0 = Vector256.Shuffle(output0, shuf); + output1 = Vector256.Shuffle(output1, shuf); + + // Store 32 bytes total + Unsafe.WriteUnaligned(ref Unsafe.Add(ref destRef, offset / 2), output0.AsUInt64().GetLower()); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref destRef, offset / 2 + 16), output1.AsUInt64().GetLower()); + + offset += (nuint)Vector256.Count * 2; + } + + // Remainder: 1x loop for remaining 32-63 bytes + while (offset + (nuint)Vector256.Count <= hexLength) { Vector256 vec = Vector256.LoadUnsafe(ref srcRef, offset); - // Based on "Algorithm #3" https://github.com/WojciechMula/toys/blob/master/simd-parse-hex/geoff_algorithm.cpp - // by Geoff Langdale and Wojciech Mula - // Move digits '0'..'9' into range 0xf6..0xff. Vector256 t1 = vec + Vector256.Create((byte)(0xFF - '9')); - // And then correct the range to 0xf0..0xf9. - // All other bytes become less than 0xf0. Vector256 t2 = Avx2.SubtractSaturate(t1, Vector256.Create((byte)6)); - // Convert into uppercase 'a'..'f' => 'A'..'F' and - // move hex letter 'A'..'F' into range 0..5. Vector256 t3 = (vec & Vector256.Create((byte)0xDF)) - Vector256.Create((byte)'A'); - // And correct the range into 10..15. - // The non-hex letters bytes become greater than 0x0f. Vector256 t4 = Avx2.AddSaturate(t3, Vector256.Create((byte)10)); - // Convert '0'..'9' into nibbles 0..9. Non-digit bytes become - // greater than 0x0f. Finally choose the result: either valid nibble (0..9/10..15) - // or some byte greater than 0x0f. Vector256 nibbles = Vector256.Min(t2 - Vector256.Create((byte)0xF0), t4); - // Any high bit is a sign that input is not a valid hex data - if (!AllCharsInVectorAreAscii(vec) || - Avx2.AddSaturate(nibbles, Vector256.Create((byte)(127 - 15))).ExtractMostSignificantBits() != 0) - { - // Input is either non-ASCII or invalid hex data - break; - } - Vector256 output = Avx2.MultiplyAddAdjacent(nibbles, - Vector256.Create((short)0x0110).AsSByte()).AsByte(); - // Accumulate output in lower INT64 half and take care about endianness + + Vector256 invalid = (vec & Vector256.Create((byte)0x80)) | Avx2.AddSaturate(nibbles, Vector256.Create((byte)(127 - 15))); + if (invalid.ExtractMostSignificantBits() != 0) + return false; + + Vector256 output = Avx2.MultiplyAddAdjacent(nibbles, Vector256.Create((short)0x0110).AsSByte()).AsByte(); output = Vector256.Shuffle(output, Vector256.Create((byte)0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); - // Store 16 bytes in dest by given offset Unsafe.WriteUnaligned(ref Unsafe.Add(ref destRef, offset / 2), output.AsUInt64().GetLower()); - offset += (nuint)Vector256.Count * 2; - if (offset == (nuint)hex.Length) - { - return true; - } - // Overlap with the current chunk for trailing elements - if (offset > lengthSubTwoVector256) - { - offset = lengthSubTwoVector256; - } + offset += (nuint)Vector256.Count; } - while (true); - // Invalid input. - return false; + // Handle trailing < 32 bytes with overlap + if (offset < hexLength) + { + offset = hexLength - (nuint)Vector256.Count; + Vector256 vec = Vector256.LoadUnsafe(ref srcRef, offset); + + Vector256 t1 = vec + Vector256.Create((byte)(0xFF - '9')); + Vector256 t2 = Avx2.SubtractSaturate(t1, Vector256.Create((byte)6)); + Vector256 t3 = (vec & Vector256.Create((byte)0xDF)) - Vector256.Create((byte)'A'); + Vector256 t4 = Avx2.AddSaturate(t3, Vector256.Create((byte)10)); + Vector256 nibbles = Vector256.Min(t2 - Vector256.Create((byte)0xF0), t4); + + Vector256 invalid = (vec & Vector256.Create((byte)0x80)) | Avx2.AddSaturate(nibbles, Vector256.Create((byte)(127 - 15))); + if (invalid.ExtractMostSignificantBits() != 0) + return false; + + Vector256 output = Avx2.MultiplyAddAdjacent(nibbles, Vector256.Create((short)0x0110).AsSByte()).AsByte(); + output = Vector256.Shuffle(output, Vector256.Create((byte)0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref destRef, offset / 2), output.AsUInt64().GetLower()); + } + + return true; } /// /// Loops in 64 byte chunks and decodes them into 32 byte chunks. + /// Unrolled 2x to process 128 hex bytes per iteration when possible. /// + [SkipLocalsInit] internal static bool TryDecodeFromUtf8_Vector512(ReadOnlySpan hex, Span bytes) { Debug.Assert(Avx512BW.IsSupported); @@ -445,61 +575,95 @@ internal static bool TryDecodeFromUtf8_Vector512(ReadOnlySpan hex, Span= Vector512.Count); nuint offset = 0; - nuint lengthSubTwoVector512 = (nuint)hex.Length - ((nuint)Vector512.Count); + nuint hexLength = (nuint)hex.Length; ref byte srcRef = ref MemoryMarshal.GetReference(hex); ref byte destRef = ref MemoryMarshal.GetReference(bytes); - do + Vector512 shuf = Vector512.Create((byte)0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + + // 2x unrolled loop: process 128 hex bytes -> 64 output bytes per iteration + while (offset + (nuint)Vector512.Count * 2 <= hexLength) + { + Vector512 vec0 = Vector512.LoadUnsafe(ref srcRef, offset); + Vector512 vec1 = Vector512.LoadUnsafe(ref srcRef, offset + (nuint)Vector512.Count); + + // Process vec0 + Vector512 t1_0 = vec0 + Vector512.Create((byte)(0xFF - '9')); + Vector512 t2_0 = Avx512BW.SubtractSaturate(t1_0, Vector512.Create((byte)6)); + Vector512 t3_0 = (vec0 & Vector512.Create((byte)0xDF)) - Vector512.Create((byte)'A'); + Vector512 t4_0 = Avx512BW.AddSaturate(t3_0, Vector512.Create((byte)10)); + Vector512 nibbles0 = Vector512.Min(t2_0 - Vector512.Create((byte)0xF0), t4_0); + + // Process vec1 + Vector512 t1_1 = vec1 + Vector512.Create((byte)(0xFF - '9')); + Vector512 t2_1 = Avx512BW.SubtractSaturate(t1_1, Vector512.Create((byte)6)); + Vector512 t3_1 = (vec1 & Vector512.Create((byte)0xDF)) - Vector512.Create((byte)'A'); + Vector512 t4_1 = Avx512BW.AddSaturate(t3_1, Vector512.Create((byte)10)); + Vector512 nibbles1 = Vector512.Min(t2_1 - Vector512.Create((byte)0xF0), t4_1); + + // Combined validity check + Vector512 invalid0 = (vec0 & Vector512.Create((byte)0x80)) | Avx512BW.AddSaturate(nibbles0, Vector512.Create((byte)(127 - 15))); + Vector512 invalid1 = (vec1 & Vector512.Create((byte)0x80)) | Avx512BW.AddSaturate(nibbles1, Vector512.Create((byte)(127 - 15))); + if (((invalid0 | invalid1).ExtractMostSignificantBits()) != 0) + return false; + + // Convert and store + Vector512 output0 = Avx512BW.MultiplyAddAdjacent(nibbles0, Vector512.Create((short)0x0110).AsSByte()).AsByte(); + Vector512 output1 = Avx512BW.MultiplyAddAdjacent(nibbles1, Vector512.Create((short)0x0110).AsSByte()).AsByte(); + output0 = Vector512.Shuffle(output0, shuf); + output1 = Vector512.Shuffle(output1, shuf); + + Unsafe.WriteUnaligned(ref Unsafe.Add(ref destRef, offset / 2), output0.AsUInt64().GetLower()); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref destRef, offset / 2 + 32), output1.AsUInt64().GetLower()); + + offset += (nuint)Vector512.Count * 2; + } + + // Remainder: 1x loop for remaining 64-127 bytes + while (offset + (nuint)Vector512.Count <= hexLength) { Vector512 vec = Vector512.LoadUnsafe(ref srcRef, offset); - // Based on "Algorithm #3" https://github.com/WojciechMula/toys/blob/master/simd-parse-hex/geoff_algorithm.cpp - // by Geoff Langdale and Wojciech Mula - // Move digits '0'..'9' into range 0xf6..0xff. Vector512 t1 = vec + Vector512.Create((byte)(0xFF - '9')); - // And then correct the range to 0xf0..0xf9. - // All other bytes become less than 0xf0. Vector512 t2 = Avx512BW.SubtractSaturate(t1, Vector512.Create((byte)6)); - // Convert into uppercase 'a'..'f' => 'A'..'F' and - // move hex letter 'A'..'F' into range 0..5. Vector512 t3 = (vec & Vector512.Create((byte)0xDF)) - Vector512.Create((byte)'A'); - // And correct the range into 10..15. - // The non-hex letters bytes become greater than 0x0f. Vector512 t4 = Avx512BW.AddSaturate(t3, Vector512.Create((byte)10)); - // Convert '0'..'9' into nibbles 0..9. Non-digit bytes become - // greater than 0x0f. Finally choose the result: either valid nibble (0..9/10..15) - // or some byte greater than 0x0f. Vector512 nibbles = Vector512.Min(t2 - Vector512.Create((byte)0xF0), t4); - // Any high bit is a sign that input is not a valid hex data - if (!AllCharsInVectorAreAscii(vec) || - Avx512BW.AddSaturate(nibbles, Vector512.Create((byte)(127 - 15))).ExtractMostSignificantBits() != 0) - { - // Input is either non-ASCII or invalid hex data - break; - } - Vector512 output = Avx512BW.MultiplyAddAdjacent(nibbles, - Vector512.Create((short)0x0110).AsSByte()).AsByte(); - // Accumulate output in lower INT64 half and take care about endianness - output = Vector512.Shuffle(output, Vector512.Create((byte)0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); - // Store 32 bytes in dest by given offset + + Vector512 invalid = (vec & Vector512.Create((byte)0x80)) | Avx512BW.AddSaturate(nibbles, Vector512.Create((byte)(127 - 15))); + if (invalid.ExtractMostSignificantBits() != 0) + return false; + + Vector512 output = Avx512BW.MultiplyAddAdjacent(nibbles, Vector512.Create((short)0x0110).AsSByte()).AsByte(); + output = Vector512.Shuffle(output, shuf); Unsafe.WriteUnaligned(ref Unsafe.Add(ref destRef, offset / 2), output.AsUInt64().GetLower()); - offset += (nuint)Vector512.Count * 2; - if (offset == (nuint)hex.Length) - { - return true; - } - // Overlap with the current chunk for trailing elements - if (offset > lengthSubTwoVector512) - { - offset = lengthSubTwoVector512; - } + offset += (nuint)Vector512.Count; + } + + // Handle trailing < 64 bytes with overlap + if (offset < hexLength) + { + offset = hexLength - (nuint)Vector512.Count; + Vector512 vec = Vector512.LoadUnsafe(ref srcRef, offset); + + Vector512 t1 = vec + Vector512.Create((byte)(0xFF - '9')); + Vector512 t2 = Avx512BW.SubtractSaturate(t1, Vector512.Create((byte)6)); + Vector512 t3 = (vec & Vector512.Create((byte)0xDF)) - Vector512.Create((byte)'A'); + Vector512 t4 = Avx512BW.AddSaturate(t3, Vector512.Create((byte)10)); + Vector512 nibbles = Vector512.Min(t2 - Vector512.Create((byte)0xF0), t4); + + Vector512 invalid = (vec & Vector512.Create((byte)0x80)) | Avx512BW.AddSaturate(nibbles, Vector512.Create((byte)(127 - 15))); + if (invalid.ExtractMostSignificantBits() != 0) + return false; + + Vector512 output = Avx512BW.MultiplyAddAdjacent(nibbles, Vector512.Create((short)0x0110).AsSByte()).AsByte(); + output = Vector512.Shuffle(output, shuf); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref destRef, offset / 2), output.AsUInt64().GetLower()); } - while (true); - // Invalid input. - return false; + return true; } public static bool TryDecodeFromUtf16_Vector128(ReadOnlySpan chars, Span bytes) diff --git a/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs index 1ecd2adc0d84..3f4a9e0c2919 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs @@ -29,13 +29,6 @@ public static UInt256 GWei(this int @this) return (uint)@this * Unit.GWei; } - public static byte[] ToByteArray(this int value) - { - byte[] bytes = new byte[sizeof(int)]; - BinaryPrimitives.WriteInt32BigEndian(bytes, value); - return bytes; - } - public static byte[] ToBigEndianByteArray(this uint value) { byte[] bytes = BitConverter.GetBytes(value); @@ -49,4 +42,14 @@ public static byte[] ToBigEndianByteArray(this uint value) public static byte[] ToBigEndianByteArray(this int value) => ToBigEndianByteArray((uint)value); + + public static byte[] ToLittleEndianByteArray(this uint value) + { + byte[] bytes = new byte[sizeof(uint)]; + BinaryPrimitives.WriteUInt32LittleEndian(bytes, value); + return bytes; + } + + public static byte[] ToLittleEndianByteArray(this int value) + => ToLittleEndianByteArray((uint)value); } diff --git a/src/Nethermind/Nethermind.Core/Extensions/ReadOnlySequenceExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/ReadOnlySequenceExtensions.cs index f53858d950b5..716dfbbd41e2 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/ReadOnlySequenceExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/ReadOnlySequenceExtensions.cs @@ -12,24 +12,33 @@ public static class ReadOnlySequenceExtensions public static ReadOnlySequence TrimStart(this ReadOnlySequence sequence, byte[]? chars = null) { - SequencePosition position = sequence.Start; ReadOnlySpan charsSpan = chars ?? WhitespaceChars; - while (sequence.TryGet(ref position, out ReadOnlyMemory memory)) + SequencePosition start = sequence.Start; + + foreach (ReadOnlyMemory memory in sequence) { ReadOnlySpan span = memory.Span; int index = span.IndexOfAnyExcept(charsSpan); - if (index != 0) + if (index == -1) + { + // The entire segment is trimmed chars, advance past it + start = sequence.GetPosition(span.Length, start); + } + else if (index > 0) { - // if index == -1, then the whole span is whitespace - sequence = sequence.Slice(index != -1 ? index : span.Length); + // Found non-trimmed char partway through the segment + start = sequence.GetPosition(index, start); + return sequence.Slice(start); } else { - return sequence; + // First char is non-trimmed, we're done + return sequence.Slice(start); } } - return sequence; + // The entire sequence was trimmed chars + return sequence.Slice(sequence.End); } } diff --git a/src/Nethermind/Nethermind.Core/Extensions/SizeExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/SizeExtensions.cs index ba773225fb46..2ae4af43e7e8 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/SizeExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/SizeExtensions.cs @@ -67,7 +67,7 @@ public static long KiB(this int @this) return ((long)@this).KiB(); } - public static string SizeToString(this long @this, bool useSi = false, int precision = 1) + public static string SizeToString(this long @this, bool useSi = false, bool addSpace = false, int precision = 1) { string[] suf = useSi ? ["B", "KB", "MB", "GB", "TB"] : ["B", "KiB", "MiB", "GiB", "TiB"]; if (@this == 0) @@ -77,7 +77,7 @@ public static string SizeToString(this long @this, bool useSi = false, int preci long bytes = Math.Abs(@this); int place = Math.Min(suf.Length - 1, Convert.ToInt32(Math.Floor(Math.Log(bytes, useSi ? 1000 : 1024)))); double num = Math.Round(bytes / Math.Pow(useSi ? 1000 : 1024, place), precision); - return (Math.Sign(@this) * num).ToString() + suf[place]; + return string.Concat(Math.Sign(@this) * num, addSpace ? " " : "", suf[place]); } } } diff --git a/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs index 51ba5ff778a9..7ae71df3f508 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs @@ -7,6 +7,9 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using Arm = System.Runtime.Intrinsics.Arm; +using x64 = System.Runtime.Intrinsics.X86; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; @@ -19,6 +22,8 @@ public static class SpanExtensions // the performance of the network as a whole. private static readonly uint s_instanceRandom = (uint)System.Security.Cryptography.RandomNumberGenerator.GetInt32(int.MinValue, int.MaxValue); + internal static uint ComputeSeed(int len) => s_instanceRandom + (uint)len; + public static string ToHexString(this in Memory memory, bool withZeroX = false) { return ToHexString(memory.Span, withZeroX, false, false); @@ -132,7 +137,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' @@ -185,88 +190,405 @@ public static Span TakeAndMove(this ref Span span, int length) public static ArrayPoolList ToPooledList(this in ReadOnlySpan span) { - ArrayPoolList newList = new ArrayPoolList(span.Length); + ArrayPoolList newList = new(span.Length); newList.AddRange(span); return newList; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int FastHash(this Span input) - => FastHash((ReadOnlySpan)input); + public static ArrayPoolListRef ToPooledListRef(this in ReadOnlySpan span) + { + ArrayPoolListRef newList = new(span.Length); + newList.AddRange(span); + return newList; + } + /// + /// 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; + int len = input.Length; + if (len == 0) return 0; + + ref byte start = ref MemoryMarshal.GetReference(input); + uint seed = s_instanceRandom + (uint)len; - ref var b = ref MemoryMarshal.GetReference(input); - uint hash = s_instanceRandom + (uint)length; - if (length < sizeof(long)) + if (len >= 16) { - goto Short; + if (x64.Aes.IsSupported) return FastHashAesX64(ref start, len, seed); + if (Arm.Aes.IsSupported) return FastHashAesArm(ref start, len, seed); } - // Start with instance random, length and first ulong as seed - hash = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned(ref b)); + return FastHashCrc(ref start, len, seed); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + [SkipLocalsInit] + internal static int FastHashAesX64(ref byte start, int len, uint seed) + { + Vector128 seedVec = Vector128.CreateScalar(seed).AsByte(); + Vector128 acc0 = Unsafe.As>(ref start) ^ seedVec; + + if (len > 64) + { + Vector128 acc1 = Unsafe.As>(ref Unsafe.Add(ref start, 16)) ^ seedVec; + Vector128 acc2 = Unsafe.As>(ref Unsafe.Add(ref start, 32)) ^ seedVec; + Vector128 acc3 = Unsafe.As>(ref Unsafe.Add(ref start, 48)) ^ seedVec; + + ref byte p = ref Unsafe.Add(ref start, 64); + int remaining = len - 64; + + while (remaining >= 64) + { + acc0 = x64.Aes.Encrypt(Unsafe.As>(ref p), acc0); + acc1 = x64.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 16)), acc1); + acc2 = x64.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 32)), acc2); + acc3 = x64.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 48)), acc3); + + p = ref Unsafe.Add(ref p, 64); + remaining -= 64; + } + + // Fold 4 lanes: 3 XOR + 1 AES (minimal serial latency) + acc0 ^= acc1; + acc2 ^= acc3; + acc0 ^= acc2; + acc0 = x64.Aes.Encrypt(seedVec, acc0); + + // Drain remaining 0-63 bytes + while (remaining >= 16) + { + acc0 = x64.Aes.Encrypt(Unsafe.As>(ref p), acc0); + p = ref Unsafe.Add(ref p, 16); + remaining -= 16; + } - // 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) + // Remaining 1-15 bytes: use CRC to avoid overlap with drain blocks + if (remaining > 0) + { + uint crc = seed; + if (remaining >= 8) + { + crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 8); + remaining -= 8; + } + if ((remaining & 4) != 0) + { + crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 4); + } + if ((remaining & 2) != 0) + { + crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 2); + } + if ((remaining & 1) != 0) + { + crc = BitOperations.Crc32C(crc, p); + } + acc0 = x64.Aes.Encrypt(Vector128.CreateScalar(crc).AsByte(), acc0); + } + } + else if (len > 32) { - // Align by moving by the misaligned count - b = ref Unsafe.Add(ref b, misaligned); - length -= (int)misaligned; + ref byte p = ref Unsafe.Add(ref start, 16); + int remaining = len - 16; + + while (remaining > 16) + { + acc0 = x64.Aes.Encrypt(Unsafe.As>(ref p), acc0); + p = ref Unsafe.Add(ref p, 16); + remaining -= 16; + } + + Vector128 last = Unsafe.As>(ref Unsafe.Add(ref start, len - 16)); + acc0 = x64.Aes.Encrypt(last, acc0); } else { - // Already Crc'd first ulong so skip it - b = ref Unsafe.Add(ref b, sizeof(ulong)); - length -= sizeof(ulong); + Vector128 data = Unsafe.As>(ref Unsafe.Add(ref start, len - 16)); + acc0 = x64.Aes.Encrypt(data, acc0); + } + + ulong compressed = acc0.AsUInt64().GetElement(0) ^ acc0.AsUInt64().GetElement(1); + return (int)(uint)(compressed ^ (compressed >> 32)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + [SkipLocalsInit] + internal static int FastHashAesArm(ref byte start, int len, uint seed) + { + Vector128 seedVec = Vector128.CreateScalar(seed).AsByte(); + Vector128 acc0 = Unsafe.As>(ref start) ^ seedVec; + + if (len > 64) + { + Vector128 acc1 = Unsafe.As>(ref Unsafe.Add(ref start, 16)) ^ seedVec; + Vector128 acc2 = Unsafe.As>(ref Unsafe.Add(ref start, 32)) ^ seedVec; + Vector128 acc3 = Unsafe.As>(ref Unsafe.Add(ref start, 48)) ^ seedVec; + + ref byte p = ref Unsafe.Add(ref start, 64); + int remaining = len - 64; + + while (remaining >= 64) + { + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref p), acc0)); + acc1 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 16)), acc1)); + acc2 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 32)), acc2)); + acc3 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 48)), acc3)); + + p = ref Unsafe.Add(ref p, 64); + remaining -= 64; + } + + acc0 ^= acc1; + acc2 ^= acc3; + acc0 ^= acc2; + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(seedVec, acc0)); + + while (remaining >= 16) + { + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref p), acc0)); + p = ref Unsafe.Add(ref p, 16); + remaining -= 16; + } + + if (remaining > 0) + { + uint crc = seed; + if (remaining >= 8) + { + crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 8); + remaining -= 8; + } + if ((remaining & 4) != 0) + { + crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 4); + } + if ((remaining & 2) != 0) + { + crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 2); + } + if ((remaining & 1) != 0) + { + crc = BitOperations.Crc32C(crc, p); + } + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Vector128.CreateScalar(crc).AsByte(), acc0)); + } } + else if (len > 32) + { + ref byte p = ref Unsafe.Add(ref start, 16); + int remaining = len - 16; - // Length is fully aligned here and b is set in place - while (length >= sizeof(ulong) * 3) + while (remaining > 16) + { + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref p), acc0)); + p = ref Unsafe.Add(ref p, 16); + remaining -= 16; + } + + Vector128 last = Unsafe.As>(ref Unsafe.Add(ref start, len - 16)); + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(last, acc0)); + } + else { - // 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); + Vector128 data = Unsafe.As>(ref Unsafe.Add(ref start, len - 16)); + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(data, acc0)); } - while (length > 0) + ulong compressed = acc0.AsUInt64().GetElement(0) ^ acc0.AsUInt64().GetElement(1); + return (int)(uint)(compressed ^ (compressed >> 32)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + [SkipLocalsInit] + internal static int FastHashCrc(ref byte start, int len, uint seed) + { + uint hash; + if (len < 16) + { + if (len >= 8) + { + ulong lo = Unsafe.ReadUnaligned(ref start); + ulong hi = Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, len - 8)); + uint h0 = BitOperations.Crc32C(seed, lo); + uint h1 = BitOperations.Crc32C(seed ^ 0x9E3779B9u, hi); + hash = h0 + BitOperations.RotateLeft(h1, 11); + } + else + { + hash = CrcTailOrdered(seed, ref start, len); + } + } + else { - hash = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned(ref b)); - b = ref Unsafe.Add(ref b, sizeof(ulong)); - length -= sizeof(ulong); + uint h0 = seed; + uint h1 = seed ^ 0x9E3779B9u; + uint h2 = seed ^ 0x85EBCA6Bu; + uint h3 = seed ^ 0xC2B2AE35u; + + ref byte q = ref start; + int aligned = len & ~7; + int remaining = aligned; + + while (remaining >= 64) + { + 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; + } + + if (remaining >= 32) + { + 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; + } + + 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))); + + h2 = BitOperations.RotateLeft(h2, 17) + BitOperations.RotateLeft(h3, 23); + h0 += BitOperations.RotateLeft(h1, 11); + hash = h2 + h0; + + int tailBytes = len - aligned; + if (tailBytes != 0) + { + ref byte tailRef = ref Unsafe.Add(ref start, aligned); + hash = CrcTailOrdered(hash, ref tailRef, tailBytes); + } } + hash ^= hash >> 16; + hash *= 0x9E3779B1u; + hash ^= hash >> 16; return (int)hash; - Short: - ulong data = 0; - if ((length & sizeof(byte)) != 0) + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static uint CrcTailOrdered(uint hash, ref byte p, int length) { - data = b; - b = ref Unsafe.Add(ref b, sizeof(byte)); + 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(ushort)) != 0) + } + + /// + /// Computes a very fast, non-cryptographic 64-bit hash of exactly 32 bytes. + /// + /// Reference to the first byte of the 32-byte input. + /// A 64-bit hash value with good distribution across all bits. + /// + /// Uses AES hardware acceleration when available, falls back to CRC32C otherwise. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long FastHash64For32Bytes(ref byte start) + { + uint seed = s_instanceRandom + 32; + + if (x64.Aes.IsSupported || Arm.Aes.IsSupported) { - data = (data << (sizeof(ushort) * 8)) | Unsafe.ReadUnaligned(ref b); - b = ref Unsafe.Add(ref b, sizeof(ushort)); + Vector128 key = Unsafe.As>(ref start); + Vector128 data = Unsafe.As>(ref Unsafe.Add(ref start, 16)); + key ^= Vector128.CreateScalar(seed).AsByte(); + Vector128 mixed = x64.Aes.IsSupported + ? x64.Aes.Encrypt(data, key) + : Arm.Aes.MixColumns(Arm.Aes.Encrypt(data, key)); + return (long)(mixed.AsUInt64().GetElement(0) ^ mixed.AsUInt64().GetElement(1)); } - if ((length & sizeof(uint)) != 0) + + // Fallback: CRC32C-based 64-bit hash + ulong h0 = BitOperations.Crc32C(seed, Unsafe.ReadUnaligned(ref start)); + ulong h1 = BitOperations.Crc32C(seed ^ 0x9E3779B9u, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 8))); + ulong h2 = BitOperations.Crc32C(seed ^ 0x85EBCA6Bu, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 16))); + ulong h3 = BitOperations.Crc32C(seed ^ 0xC2B2AE35u, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 24))); + return (long)((h0 | (h1 << 32)) ^ (h2 | (h3 << 32))); + } + + /// + /// Computes a very fast, non-cryptographic 64-bit hash of exactly 20 bytes (Address size). + /// + /// Reference to the first byte of the 20-byte input. + /// A 64-bit hash value with good distribution across all bits. + /// + /// Uses AES hardware acceleration when available, falls back to CRC32C otherwise. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long FastHash64For20Bytes(ref byte start) + { + uint seed = s_instanceRandom + 20; + + if (x64.Aes.IsSupported || Arm.Aes.IsSupported) { - data = (data << (sizeof(uint) * 8)) | Unsafe.ReadUnaligned(ref b); + Vector128 key = Unsafe.As>(ref start); + uint last4 = Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 16)); + Vector128 data = Vector128.CreateScalar(last4).AsByte(); + key ^= Vector128.CreateScalar(seed).AsByte(); + Vector128 mixed = x64.Aes.IsSupported + ? x64.Aes.Encrypt(data, key) + : Arm.Aes.MixColumns(Arm.Aes.Encrypt(data, key)); + return (long)(mixed.AsUInt64().GetElement(0) ^ mixed.AsUInt64().GetElement(1)); } - return (int)BitOperations.Crc32C(hash, data); + // Fallback: CRC32C-based 64-bit hash + ulong h0 = BitOperations.Crc32C(seed, Unsafe.ReadUnaligned(ref start)); + ulong h1 = BitOperations.Crc32C(seed ^ 0x9E3779B9u, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 8))); + uint h2 = BitOperations.Crc32C(seed ^ 0x85EBCA6Bu, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 16))); + return (long)((h0 | (h1 << 32)) ^ ((ulong)h2 * 0x9E3779B97F4A7C15)); } } } diff --git a/src/Nethermind/Nethermind.Core/Extensions/SpecProviderExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/SpecProviderExtensions.cs index b5205d186999..a95e2f8b3157 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/SpecProviderExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/SpecProviderExtensions.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Linq; using Nethermind.Core.Specs; namespace Nethermind.Core.Extensions @@ -9,8 +8,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 +20,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 04a82b79e468..20e6ab5de765 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 d28b523d305a..92712f8af166 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.Core/Extensions/WaitHandleExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/WaitHandleExtensions.cs index fd0616dca645..5090c78856f1 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/WaitHandleExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/WaitHandleExtensions.cs @@ -23,8 +23,12 @@ public static async Task WaitOneAsync(this WaitHandle handle, int millisec millisecondsTimeout, true); tokenRegistration = cancellationToken.Register( - static state => ((TaskCompletionSource)state!).TrySetCanceled(), - tcs); + static state => + { + var (tcs, ct) = ((TaskCompletionSource tcs, CancellationToken ct))state!; + tcs.TrySetCanceled(ct); + }, + (tcs, cancellationToken)); return await tcs.Task; } diff --git a/src/Nethermind/Nethermind.Core/FakeWriteBatch.cs b/src/Nethermind/Nethermind.Core/FakeWriteBatch.cs index ce27831eb399..1fcf153998a1 100644 --- a/src/Nethermind/Nethermind.Core/FakeWriteBatch.cs +++ b/src/Nethermind/Nethermind.Core/FakeWriteBatch.cs @@ -31,5 +31,12 @@ public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteF { _storePretendingToSupportBatches.Set(key, value, flags); } + + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) + { + throw new NotSupportedException("Merging is not supported by this implementation."); + } + + public void Clear() { } } } 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 ddb2d5caee44..8465cc5765a9 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/HardwareInfo.cs b/src/Nethermind/Nethermind.Core/HardwareInfo.cs index 280c04c5bb68..1073c917a671 100644 --- a/src/Nethermind/Nethermind.Core/HardwareInfo.cs +++ b/src/Nethermind/Nethermind.Core/HardwareInfo.cs @@ -2,17 +2,64 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Runtime.InteropServices; namespace Nethermind.Core; public class HardwareInfo : IHardwareInfo { public long AvailableMemoryBytes { get; } + public int? MaxOpenFilesLimit { get; } public HardwareInfo() { // Note: Not the same as memory capacity. This take into account current system memory pressure such as // other process as well as OS level limit such as rlimit. Eh, its good enough. AvailableMemoryBytes = GC.GetGCMemoryInfo().TotalAvailableMemoryBytes; + MaxOpenFilesLimit = GetMaxOpenFilesLimit(); } + + private static int? GetMaxOpenFilesLimit() + { + try + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Windows doesn't have a per-process file descriptor limit like Unix systems + // The limit is much higher (typically thousands), so we return null to indicate no specific limit + return null; + } + else + { + // Unix-like systems (Linux, macOS, etc.) + const int RLIMIT_NOFILE = 7; // Same value for both macOS and Linux + + RLimit limit = new(); + int result = getrlimit(RLIMIT_NOFILE, ref limit); + + if (result == 0) + { + // Return the soft limit as it's what the process can actually use + // The soft limit can be raised up to the hard limit by the process + return (int)Math.Min(limit.rlim_cur, int.MaxValue); + } + } + } + catch + { + // If we can't detect the limit, return null to indicate unknown + } + + return null; + } + + [StructLayout(LayoutKind.Sequential)] + private struct RLimit + { + public ulong rlim_cur; // Soft limit + public ulong rlim_max; // Hard limit + } + + [DllImport("libc", SetLastError = true)] + private static extern int getrlimit(int resource, ref RLimit rlim); } diff --git a/src/Nethermind/Nethermind.Core/IAccountStateProvider.cs b/src/Nethermind/Nethermind.Core/IAccountStateProvider.cs index ec52457f1a5f..5b949c67dea3 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/IHardwareInfo.cs b/src/Nethermind/Nethermind.Core/IHardwareInfo.cs index 0b9e98ff9516..87d78540683c 100644 --- a/src/Nethermind/Nethermind.Core/IHardwareInfo.cs +++ b/src/Nethermind/Nethermind.Core/IHardwareInfo.cs @@ -9,4 +9,5 @@ public interface IHardwareInfo { public static readonly long StateDbLargerMemoryThreshold = 20.GiB(); long AvailableMemoryBytes { get; } + int? MaxOpenFilesLimit { get; } } diff --git a/src/Nethermind/Nethermind.Core/IKeyValueStore.cs b/src/Nethermind/Nethermind.Core/IKeyValueStore.cs index 1e4a1f33c8b4..d30dbcfe87df 100644 --- a/src/Nethermind/Nethermind.Core/IKeyValueStore.cs +++ b/src/Nethermind/Nethermind.Core/IKeyValueStore.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; +using Nethermind.Core.Buffers; using Nethermind.Core.Extensions; namespace Nethermind.Core @@ -22,12 +24,40 @@ public interface IReadOnlyKeyValueStore byte[]? Get(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None); /// - /// Return span. Must call `DangerousReleaseMemory` or there can be some leak. + /// Return span. Must call after use to avoid memory leaks. + /// Prefer using which handles release automatically via disposal. /// - /// - /// Can return null or empty Span on missing key + /// Key whose associated value should be read. + /// Read behavior flags that control how the value is retrieved. + /// Can return null or empty Span on a 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); @@ -37,6 +67,19 @@ bool KeyExists(ReadOnlySpan key) } void DangerousReleaseMemory(in ReadOnlySpan span) { } + + /// + /// Returns a MemoryManager wrapping the value for the given key. + /// The MemoryManager must be disposed of when done to release any underlying resources. + /// + /// Key whose associated value should be read. + /// Read behavior flags that control how the value is retrieved. + /// A MemoryManager wrapping the value or null if the key doesn't exist. + MemoryManager? GetOwnedMemory(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + { + byte[]? data = Get(key, flags); + return data is null or { Length: 0 } ? null : new ArrayMemoryManager(data); + } } public interface IReadOnlyNativeKeyValueStore @@ -69,6 +112,72 @@ public interface IWriteOnlyKeyValueStore void Remove(ReadOnlySpan key) => Set(key, null); } + public interface IMergeableKeyValueStore : IWriteOnlyKeyValueStore + { + void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None); + } + + public interface ISortedKeyValueStore : IReadOnlyKeyValueStore + { + byte[]? FirstKey { get; } + byte[]? LastKey { get; } + + 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 + { + } + + /// + /// Represent a sorted view of a `ISortedKeyValueStore`. + /// + public interface ISortedView : IDisposable + { + public bool StartBefore(ReadOnlySpan value); + public bool MoveNext(); + public ReadOnlySpan CurrentKey { get; } + public ReadOnlySpan CurrentValue { get; } + } + [Flags] public enum ReadFlags { diff --git a/src/Nethermind/Nethermind.Core/IMessageHandler.cs b/src/Nethermind/Nethermind.Core/IMessageHandler.cs new file mode 100644 index 000000000000..41abb066e010 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/IMessageHandler.cs @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Core; + +public interface IMessageHandler +{ + void HandleMessage(TMessage message); +} diff --git a/src/Nethermind/Nethermind.Core/INew.cs b/src/Nethermind/Nethermind.Core/INew.cs new file mode 100644 index 000000000000..76cb860edd8b --- /dev/null +++ b/src/Nethermind/Nethermind.Core/INew.cs @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Core; + +public interface INew +{ + public static abstract T New(TArg arg); +} diff --git a/src/Nethermind/Nethermind.Core/IWriteBatch.cs b/src/Nethermind/Nethermind.Core/IWriteBatch.cs index 909f184541c0..852dc132caa9 100644 --- a/src/Nethermind/Nethermind.Core/IWriteBatch.cs +++ b/src/Nethermind/Nethermind.Core/IWriteBatch.cs @@ -5,5 +5,8 @@ namespace Nethermind.Core { - public interface IWriteBatch : IDisposable, IWriteOnlyKeyValueStore; + public interface IWriteBatch : IDisposable, IWriteOnlyKeyValueStore, IMergeableKeyValueStore + { + void Clear(); + } } diff --git a/src/Nethermind/Nethermind.Core/InvalidBlockHelper.cs b/src/Nethermind/Nethermind.Core/InvalidBlockHelper.cs index f3d151850f29..171b3e125839 100644 --- a/src/Nethermind/Nethermind.Core/InvalidBlockHelper.cs +++ b/src/Nethermind/Nethermind.Core/InvalidBlockHelper.cs @@ -5,8 +5,6 @@ namespace Nethermind.Core; public static class InvalidBlockHelper { - public static string GetMessage(Block? block, string reason) - { - return $"Rejected invalid block {block?.ToString(Block.Format.FullHashNumberAndExtraData)}, reason: {reason}"; - } + public static string GetMessage(Block? block, string reason) => + $"Rejected invalid block {block?.ToString(Block.Format.FullHashNumberAndExtraData)}, reason: {reason}"; } diff --git a/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs b/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs index e67479f53636..1d1c0aa88199 100644 --- a/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs +++ b/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs @@ -15,7 +15,8 @@ namespace Nethermind.Serialization.Json; public class ByteArrayConverter : JsonConverter { - private static readonly ushort _hexPrefix = MemoryMarshal.Cast("0x"u8)[0]; + // '0' = 0x30, 'x' = 0x78, little-endian: 0x7830 + private const ushort HexPrefix = 0x7830; public override byte[]? Read( ref Utf8JsonReader reader, @@ -25,7 +26,8 @@ public class ByteArrayConverter : JsonConverter return Convert(ref reader); } - public static byte[]? Convert(ref Utf8JsonReader reader) + [SkipLocalsInit] + public static byte[]? Convert(ref Utf8JsonReader reader, bool strictHexFormat = false) { JsonTokenType tokenType = reader.TokenType; if (tokenType == JsonTokenType.None || tokenType == JsonTokenType.Null) @@ -35,25 +37,30 @@ public class ByteArrayConverter : JsonConverter if (reader.HasValueSequence) { - return ConvertValueSequence(ref reader); + return ConvertValueSequence(ref reader, strictHexFormat); } - int length = reader.ValueSpan.Length; ReadOnlySpan hex = reader.ValueSpan; - if (hex.Length == 0) return null; - if (length >= 2 && Unsafe.As(ref MemoryMarshal.GetReference(hex)) == _hexPrefix) - hex = hex[2..]; + int length = hex.Length; + if (length == 0) return null; + ref byte hexRef = ref MemoryMarshal.GetReference(hex); + if (length >= 2 && Unsafe.As(ref hexRef) == HexPrefix) + { + hex = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref hexRef, 2), length - 2); + } + else if (strictHexFormat) ThrowFormatException(); return Bytes.FromUtf8HexString(hex); } [SkipLocalsInit] [MethodImpl(MethodImplOptions.NoInlining)] - private static byte[]? ConvertValueSequence(ref Utf8JsonReader reader) + private static byte[]? ConvertValueSequence(ref Utf8JsonReader reader, bool strictHexFormat) { ReadOnlySequence valueSequence = reader.ValueSequence; int length = checked((int)valueSequence.Length); if (length == 0) return null; + // Detect and skip 0x prefix even if split across segments SequenceReader sr = new(valueSequence); bool hadPrefix = false; @@ -62,86 +69,81 @@ public class ByteArrayConverter : JsonConverter if (b0 == (byte)'0') { sr.Advance(1); - if (sr.TryPeek(out byte b1) && (b1 == (byte)'x')) + if (sr.TryPeek(out byte b1) && b1 == (byte)'x') { sr.Advance(1); hadPrefix = true; } else { - // rewind if not really a prefix sr.Rewind(1); + if (strictHexFormat) + ThrowFormatException(); } } + else if (strictHexFormat) + { + ThrowFormatException(); + } } - // Compute total hex digit count (after prefix) long totalHexChars = length - (hadPrefix ? 2 : 0); if (totalHexChars <= 0) return []; int odd = (int)(totalHexChars & 1); - int outLenFinal = (int)(totalHexChars >> 1) + odd; - if (outLenFinal == 0) return []; + int outLen = (int)(totalHexChars >> 1) + odd; + + byte[] result = GC.AllocateUninitializedArray(outLen); + ref byte resultRef = ref MemoryMarshal.GetArrayDataReference(result); + int outPos = 0; - byte[] result = GC.AllocateUninitializedArray(outLenFinal); - Span output = result; if (odd == 1) { - // If odd, we deal with the extra nibble, so we are left with an even number of nibbles if (!sr.TryRead(out byte firstNibble)) - { ThrowInvalidOperationException(); - } + firstNibble = (byte)HexConverter.FromLowerChar(firstNibble | 0x20); if (firstNibble > 0x0F) - { ThrowFormatException(); - } - result[0] = firstNibble; - output = output[1..]; + + Unsafe.Add(ref resultRef, outPos++) = firstNibble; } - // Stackalloc outside of the loop to avoid stackoverflow. - Span twoNibbles = stackalloc byte[2]; + // Use ushort as 2-byte buffer instead of stackalloc + Unsafe.SkipInit(out ushort twoNibblesStorage); + Span twoNibbles = MemoryMarshal.CreateSpan(ref Unsafe.As(ref twoNibblesStorage), 2); + while (!sr.End) { - ReadOnlySpan first = sr.UnreadSpan; - if (!first.IsEmpty) + ReadOnlySpan span = sr.UnreadSpan; + if (!span.IsEmpty) { - // Decode the largest even-length slice of the current contiguous span without copying. - int evenLen = first.Length & ~1; // largest even + int evenLen = span.Length & ~1; if (evenLen > 0) { int outBytes = evenLen >> 1; - Bytes.FromUtf8HexString(first.Slice(0, evenLen), output.Slice(0, outBytes)); - output = output.Slice(outBytes); + Bytes.FromUtf8HexString(span.Slice(0, evenLen), + MemoryMarshal.CreateSpan(ref Unsafe.Add(ref resultRef, outPos), outBytes)); + outPos += outBytes; sr.Advance(evenLen); continue; } } - // Either current span is empty or has exactly 1 trailing nibble; marshal an even-sized chunk. long remaining = sr.Remaining; if (remaining == 0) break; - - // If remaining is even overall, remaining will be >= 2 here; be defensive just in case. if (remaining == 1) - { ThrowInvalidOperationException(); - } if (!sr.TryCopyTo(twoNibbles)) - { - // Should not happen since CopyTo should copy 2 hex chars and bridge the spans. ThrowInvalidOperationException(); - } - Bytes.FromUtf8HexString(twoNibbles, output[..1]); - output = output[1..]; - sr.Advance(twoNibbles.Length); + Bytes.FromUtf8HexString(twoNibbles, MemoryMarshal.CreateSpan(ref Unsafe.Add(ref resultRef, outPos), 1)); + outPos++; + sr.Advance(2); } - if (!output.IsEmpty) + if (outPos != outLen) ThrowInvalidOperationException(); return result; @@ -161,7 +163,7 @@ public static void Convert(ref Utf8JsonReader reader, scoped Span span) } ReadOnlySpan hex = reader.ValueSpan; - if (hex.Length >= 2 && Unsafe.As(ref MemoryMarshal.GetReference(hex)) == _hexPrefix) + if (hex.Length >= 2 && Unsafe.As(ref MemoryMarshal.GetReference(hex)) == HexPrefix) { hex = hex[2..]; } @@ -183,16 +185,69 @@ public override void Write( Convert(writer, bytes, skipLeadingZeros: false); } + /// + /// Writes bytes as a hex string value (e.g. "0xabcd") using WriteRawValue. + /// [SkipLocalsInit] public static void Convert(Utf8JsonWriter writer, ReadOnlySpan bytes, bool skipLeadingZeros = true, bool addHexPrefix = true) { - Convert(writer, - bytes, - static (w, h) => w.WriteRawValue(h, skipInputValidation: true), skipLeadingZeros, addHexPrefix: addHexPrefix); + int leadingNibbleZeros = skipLeadingZeros ? bytes.CountLeadingNibbleZeros() : 0; + int nibblesCount = bytes.Length * 2; + + if (skipLeadingZeros && nibblesCount is not 0 && leadingNibbleZeros == nibblesCount) + { + WriteZeroValue(writer); + return; + } + + int prefixLength = addHexPrefix ? 2 : 0; + // +2 for surrounding quotes: "0xABCD..." + int rawLength = nibblesCount - leadingNibbleZeros + prefixLength + 2; + + byte[]? array = null; + Unsafe.SkipInit(out HexBuffer256 buffer); + Span hex = rawLength <= 256 + ? MemoryMarshal.CreateSpan(ref Unsafe.As(ref buffer), 256) + : (array = ArrayPool.Shared.Rent(rawLength)); + hex = hex[..rawLength]; + + // Build the JSON string value directly: "0x" + ref byte hexRef = ref MemoryMarshal.GetReference(hex); + hexRef = (byte)'"'; + int start = 1; + if (addHexPrefix) + { + Unsafe.As(ref Unsafe.Add(ref hexRef, 1)) = HexPrefix; + start = 3; + } + Unsafe.Add(ref hexRef, rawLength - 1) = (byte)'"'; + + int offset = leadingNibbleZeros >>> 1; + MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref MemoryMarshal.GetReference(bytes), offset), bytes.Length - offset) + .OutputBytesToByteHex( + MemoryMarshal.CreateSpan(ref Unsafe.Add(ref hexRef, start), rawLength - 1 - start), + extraNibble: (leadingNibbleZeros & 1) != 0); + // Hex chars (0-9, a-f) never need JSON escaping — bypass encoder entirely + writer.WriteRawValue(hex, skipInputValidation: true); + + if (array is not null) + ArrayPool.Shared.Return(array); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void WriteZeroValue(Utf8JsonWriter writer) => writer.WriteStringValue("0x0"u8); + + [InlineArray(256)] + private struct HexBuffer256 + { + private byte _element0; } public delegate void WriteHex(Utf8JsonWriter writer, ReadOnlySpan hex); + /// + /// Writes bytes as hex using a custom write action (e.g. for property names). + /// [SkipLocalsInit] public static void Convert( Utf8JsonWriter writer, @@ -202,48 +257,69 @@ public static void Convert( bool addQuotations = true, bool addHexPrefix = true) { - const int maxStackLength = 128; - const int stackLength = 256; - - var leadingNibbleZeros = skipLeadingZeros ? bytes.CountLeadingNibbleZeros() : 0; - var nibblesCount = bytes.Length * 2; + int leadingNibbleZeros = skipLeadingZeros ? bytes.CountLeadingNibbleZeros() : 0; + int nibblesCount = bytes.Length * 2; if (skipLeadingZeros && nibblesCount is not 0 && leadingNibbleZeros == nibblesCount) { - writer.WriteStringValue(Bytes.ZeroHexValue); + WriteZeroValue(writer, writeAction, addQuotations); return; } - var prefixLength = addHexPrefix ? 2 : 0; - var length = nibblesCount - leadingNibbleZeros + prefixLength + (addQuotations ? 2 : 0); + int prefixLength = addHexPrefix ? 2 : 0; + int quotesLength = addQuotations ? 2 : 0; + int length = nibblesCount - leadingNibbleZeros + prefixLength + quotesLength; byte[]? array = null; - if (length > maxStackLength) - array = ArrayPool.Shared.Rent(length); - - Span hex = (array ?? stackalloc byte[stackLength])[..length]; - var start = 0; - Index end = ^0; + Unsafe.SkipInit(out HexBuffer256 buffer); + Span hex = length <= 256 + ? MemoryMarshal.CreateSpan(ref Unsafe.As(ref buffer), 256) + : (array = ArrayPool.Shared.Rent(length)); + hex = hex[..length]; + + ref byte hexRef = ref MemoryMarshal.GetReference(hex); + int start = 0; + int endPad = 0; if (addQuotations) { - end = ^1; - hex[^1] = (byte)'"'; - hex[start++] = (byte)'"'; + hexRef = (byte)'"'; + Unsafe.Add(ref hexRef, length - 1) = (byte)'"'; + start = 1; + endPad = 1; } if (addHexPrefix) { - hex[start++] = (byte)'0'; - hex[start++] = (byte)'x'; + Unsafe.As(ref Unsafe.Add(ref hexRef, start)) = HexPrefix; + start += 2; } - Span output = hex[start..end]; - - ReadOnlySpan input = bytes[(leadingNibbleZeros / 2)..]; - input.OutputBytesToByteHex(output, extraNibble: (leadingNibbleZeros & 1) != 0); + ReadOnlySpan input = bytes[(leadingNibbleZeros >>> 1)..]; + input.OutputBytesToByteHex( + MemoryMarshal.CreateSpan(ref Unsafe.Add(ref hexRef, start), length - start - endPad), + extraNibble: (leadingNibbleZeros & 1) != 0); writeAction(writer, hex); if (array is not null) ArrayPool.Shared.Return(array); } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void WriteZeroValue(Utf8JsonWriter writer, WriteHex writeAction, bool addQuotations) + => writeAction(writer, addQuotations ? "\"0x0\""u8 : "0x0"u8); + + public override byte[] ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + byte[]? result = Convert(ref reader); + + if (result is null) + ThrowInvalidOperationException(); + + return result; + } + + public override void WriteAsPropertyName(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options) + { + Convert(writer, value, static (w, h) => w.WritePropertyName(h), skipLeadingZeros: false, addQuotations: false, addHexPrefix: true); + } } diff --git a/src/Nethermind/Nethermind.Core/JsonConverters/ChecksumAddressConverter.cs b/src/Nethermind/Nethermind.Core/JsonConverters/ChecksumAddressConverter.cs index 3e0268531e64..8044a65c3590 100644 --- a/src/Nethermind/Nethermind.Core/JsonConverters/ChecksumAddressConverter.cs +++ b/src/Nethermind/Nethermind.Core/JsonConverters/ChecksumAddressConverter.cs @@ -1,13 +1,9 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Net; using System.Runtime.CompilerServices; using System.Text.Json; -using System.Text.Json.Serialization; using Nethermind.Core; -using Nethermind.Core.Extensions; namespace Nethermind.Serialization.Json; diff --git a/src/Nethermind/Nethermind.Core/KeyValueStoreExtensions.cs b/src/Nethermind/Nethermind.Core/KeyValueStoreExtensions.cs index 906b39acf45d..0220b81ae103 100644 --- a/src/Nethermind/Nethermind.Core/KeyValueStoreExtensions.cs +++ b/src/Nethermind/Nethermind.Core/KeyValueStoreExtensions.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; using System.Runtime.CompilerServices; using Nethermind.Core.Buffers; using Nethermind.Core.Crypto; @@ -12,114 +11,108 @@ namespace Nethermind.Core { public static class KeyValueStoreExtensions { - public static IWriteBatch LikeABatch(this IWriteOnlyKeyValueStore keyValueStore) - { - return LikeABatch(keyValueStore, null); - } - - public static IWriteBatch LikeABatch(this IWriteOnlyKeyValueStore keyValueStore, Action? onDispose) - { - return new FakeWriteBatch(keyValueStore, onDispose); - } - - #region Getters - - public static byte[]? Get(this IReadOnlyKeyValueStore db, Hash256 key) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void GuardKey(Hash256 key) { #if DEBUG - if (key == Keccak.OfAnEmptyString) - { - throw new InvalidOperationException(); - } + if (key == Keccak.OfAnEmptyString) throw new InvalidOperationException(); #endif - - return db[key.Bytes]; } - /// - /// - /// /// - /// - /// Can return null or empty Span on missing key - /// - public static Span GetSpan(this IReadOnlyKeyValueStore db, Hash256 key) + extension(IReadOnlyKeyValueStore db) { -#if DEBUG - if (key == Keccak.OfAnEmptyString) + public byte[]? Get(Hash256 key) { - throw new InvalidOperationException(); + GuardKey(key); + return db[key.Bytes]; } -#endif - return db.GetSpan(key.Bytes); - } + /// + /// + /// + /// + /// Can return null or empty Span on missing key + /// + public Span GetSpan(Hash256 key) + { + GuardKey(key); + return db.GetSpan(key.Bytes); + } - public static bool KeyExists(this IReadOnlyKeyValueStore db, Hash256 key) - { -#if DEBUG - if (key == Keccak.OfAnEmptyString) + public bool KeyExists(Hash256 key) { - throw new InvalidOperationException(); + GuardKey(key); + return db.KeyExists(key.Bytes); } -#endif - return db.KeyExists(key.Bytes); - } + public bool KeyExists(long key) => db.KeyExists(key.ToBigEndianSpanWithoutLeadingZeros(out _)); - public static bool KeyExists(this IReadOnlyKeyValueStore db, long key) - { - return db.KeyExists(key.ToBigEndianSpanWithoutLeadingZeros(out _)); + public byte[]? Get(long key) => db[key.ToBigEndianSpanWithoutLeadingZeros(out _)]; } - public static byte[]? Get(this IReadOnlyKeyValueStore db, long key) => db[key.ToBigEndianSpanWithoutLeadingZeros(out _)]; - - /// - /// - /// - /// - /// - /// Can return null or empty Span on missing key - public static Span GetSpan(this IReadOnlyKeyValueStore db, long key) => db.GetSpan(key.ToBigEndianSpanWithoutLeadingZeros(out _)); - - public static MemoryManager? GetOwnedMemory(this IReadOnlyKeyValueStore db, ReadOnlySpan key) + extension(IWriteOnlyKeyValueStore db) { - Span span = db.GetSpan(key); - return span.IsNullOrEmpty() ? null : new DbSpanMemoryManager(db, span); - } + public IWriteBatch LikeABatch(Action? onDispose = null) => new FakeWriteBatch(db, onDispose); + public void Set(Hash256 key, byte[] value, WriteFlags writeFlags = WriteFlags.None) + { + if (db.PreferWriteByArray) + { + db.Set(key.Bytes, value, writeFlags); + } + else + { + db.PutSpan(key.Bytes, value, writeFlags); + } + } - #endregion + public void Set(Hash256 key, in CappedArray value, WriteFlags writeFlags = WriteFlags.None) + { + if (db.PreferWriteByArray && value.IsUncapped) + { + db.Set(key.Bytes, value.UnderlyingArray, writeFlags); + } + else + { + db.PutSpan(key.Bytes, value.AsSpan(), writeFlags); + } + } + public void Set(long blockNumber, Hash256 key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) + { + Span blockNumberPrefixedKey = stackalloc byte[40]; + GetBlockNumPrefixedKey(blockNumber, key, blockNumberPrefixedKey); + db.PutSpan(blockNumberPrefixedKey, value, writeFlags); + } - #region Setters + public void Set(in ValueHash256 key, Span value) + { + db.PutSpan(key.Bytes, value); + } - public static void Set(this IWriteOnlyKeyValueStore db, Hash256 key, byte[] value, WriteFlags writeFlags = WriteFlags.None) - { - if (db.PreferWriteByArray) + public void Delete(Hash256 key) { - db.Set(key.Bytes, value, writeFlags); - return; + db.Remove(key.Bytes); } - db.PutSpan(key.Bytes, value, writeFlags); - } - public static void Set(this IWriteOnlyKeyValueStore db, Hash256 key, in CappedArray value, WriteFlags writeFlags = WriteFlags.None) - { - if (value.IsUncapped && db.PreferWriteByArray) + public void Delete(long key) { - db.PutSpan(key.Bytes, value.AsSpan(), writeFlags); - return; + db.Remove(key.ToBigEndianSpanWithoutLeadingZeros(out _)); } - db.PutSpan(key.Bytes, value.AsSpan(), writeFlags); - } + [SkipLocalsInit] + public void Delete(long blockNumber, Hash256 hash) + { + Span key = stackalloc byte[40]; + GetBlockNumPrefixedKey(blockNumber, hash, key); + db.Remove(key); + } - public static void Set(this IWriteOnlyKeyValueStore db, long blockNumber, Hash256 key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) - { - Span blockNumberPrefixedKey = stackalloc byte[40]; - GetBlockNumPrefixedKey(blockNumber, key, blockNumberPrefixedKey); - db.PutSpan(blockNumberPrefixedKey, value, writeFlags); + public void Set(long key, byte[] value) + { + db[key.ToBigEndianSpanWithoutLeadingZeros(out _)] = value; + } } public static void GetBlockNumPrefixedKey(long blockNumber, ValueHash256 blockHash, Span output) @@ -127,35 +120,5 @@ public static void GetBlockNumPrefixedKey(long blockNumber, ValueHash256 blockHa blockNumber.WriteBigEndian(output); blockHash!.Bytes.CopyTo(output[8..]); } - - public static void Set(this IWriteOnlyKeyValueStore db, in ValueHash256 key, Span value) - { - db.PutSpan(key.Bytes, value); - } - - public static void Delete(this IWriteOnlyKeyValueStore db, Hash256 key) - { - db.Remove(key.Bytes); - } - - public static void Delete(this IWriteOnlyKeyValueStore db, long key) - { - db.Remove(key.ToBigEndianSpanWithoutLeadingZeros(out _)); - } - - [SkipLocalsInit] - public static void Delete(this IWriteOnlyKeyValueStore db, long blockNumber, Hash256 hash) - { - Span key = stackalloc byte[40]; - GetBlockNumPrefixedKey(blockNumber, hash, key); - db.Remove(key); - } - - public static void Set(this IWriteOnlyKeyValueStore db, long key, byte[] value) - { - db[key.ToBigEndianSpanWithoutLeadingZeros(out _)] = value; - } - - #endregion } } diff --git a/src/Nethermind/Nethermind.Core/Messages/BlockErrorMessages.cs b/src/Nethermind/Nethermind.Core/Messages/BlockErrorMessages.cs index b0863c6da056..8cb8db2d83ce 100644 --- a/src/Nethermind/Nethermind.Core/Messages/BlockErrorMessages.cs +++ b/src/Nethermind/Nethermind.Core/Messages/BlockErrorMessages.cs @@ -149,4 +149,7 @@ public static string InvalidDepositEventLayout(string error) => public static string ExceededBlockSizeLimit(int limit) => $"ExceededBlockSizeLimit: Exceeded block size limit of {limit} bytes."; + + public static string ReceiptCountMismatch(int expectedCount, int actualCount) => + $"ReceiptCountMismatch: Expected {expectedCount} receipts to match transaction count, but got {actualCount}."; } diff --git a/src/Nethermind/Nethermind.Core/Messages/TxErrorMessages.cs b/src/Nethermind/Nethermind.Core/Messages/TxErrorMessages.cs index 597cab98ce12..f3da0ea2ef0a 100644 --- a/src/Nethermind/Nethermind.Core/Messages/TxErrorMessages.cs +++ b/src/Nethermind/Nethermind.Core/Messages/TxErrorMessages.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only namespace Nethermind.Core.Messages; + public static class TxErrorMessages { public static string InvalidTxType(string name) => @@ -43,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 756bd0bfea1c..a01fc358a562 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/ProductInfo.cs b/src/Nethermind/Nethermind.Core/ProductInfo.cs index 42745ddd879e..b6a5371f2eee 100644 --- a/src/Nethermind/Nethermind.Core/ProductInfo.cs +++ b/src/Nethermind/Nethermind.Core/ProductInfo.cs @@ -18,11 +18,11 @@ static ProductInfo() var metadataAttrs = assembly.GetCustomAttributes()!; var productAttr = assembly.GetCustomAttribute()!; var versionAttr = assembly.GetCustomAttribute()!; - var timestamp = metadataAttrs - ?.FirstOrDefault(static a => a.Key.Equals("BuildTimestamp", StringComparison.Ordinal)) + var sourceDateEpoch = metadataAttrs + ?.FirstOrDefault(static a => a.Key.Equals("SourceDateEpoch", StringComparison.Ordinal)) ?.Value; - BuildTimestamp = long.TryParse(timestamp, out var t) + SourceDate = long.TryParse(sourceDateEpoch, out var t) ? DateTimeOffset.FromUnixTimeSeconds(t) : DateTimeOffset.MinValue; Name = productAttr?.Product ?? "Nethermind"; @@ -51,7 +51,7 @@ static ProductInfo() PublicClientId = ClientId; } - public static DateTimeOffset BuildTimestamp { get; } + public static DateTimeOffset SourceDate { get; } private static string FormatClientId(string formatString) { diff --git a/src/Nethermind/Nethermind.Core/ProgressLogger.cs b/src/Nethermind/Nethermind.Core/ProgressLogger.cs index f674106dab0d..589bb60f90c4 100644 --- a/src/Nethermind/Nethermind.Core/ProgressLogger.cs +++ b/src/Nethermind/Nethermind.Core/ProgressLogger.cs @@ -47,9 +47,9 @@ public void IncrementSkipped(int skipped = 1) Interlocked.Add(ref _skipped, skipped); } - public void SetMeasuringPoint() + public void SetMeasuringPoint(bool resetCompletion = true) { - UtcEndTime = null; + if (resetCompletion) UtcEndTime = null; if (UtcStartTime is not null) { @@ -169,7 +169,7 @@ public void LogProgress() _lastReportState = reportState; _logger.Info(reportString); } - SetMeasuringPoint(); + SetMeasuringPoint(resetCompletion: false); } private string DefaultFormatter() diff --git a/src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs b/src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs deleted file mode 100644 index 02e1a8addc58..000000000000 --- a/src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Threading.Tasks; -using Nethermind.Core.Collections; - -namespace Nethermind.Core.PubSub -{ - public class CompositePublisher(params IPublisher[] publishers) : IPublisher - { - public async Task PublishAsync(T data) where T : class - { - using ArrayPoolList tasks = new(publishers.Length); - for (int i = 0; i < publishers.Length; i++) - { - tasks.Add(publishers[i].PublishAsync(data)); - } - - await Task.WhenAll(tasks.AsSpan()); - } - - public void Dispose() - { - foreach (IPublisher publisher in publishers) - { - publisher.Dispose(); - } - } - } -} diff --git a/src/Nethermind/Nethermind.Core/RequestSizer/LatencyAndMessageSizeBasedRequestSizer.cs b/src/Nethermind/Nethermind.Core/RequestSizer/LatencyAndMessageSizeBasedRequestSizer.cs index dd68de6bc40b..b72515d1a2f6 100644 --- a/src/Nethermind/Nethermind.Core/RequestSizer/LatencyAndMessageSizeBasedRequestSizer.cs +++ b/src/Nethermind/Nethermind.Core/RequestSizer/LatencyAndMessageSizeBasedRequestSizer.cs @@ -61,7 +61,10 @@ public Task Run(IReadOnlyList cappedRequest = affectiveRequestSize == request.Count + ? request + : request.Slice(0, (int)affectiveRequestSize); + (TResponse result, long messageSize) = await func(cappedRequest); TimeSpan duration = Stopwatch.GetElapsedTime(startTime); if (messageSize > _maxResponseSize) { diff --git a/src/Nethermind/Nethermind.Core/Resettables/IResettable.cs b/src/Nethermind/Nethermind.Core/Resettables/IResettable.cs new file mode 100644 index 000000000000..3680f13bbf2a --- /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 000000000000..228bb5ebb866 --- /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/Result.cs b/src/Nethermind/Nethermind.Core/Result.cs index 578dee61df05..95bda8fe2489 100644 --- a/src/Nethermind/Nethermind.Core/Result.cs +++ b/src/Nethermind/Nethermind.Core/Result.cs @@ -1,18 +1,62 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using System.Diagnostics.CodeAnalysis; + namespace Nethermind.Core { - public class Result + public readonly record struct Result(ResultType ResultType, string? Error = null) { - private Result() { } + public static Result Fail(string error) => new() { ResultType = ResultType.Failure, Error = error }; + public static Result Success { get; } = new() { ResultType = ResultType.Success }; + public static implicit operator bool(Result result) => result.ResultType == ResultType.Success; + public static implicit operator Result(string? error) => error is null ? Success : Fail(error); + // Required for short-circuit && operator + public static bool operator true(Result result) => result.ResultType == ResultType.Success; + public static bool operator false(Result result) => result.ResultType == ResultType.Failure; + // This provides the short-circuit behavior + public static Result operator &(Result left, Result right) => left ? right : left; // If left fails, return left (short-circuit) + public override string ToString() => ResultType == ResultType.Success ? "Success" : $"Failure: {Error}"; + } - public ResultType ResultType { get; set; } + public readonly record struct Result(ResultType ResultType, TData? Data = default, string? Error = null) + { + public static Result Fail(string error, TData? data = default) => new() { ResultType = ResultType.Failure, Error = error, Data = data }; + public static Result Success(TData data) => new() { ResultType = ResultType.Success, Data = data }; + public static implicit operator bool(Result result) => result.IsSuccess; + public static implicit operator Result(TData data) => Success(data); + public static implicit operator Result(string error) => Fail(error, typeof(TData) == typeof(byte[]) ? (TData)(object)Array.Empty() : default); + public static implicit operator (TData?, bool)(Result result) => (result.Data, result.IsSuccess); + public static implicit operator Result(Result result) => result.Error; + // Required for short-circuit && operator + public static bool operator true(Result result) => result.IsSuccess; + public static bool operator false(Result result) => result.IsError; + public static explicit operator TData(Result result) => + result.Data ?? throw new InvalidOperationException($"Cannot convert {nameof(Result<>)} to TData when {nameof(result.ResultType)} is {nameof(ResultType.Failure)} with error: {result.Error}"); - public string? Error { get; set; } + // This provides the short-circuit behavior + public static Result operator &(Result left, Result right) => left ? right : left; // If left fails, return left (short-circuit) - public static Result Fail(string error) => new() { ResultType = ResultType.Failure, Error = error }; + [MemberNotNullWhen(true, nameof(Data))] + [MemberNotNullWhen(false, nameof(Error))] + public bool IsSuccess => ResultType == ResultType.Success; - public static Result Success { get; } = new() { ResultType = ResultType.Success }; + [MemberNotNullWhen(false, nameof(Data))] + [MemberNotNullWhen(true, nameof(Error))] + public bool IsError => !IsSuccess; + + public void Deconstruct(out TData? result, out bool success) + { + result = Data; + success = ResultType == ResultType.Success; + } + + public bool Success([NotNullWhen(true)] out TData? result, [NotNullWhen(false)] out string? error) + { + result = Data; + error = Error; + return ResultType == ResultType.Success; + } } } diff --git a/src/Nethermind/Nethermind.Core/SlotTime.cs b/src/Nethermind/Nethermind.Core/SlotTime.cs index 147710f28543..c0d1e48f7ebc 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 f5d78c4c02a4..da4c55202fe8 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 2960b6443c7f..fa50589138e6 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,7 +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. + /// + public long Eip2935RingBufferSize { get; } /// /// SELFDESTRUCT only in same transaction @@ -348,6 +352,9 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec /// OP Holocene bool IsOpHoloceneEnabled { get; } + /// OP Jovian + bool IsOpJovianEnabled { get; } + // OP Isthmus bool IsOpIsthmusEnabled { get; } @@ -376,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 @@ -390,84 +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; - public bool IsEip7594Enabled { get; } /// @@ -497,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 /// diff --git a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpecExtensions.cs b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpecExtensions.cs new file mode 100644 index 000000000000..ba4d778cac71 --- /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 7abe0168014b..76cb758023d1 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 37ec9b2c548a..eff103b6c296 100644 --- a/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs +++ b/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs @@ -70,29 +70,32 @@ 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; public virtual bool IsRip7212Enabled => spec.IsRip7212Enabled; public virtual bool IsOpGraniteEnabled => spec.IsOpGraniteEnabled; public virtual bool IsOpHoloceneEnabled => spec.IsOpHoloceneEnabled; + public virtual bool IsOpJovianEnabled => spec.IsOpJovianEnabled; public virtual bool IsOpIsthmusEnabled => spec.IsOpIsthmusEnabled; public virtual bool IsEip7623Enabled => spec.IsEip7623Enabled; public virtual bool IsEip7825Enabled { get; init; } = spec.IsEip7825Enabled; @@ -102,57 +105,15 @@ 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 virtual bool IsEip7939Enabled => spec.IsEip7939Enabled; + public virtual bool IsEip7907Enabled => spec.IsEip7907Enabled; + public virtual bool IsRip7728Enabled => spec.IsRip7728Enabled; } diff --git a/src/Nethermind/Nethermind.Core/Specs/SpecProviderDecorator.cs b/src/Nethermind/Nethermind.Core/Specs/SpecProviderDecorator.cs index b7856c4f7f22..da475887053a 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/StorageCell.cs b/src/Nethermind/Nethermind.Core/StorageCell.cs index a35bc2eda97c..d28c5648f364 100644 --- a/src/Nethermind/Nethermind.Core/StorageCell.cs +++ b/src/Nethermind/Nethermind.Core/StorageCell.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -13,12 +14,13 @@ namespace Nethermind.Core { [DebuggerDisplay("{Address}->{Index}")] - public readonly struct StorageCell : IEquatable + public readonly struct StorageCell : IEquatable, IHash64bit { + private readonly AddressAsKey _address; private readonly UInt256 _index; private readonly bool _isHash; - public Address Address { get; } + public Address Address => _address.Value; public bool IsHash => _isHash; public UInt256 Index => _index; @@ -33,21 +35,45 @@ private ValueHash256 GetHash() public StorageCell(Address address, in UInt256 index) { - Address = address; + _address = address; _index = index; } public StorageCell(Address address, ValueHash256 hash) { - Address = address; + _address = address; _index = Unsafe.As(ref hash); _isHash = true; } - public bool Equals(StorageCell other) => - _isHash == other._isHash && - Unsafe.As>(ref Unsafe.AsRef(in _index)) == Unsafe.As>(ref Unsafe.AsRef(in other._index)) && - Address.Equals(other.Address); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(in StorageCell other) + { + if (_isHash != other._isHash) + return false; + + if (Unsafe.As>(ref Unsafe.AsRef(in _index)) != + Unsafe.As>(ref Unsafe.AsRef(in other._index))) + return false; + + // Inline 20-byte Address comparison: avoids the Address.Equals call + // that the JIT refuses to inline when called from deep inline chains + // (e.g. SeqlockCache.TryGetValue). Address.Bytes is always exactly 20 bytes. + Address a = _address.Value; + Address b = other._address.Value; + if (ReferenceEquals(a, b)) + return true; + + ref byte ab = ref MemoryMarshal.GetArrayDataReference(a.Bytes); + ref byte bb = ref MemoryMarshal.GetArrayDataReference(b.Bytes); + return Unsafe.As>(ref ab) == Unsafe.As>(ref bb) + && Unsafe.As(ref Unsafe.Add(ref ab, 16)) == Unsafe.As(ref Unsafe.Add(ref bb, 16)); + } + + public bool Equals(StorageCell other) => Equals(in other); + + public long GetHashCode64() + => SpanExtensions.FastHash64For32Bytes(ref Unsafe.As(ref Unsafe.AsRef(in _index))) ^ _address.Value.GetHashCode64(); public override bool Equals(object? obj) { @@ -62,12 +88,12 @@ public override bool Equals(object? obj) public override int GetHashCode() { int hash = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in _index), 1)).FastHash(); - return hash ^ Address.GetHashCode(); + return hash ^ _address.Value.GetHashCode(); } public override string ToString() { - return $"{Address}.{Index}"; + return $"{_address.Value}.{Index}"; } } } diff --git a/src/Nethermind/Nethermind.Core/Tasks/WaitForPassingTasks.cs b/src/Nethermind/Nethermind.Core/Tasks/WaitForPassingTasks.cs index 9e093aded1b8..98cd2e116a70 100644 --- a/src/Nethermind/Nethermind.Core/Tasks/WaitForPassingTasks.cs +++ b/src/Nethermind/Nethermind.Core/Tasks/WaitForPassingTasks.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; namespace Nethermind.Core.Tasks; diff --git a/src/Nethermind/Nethermind.Core/Threading/InterlockedEx.cs b/src/Nethermind/Nethermind.Core/Threading/InterlockedEx.cs new file mode 100644 index 000000000000..b5457b85cbb4 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Threading/InterlockedEx.cs @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading; + +namespace Nethermind.Core.Threading; + +public class InterlockedEx +{ + /// + /// Atomically sets a field to the maximum of the field's current value and a specified value. + /// + /// The field to update + /// The value to compare with the current value + /// The original value of the field + public static int Max(ref int location, int value) + { + int current, newValue; + do + { + current = location; + newValue = Math.Max(current, value); + + // If current is already >= value, no need to update + if (current >= value) + return current; + } + while (Interlocked.CompareExchange(ref location, newValue, current) != current); + + return current; + } + + /// + /// Atomically sets a field to the maximum of the field's current value and a specified value. + /// + /// The field to update + /// The value to compare with the current value + /// The original value of the field + public static long Max(ref long location, long value) + { + long current, newValue; + do + { + current = location; + newValue = Math.Max(current, value); + + // If current is already >= value, no need to update + if (current >= value) + return current; + } + while (Interlocked.CompareExchange(ref location, newValue, current) != current); + + return current; + } + + /// + /// Atomically sets a field to the minimum of the field's current value and a specified value. + /// + /// The field to update + /// The value to compare with the current value + /// The original value of the field + public static int Min(ref int location, int value) + { + int current, newValue; + do + { + current = location; + newValue = Math.Min(current, value); + + // If current is already <= value, no need to update + if (current <= value) + return current; + } + while (Interlocked.CompareExchange(ref location, newValue, current) != current); + + return current; + } + + /// + /// Atomically sets a field to the minimum of the field's current value and a specified value. + /// + /// The field to update + /// The value to compare with the current value + /// The original value of the field + public static long Min(ref long location, long value) + { + long current, newValue; + do + { + current = location; + newValue = Math.Min(current, value); + + // If current is already <= value, no need to update + if (current <= value) + return current; + } + while (Interlocked.CompareExchange(ref location, newValue, current) != current); + + return current; + } +} diff --git a/src/Nethermind/Nethermind.Core/Threading/ZeroContentionCounter.cs b/src/Nethermind/Nethermind.Core/Threading/ZeroContentionCounter.cs index c2c7ccdc1f94..dcb143fbb30d 100644 --- a/src/Nethermind/Nethermind.Core/Threading/ZeroContentionCounter.cs +++ b/src/Nethermind/Nethermind.Core/Threading/ZeroContentionCounter.cs @@ -7,6 +7,7 @@ using System.Threading; namespace Nethermind.Core.Threading; + public class ZeroContentionCounter { private readonly ThreadLocal _threadLocal = new(static () => new BoxedLong(), trackAllValues: true); diff --git a/src/Nethermind/Nethermind.Core/Transaction.cs b/src/Nethermind/Nethermind.Core/Transaction.cs index 3d5bf509b479..c672eb29a2ac 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; @@ -332,12 +344,17 @@ public void CopyTo(Transaction tx) tx._size = _size; tx.AuthorizationList = AuthorizationList; } + + public virtual ProofVersion? GetProofVersion() => + SupportsBlobs && this is { NetworkWrapper: ShardBlobNetworkWrapper { Version: var version } } + ? version + : null; } /// /// Transaction that is generated by the node to be included in future block. After included in the block can be handled as regular . /// - public sealed class GeneratedTransaction : Transaction { } + public sealed class GeneratedTransaction : Transaction; /// /// System transaction that is to be executed by the node without including in the block. @@ -350,9 +367,7 @@ public sealed class SystemTransaction : Transaction /// /// System call like transaction that is to be executed by the node without including in the block. /// - public sealed class SystemCall : Transaction - { - } + public sealed class SystemCall : Transaction; /// /// Used inside Transaction::GetSize to calculate encoded transaction size diff --git a/src/Nethermind/Nethermind.Core/TransactionExtensions.cs b/src/Nethermind/Nethermind.Core/TransactionExtensions.cs index fc40ebd35766..cf757611c76a 100644 --- a/src/Nethermind/Nethermind.Core/TransactionExtensions.cs +++ b/src/Nethermind/Nethermind.Core/TransactionExtensions.cs @@ -9,61 +9,56 @@ namespace Nethermind.Core { public static class TransactionExtensions { - public static bool IsSystem(this Transaction tx) => - tx is SystemTransaction || tx.SenderAddress == Address.SystemUser || tx.IsOPSystemTransaction; - - public static bool IsFree(this Transaction tx) => tx.IsSystem() || tx.IsServiceTransaction; - - public static bool TryCalculatePremiumPerGas(this Transaction tx, in UInt256 baseFeePerGas, out UInt256 premiumPerGas) + extension(Transaction tx) { - bool freeTransaction = tx.IsFree(); - UInt256 feeCap = tx.Supports1559 ? tx.MaxFeePerGas : tx.MaxPriorityFeePerGas; - if (baseFeePerGas > feeCap) - { - premiumPerGas = UInt256.Zero; - return freeTransaction; - } + public bool IsSystem() => tx is SystemTransaction || tx.SenderAddress == Address.SystemUser || tx.IsOPSystemTransaction; - premiumPerGas = UInt256.Min(tx.MaxPriorityFeePerGas, feeCap - baseFeePerGas); - return true; - } + public bool IsFree() => tx.IsSystem() || tx.IsServiceTransaction; - public static UInt256 CalculateTransactionPotentialCost(this Transaction tx, bool eip1559Enabled, in UInt256 baseFee) - { - if (eip1559Enabled) + public bool TryCalculatePremiumPerGas(in UInt256 baseFeePerGas, out UInt256 premiumPerGas) { - UInt256 effectiveGasPrice = tx.CalculateEffectiveGasPrice(eip1559Enabled, baseFee); - if (tx.IsServiceTransaction) + bool freeTransaction = tx.IsFree(); + UInt256 feeCap = tx.Supports1559 ? tx.MaxFeePerGas : tx.MaxPriorityFeePerGas; + if (baseFeePerGas > feeCap) { - effectiveGasPrice = UInt256.Zero; + premiumPerGas = UInt256.Zero; + return freeTransaction; } - return effectiveGasPrice * (ulong)tx.GasLimit + tx.ValueRef; + premiumPerGas = UInt256.Min(tx.MaxPriorityFeePerGas, feeCap - baseFeePerGas); + return true; } - return tx.MaxPriorityFeePerGas * (ulong)tx.GasLimit + tx.ValueRef; - } - - public static UInt256 CalculateEffectiveGasPrice(this Transaction tx, bool eip1559Enabled, in UInt256 baseFee) - { - if (eip1559Enabled) + public UInt256 CalculateTransactionPotentialCost(bool eip1559Enabled, in UInt256 baseFee) { - if (UInt256.AddOverflow(tx.MaxPriorityFeePerGas, baseFee, out UInt256 effectiveFee)) + if (eip1559Enabled) { - return tx.MaxFeePerGas; + UInt256 effectiveGasPrice = tx.IsServiceTransaction switch + { + true => UInt256.Zero, + _ => tx.CalculateEffectiveGasPrice(eip1559Enabled, baseFee) + }; + + return effectiveGasPrice * (ulong)tx.GasLimit + tx.ValueRef; } - return UInt256.Min(tx.MaxFeePerGas, effectiveFee); + return tx.MaxPriorityFeePerGas * (ulong)tx.GasLimit + tx.ValueRef; } - return tx.MaxPriorityFeePerGas; - } + public UInt256 CalculateEffectiveGasPrice(bool eip1559Enabled, in UInt256 baseFee) => + !eip1559Enabled ? tx.MaxPriorityFeePerGas + : UInt256.AddOverflow(tx.MaxPriorityFeePerGas, baseFee, out UInt256 effectiveFee) ? tx.MaxFeePerGas + : UInt256.Min(tx.MaxFeePerGas, effectiveFee); - public static UInt256 CalculateMaxPriorityFeePerGas(this Transaction tx, bool eip1559Enabled, in UInt256 baseFee) => - eip1559Enabled ? UInt256.Min(tx.MaxPriorityFeePerGas, tx.MaxFeePerGas > baseFee ? tx.MaxFeePerGas - baseFee : 0) : tx.MaxPriorityFeePerGas; + public UInt256 CalculateMaxPriorityFeePerGas(bool eip1559Enabled, in UInt256 baseFee) => + eip1559Enabled ? UInt256.Min(tx.MaxPriorityFeePerGas, tx.MaxFeePerGas > baseFee ? tx.MaxFeePerGas - baseFee : 0) : tx.MaxPriorityFeePerGas; - public static bool IsAboveInitCode(this Transaction tx, IReleaseSpec spec) => - tx.IsContractCreation && spec.IsEip3860Enabled && tx.DataLength > spec.MaxInitCodeSize; + public bool IsAboveInitCode(IReleaseSpec spec) => + tx.IsContractCreation && spec.IsEip3860Enabled && tx.DataLength > spec.MaxInitCodeSize; + + public ulong GetBlobGas() => (uint)tx.GetBlobCount() * Eip4844Constants.GasPerBlob; + public int GetBlobCount() => tx.BlobVersionedHashes?.Length ?? 0; + } public static bool TryGetByTxType(this T?[] array, TxType txType, [NotNullWhen(true)] out T? item) { @@ -77,8 +72,5 @@ public static bool TryGetByTxType(this T?[] array, TxType txType, [NotNullWhe item = array[type]; return item != null; } - - public static ulong GetBlobGas(this Transaction tx) => (uint)tx.GetBlobCount() * Eip4844Constants.GasPerBlob; - public static int GetBlobCount(this Transaction tx) => tx.BlobVersionedHashes?.Length ?? 0; } } diff --git a/src/Nethermind/Nethermind.Core/TransactionReceipt.cs b/src/Nethermind/Nethermind.Core/TransactionReceipt.cs index 1f9ddee91e8f..a310bb5a2454 100644 --- a/src/Nethermind/Nethermind.Core/TransactionReceipt.cs +++ b/src/Nethermind/Nethermind.Core/TransactionReceipt.cs @@ -9,7 +9,7 @@ namespace Nethermind.Core { public class TxReceipt { - private Bloom? _boom; + private Bloom? _bloom; public TxReceipt() { @@ -61,13 +61,13 @@ public TxReceipt(TxReceipt other) /// Removed in EIP-658 /// public Hash256? PostTransactionState { get; set; } - public Bloom? Bloom { get => _boom ?? CalculateBloom(); set => _boom = value; } + public Bloom? Bloom { get => _bloom ?? CalculateBloom(); set => _bloom = value; } public LogEntry[]? Logs { get; set; } public string? Error { get; set; } public Bloom CalculateBloom() - => _boom = Logs?.Length == 0 ? Bloom.Empty : new Bloom(Logs); + => _bloom = Logs?.Length == 0 ? Bloom.Empty : new Bloom(Logs); } public ref struct TxReceiptStructRef diff --git a/src/Nethermind/Nethermind.Core/TypeDiscovery.cs b/src/Nethermind/Nethermind.Core/TypeDiscovery.cs index fcd0dce56bbb..0cdd6b87931c 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.Core/Utils/ConcurrentWriteBatcher.cs b/src/Nethermind/Nethermind.Core/Utils/ConcurrentWriteBatcher.cs deleted file mode 100644 index 9ee5829dc20f..000000000000 --- a/src/Nethermind/Nethermind.Core/Utils/ConcurrentWriteBatcher.cs +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Concurrent; -using System.Threading; - -namespace Nethermind.Core.Utils; - -/// -/// Batches writes into a set of concurrent batches. For cases where throughput matter, but not atomicity. -/// -public class ConcurrentWriteBatcher : IWriteBatch -{ - private const long PersistEveryNWrite = 10000; - - private long _counter = 0; - private readonly ConcurrentQueue _batches = new(); - private readonly IKeyValueStoreWithBatching _underlyingDb; - private bool _disposing = false; - - public ConcurrentWriteBatcher(IKeyValueStoreWithBatching underlyingDb) - { - _underlyingDb = underlyingDb; - } - - public void Dispose() - { - _disposing = true; - foreach (IWriteBatch batch in _batches) - { - batch.Dispose(); - } - } - - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { - IWriteBatch currentBatch = RentWriteBatch(); - currentBatch.PutSpan(key, value, flags); - ReturnWriteBatch(currentBatch); - } - - public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - { - IWriteBatch currentBatch = RentWriteBatch(); - currentBatch.Set(key, value, flags); - ReturnWriteBatch(currentBatch); - } - - private void ReturnWriteBatch(IWriteBatch currentBatch) - { - long val = Interlocked.Increment(ref _counter); - if (val % PersistEveryNWrite == 0) - { - currentBatch.Dispose(); - } - else - { - _batches.Enqueue(currentBatch); - } - } - - private IWriteBatch RentWriteBatch() - { - if (_disposing) throw new InvalidOperationException("Trying to set while disposing"); - if (!_batches.TryDequeue(out IWriteBatch? currentBatch)) - { - currentBatch = _underlyingDb.StartWriteBatch(); - } - - return currentBatch; - } -} diff --git a/src/Nethermind/Nethermind.Crypto/BlobProofsManagerV0.cs b/src/Nethermind/Nethermind.Crypto/BlobProofsManagerV0.cs index d11f61ca42b7..b57b5621b211 100644 --- a/src/Nethermind/Nethermind.Crypto/BlobProofsManagerV0.cs +++ b/src/Nethermind/Nethermind.Crypto/BlobProofsManagerV0.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; using CkzgLib; using Nethermind.Core; using Nethermind.Core.Collections; @@ -55,6 +54,11 @@ public bool ValidateLengths(ShardBlobNetworkWrapper wrapper) public bool ValidateProofs(ShardBlobNetworkWrapper wrapper) { + if (wrapper.Version is not ProofVersion.V0) + { + return false; + } + if (wrapper.Blobs.Length is 1 && wrapper.Commitments.Length is 1 && wrapper.Proofs.Length is 1) { try diff --git a/src/Nethermind/Nethermind.Crypto/BlobProofsManagerV1.cs b/src/Nethermind/Nethermind.Crypto/BlobProofsManagerV1.cs index adba4c817ba6..3fa12654ca78 100644 --- a/src/Nethermind/Nethermind.Crypto/BlobProofsManagerV1.cs +++ b/src/Nethermind/Nethermind.Crypto/BlobProofsManagerV1.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; using CkzgLib; using Nethermind.Core; using Nethermind.Core.Collections; @@ -15,9 +14,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 +50,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 +66,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) { @@ -75,14 +80,22 @@ public bool ValidateLengths(ShardBlobNetworkWrapper wrapper) public bool ValidateProofs(ShardBlobNetworkWrapper wrapper) { - 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); + if (wrapper.Version is not ProofVersion.V1) + { + return false; + } + + 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); @@ -98,7 +111,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/BlsSigner.cs b/src/Nethermind/Nethermind.Crypto/BlsSigner.cs index f8a5fcf35d40..3beef7a9e550 100644 --- a/src/Nethermind/Nethermind.Crypto/BlsSigner.cs +++ b/src/Nethermind/Nethermind.Crypto/BlsSigner.cs @@ -44,7 +44,7 @@ public static Signature Sign(Bls.SecretKey sk, ReadOnlySpan message) public static bool Verify(G1Affine publicKey, Signature signature, ReadOnlySpan message) { int len = 2 * GT.Sz; - using ArrayPoolList buf = new(len, len); + using ArrayPoolListRef buf = new(len, len); GT p1 = new(buf.AsSpan()[..GT.Sz]); p1.MillerLoop(signature.Point, G1Affine.Generator(stackalloc long[G1Affine.Sz])); diff --git a/src/Nethermind/Nethermind.Crypto/CryptoRandom.cs b/src/Nethermind/Nethermind.Crypto/CryptoRandom.cs index 0fd8279bc2dc..96dc3fb96de2 100644 --- a/src/Nethermind/Nethermind.Crypto/CryptoRandom.cs +++ b/src/Nethermind/Nethermind.Crypto/CryptoRandom.cs @@ -11,7 +11,6 @@ namespace Nethermind.Crypto public class CryptoRandom : ICryptoRandom { private readonly RandomNumberGenerator _secureRandom = RandomNumberGenerator.Create(); - private readonly Random _random = new(); public void GenerateRandomBytes(Span bytes) { @@ -25,10 +24,11 @@ public byte[] GenerateRandomBytes(int length) return bytes; } - [RequiresSecurityReview("There should be no unsecured method in a class that suggests security")] public int NextInt(int max) { - return _random.Next(max); + // Use cryptographically secure RNG; preserve Random.Next behavior for non-positive max + // (Random.Next(0) returns 0; negatives throw). This keeps compatibility. + return max <= 0 ? 0 : RandomNumberGenerator.GetInt32(max); } public void Dispose() diff --git a/src/Nethermind/Nethermind.Crypto/Ecdsa.cs b/src/Nethermind/Nethermind.Crypto/Ecdsa.cs index 36b7ba7f49f7..e980ed857d2e 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 73f844d8acad..fa4f58359e0f 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/EthereumEcdsaExtensions.cs b/src/Nethermind/Nethermind.Crypto/EthereumEcdsaExtensions.cs index 0381f44b97e5..3574d34137a7 100644 --- a/src/Nethermind/Nethermind.Crypto/EthereumEcdsaExtensions.cs +++ b/src/Nethermind/Nethermind.Crypto/EthereumEcdsaExtensions.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.IO; using Nethermind.Core; using Nethermind.Core.Crypto; diff --git a/src/Nethermind/Nethermind.Crypto/IBlobProofsManager.cs b/src/Nethermind/Nethermind.Crypto/IBlobProofsManager.cs index d87000a040be..6da3f60a36d3 100644 --- a/src/Nethermind/Nethermind.Crypto/IBlobProofsManager.cs +++ b/src/Nethermind/Nethermind.Crypto/IBlobProofsManager.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Linq; using Nethermind.Core; namespace Nethermind.Crypto; @@ -41,8 +40,13 @@ public interface IBlobProofsVerifier { bool ValidateLengths(ShardBlobNetworkWrapper blobs); - public bool ValidateHashes(ShardBlobNetworkWrapper blobs, byte[][] blobVersionedHashes) + public bool ValidateHashes(ShardBlobNetworkWrapper blobs, ReadOnlySpan blobVersionedHashes) { + if (blobs.Blobs.Length != blobVersionedHashes.Length) + { + return false; + } + Span hash = stackalloc byte[Eip4844Constants.BytesPerBlobVersionedHash]; for (int i = 0; i < blobVersionedHashes.Length; i++) diff --git a/src/Nethermind/Nethermind.Crypto/IEthereumEcdsa.cs b/src/Nethermind/Nethermind.Crypto/IEthereumEcdsa.cs index b4a02f65c09f..bec37cbaef7b 100644 --- a/src/Nethermind/Nethermind.Crypto/IEthereumEcdsa.cs +++ b/src/Nethermind/Nethermind.Crypto/IEthereumEcdsa.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Nethermind.Core; using Nethermind.Core.Crypto; diff --git a/src/Nethermind/Nethermind.Crypto/IHashResolver.cs b/src/Nethermind/Nethermind.Crypto/IHashResolver.cs index 35ed68cfc0b7..bd158d35b781 100644 --- a/src/Nethermind/Nethermind.Crypto/IHashResolver.cs +++ b/src/Nethermind/Nethermind.Crypto/IHashResolver.cs @@ -4,6 +4,7 @@ using Nethermind.Core.Crypto; namespace Nethermind.Crypto; + public interface IHashResolver { ValueHash256 CalculateHash(); diff --git a/src/Nethermind/Nethermind.Crypto/KeccakRlpStream.cs b/src/Nethermind/Nethermind.Crypto/KeccakRlpStream.cs index ae039b12582b..636a56cbb8f2 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 3e67ebcc309a..3d0dd4151e00 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/PrivateKeyGenerator.cs b/src/Nethermind/Nethermind.Crypto/PrivateKeyGenerator.cs index 9a872b678908..f864b69e8904 100644 --- a/src/Nethermind/Nethermind.Crypto/PrivateKeyGenerator.cs +++ b/src/Nethermind/Nethermind.Crypto/PrivateKeyGenerator.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; +using System.Linq; namespace Nethermind.Crypto { @@ -22,13 +24,21 @@ public PrivateKeyGenerator(ICryptoRandom cryptoRandom) } public PrivateKey Generate() + { + return Generate(1).First(); + } + public IEnumerable Generate(int number) { do { var bytes = _cryptoRandom.GenerateRandomBytes(32); if (SecP256k1.VerifyPrivateKey(bytes)) { - return new PrivateKey(bytes); + yield return new PrivateKey(bytes); + if (--number == 0) + { + yield break; + } } } while (true); } diff --git a/src/Nethermind/Nethermind.Crypto/Ripemd.cs b/src/Nethermind/Nethermind.Crypto/Ripemd.cs index fc02c2e524ef..8fc68db757e3 100644 --- a/src/Nethermind/Nethermind.Crypto/Ripemd.cs +++ b/src/Nethermind/Nethermind.Crypto/Ripemd.cs @@ -1,25 +1,29 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using Nethermind.Core.Extensions; using Org.BouncyCastle.Crypto.Digests; -namespace Nethermind.Crypto +namespace Nethermind.Crypto; + +public static class Ripemd { - public static class Ripemd + const int HashOutputLength = 32; + + public static byte[] Compute(ReadOnlySpan input) { - public static byte[] Compute(byte[] input) - { - var digest = new RipeMD160Digest(); - digest.BlockUpdate(input, 0, input.Length); - var result = new byte[digest.GetDigestSize()]; - digest.DoFinal(result, 0); - return result; - } + RipeMD160Digest digest = new(); + digest.BlockUpdate(input); + byte[] result = new byte[HashOutputLength]; + int length = digest.GetDigestSize(); + Span span = result.AsSpan(HashOutputLength - length, length); + digest.DoFinal(span); + return result; + } - public static string ComputeString(byte[] input) - { - return Compute(input).ToHexString(false); - } + public static string ComputeString(ReadOnlySpan input) + { + return Compute(input).ToHexString(false); } } diff --git a/src/Nethermind/Nethermind.Crypto/Secp256K1Curve.cs b/src/Nethermind/Nethermind.Crypto/Secp256K1Curve.cs index 83c8e8fb12b1..1d8559f421e7 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 cbe4fb1aea4c..000000000000 --- 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 d9fe6cb55197..94633dfd94f6 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs @@ -2,22 +2,25 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; -using System.Linq; using Nethermind.Core; +using Nethermind.Core.Buffers; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using RocksDbSharp; using IWriteBatch = Nethermind.Core.IWriteBatch; namespace Nethermind.Db.Rocks; -public class ColumnDb : IDb +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,37 +31,47 @@ public ColumnDb(RocksDb rocksDb, DbOnTheRocks mainDb, string name) Name = name; _iteratorManager = new DbOnTheRocks.IteratorManager(_rocksDb, _columnFamily, _mainDb._readAheadReadOptions); + _reader = new RocksDbReader(mainDb, mainDb.CreateReadOptions, _iteratorManager, _columnFamily); } - public void Dispose() - { - _iteratorManager.Dispose(); - } - + public void Dispose() => _iteratorManager.Dispose(); public string Name { get; } - public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - { - return _mainDb.GetWithColumnFamily(key, _columnFamily, _iteratorManager, flags); - } + byte[]? IReadOnlyKeyValueStore.Get(ReadOnlySpan key, ReadFlags flags) => _reader.Get(key, flags); + + Span IReadOnlyKeyValueStore.GetSpan(scoped ReadOnlySpan key, ReadFlags flags) => _reader.GetSpan(key, flags); - public Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + MemoryManager? IReadOnlyKeyValueStore.GetOwnedMemory(ReadOnlySpan key, ReadFlags flags) { - return _mainDb.GetSpanWithColumnFamily(key, _columnFamily, flags); + Span span = ((IReadOnlyKeyValueStore)this).GetSpan(key, flags); + return span.IsNullOrEmpty() ? null : new DbSpanMemoryManager(this, span); } - public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - { + + int IReadOnlyKeyValueStore.Get(scoped ReadOnlySpan key, Span output, ReadFlags flags) => _reader.Get(key, output, flags); + + bool IReadOnlyKeyValueStore.KeyExists(ReadOnlySpan key) => _reader.KeyExists(key); + + void IReadOnlyKeyValueStore.DangerousReleaseMemory(in ReadOnlySpan key) => _reader.DangerousReleaseMemory(key); + + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) => _mainDb.SetWithColumnFamily(key, _columnFamily, value, flags); - } - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) - { + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) => _mainDb.SetWithColumnFamily(key, _columnFamily, value, writeFlags); - } - public KeyValuePair[] this[byte[][] keys] => - _rocksDb.MultiGet(keys, keys.Select(k => _columnFamily).ToArray()); + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) => + _mainDb.MergeWithColumnFamily(key, _columnFamily, value, writeFlags); + + 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) { @@ -78,77 +91,86 @@ public IEnumerable GetAllValues(bool ordered = false) return _mainDb.GetAllValuesCore(iterator); } - public IWriteBatch StartWriteBatch() - { - return new ColumnsDbWriteBatch(this, (DbOnTheRocks.RocksDbWriteBatch)_mainDb.StartWriteBatch()); - } + public IWriteBatch StartWriteBatch() => new ColumnsDbWriteBatch(this, (DbOnTheRocks.RocksDbWriteBatch)_mainDb.StartWriteBatch()); - private class ColumnsDbWriteBatch : IWriteBatch + private class ColumnsDbWriteBatch(ColumnDb columnDb, DbOnTheRocks.RocksDbWriteBatch underlyingWriteBatch) + : IWriteBatch { - private readonly ColumnDb _columnDb; - private readonly DbOnTheRocks.RocksDbWriteBatch _underlyingWriteBatch; + public void Dispose() => underlyingWriteBatch.Dispose(); - public ColumnsDbWriteBatch(ColumnDb columnDb, DbOnTheRocks.RocksDbWriteBatch underlyingWriteBatch) - { - _columnDb = columnDb; - _underlyingWriteBatch = underlyingWriteBatch; - } - - public void Dispose() - { - _underlyingWriteBatch.Dispose(); - } + public void Clear() => underlyingWriteBatch.Clear(); public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { if (value is null) { - _underlyingWriteBatch.Delete(key, _columnDb._columnFamily); + underlyingWriteBatch.Delete(key, columnDb._columnFamily); } else { - _underlyingWriteBatch.Set(key, value, _columnDb._columnFamily, flags); + underlyingWriteBatch.Set(key, value, columnDb._columnFamily, flags); } } - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { - _underlyingWriteBatch.Set(key, value, _columnDb._columnFamily, flags); - } - } + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => + underlyingWriteBatch.Set(key, value, columnDb._columnFamily, flags); - public void Remove(ReadOnlySpan key) - { - // TODO: this does not participate in batching? - _rocksDb.Remove(key, _columnFamily, _mainDb.WriteOptions); + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => + underlyingWriteBatch.Merge(key, value, columnDb._columnFamily, flags); } - public bool KeyExists(ReadOnlySpan key) - { - return _mainDb.KeyExistsWithColumn(key, _columnFamily); - } + public void Remove(ReadOnlySpan key) => Set(key, null); - public void Flush(bool onlyWal) - { - _mainDb.Flush(onlyWal); - } + public void Flush(bool onlyWal) => _mainDb.FlushWithColumnFamily(_columnFamily); - public void Compact() - { + public void Compact() => _rocksDb.CompactRange(Keccak.Zero.BytesToArray(), Keccak.MaxValue.BytesToArray(), _columnFamily); - } /// /// Not sure how to handle delete of the columns DB /// /// - public void Clear() { throw new NotSupportedException(); } + public void Clear() => throw new NotSupportedException(); + + // Maybe it should be column-specific metric? + public IDbMeta.DbMetric GatherMetric() => _mainDb.GatherMetric(); - // Maybe it should be column specific metric? - public IDbMeta.DbMetric GatherMetric(bool includeSharedCache = false) => _mainDb.GatherMetric(includeSharedCache); + public byte[]? FirstKey + { + get + { + using Iterator iterator = _mainDb.CreateIterator(_mainDb.CreateReadOptions(), ch: _columnFamily); + iterator.SeekToFirst(); + return iterator.Valid() ? iterator.GetKeySpan().ToArray() : null; + } + } + + public byte[]? LastKey + { + get + { + using Iterator iterator = _mainDb.CreateIterator(_mainDb.CreateReadOptions(), ch: _columnFamily); + iterator.SeekToLast(); + return iterator.Valid() ? iterator.GetKeySpan().ToArray() : null; + } + } - public void DangerousReleaseMemory(in ReadOnlySpan span) + public ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey) => + _mainDb.GetViewBetween(firstKey, lastKey, _columnFamily); + + public IKeyValueStoreSnapshot CreateSnapshot() { - _mainDb.DangerousReleaseMemory(span); + Snapshot snapshot = _rocksDb.CreateSnapshot(); + + return new DbOnTheRocks.RocksDbSnapshot( + _mainDb, + () => + { + ReadOptions readOptions = _mainDb.CreateReadOptions(); + 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 02300a2d7e62..5b22cc4f0580 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,9 +64,17 @@ private static IReadOnlyList GetEnumKeys(IReadOnlyList keys) return keys; } - protected override void BuildOptions(IRocksDbConfig dbConfig, Options options, IntPtr? sharedCache) + 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); + base.BuildOptions(dbConfig, options, sharedCache, mergeOperator); options.SetCreateMissingColumnFamilies(); } @@ -94,24 +105,19 @@ protected override void ApplyOptions(IDictionary options) private class RocksColumnsWriteBatch : IColumnsWriteBatch { - internal RocksDbWriteBatch _writeBatch; + internal readonly RocksDbWriteBatch WriteBatch; private readonly ColumnsDb _columnsDb; public RocksColumnsWriteBatch(ColumnsDb columnsDb) { - _writeBatch = new RocksDbWriteBatch(columnsDb); + WriteBatch = new RocksDbWriteBatch(columnsDb); _columnsDb = columnsDb; } - public IWriteBatch GetColumnBatch(T key) - { - return new RocksColumnWriteBatch(_columnsDb._columnDbs[key], this); - } + public IWriteBatch GetColumnBatch(T key) => new RocksColumnWriteBatch(_columnsDb._columnDbs[key], this); - public void Dispose() - { - _writeBatch.Dispose(); - } + public void Clear() => WriteBatch.Clear(); + public void Dispose() => WriteBatch.Dispose(); } private class RocksColumnWriteBatch : IWriteBatch @@ -130,9 +136,53 @@ public void Dispose() _writeBatch.Dispose(); } + public void Clear() + { + _writeBatch.WriteBatch.Clear(); + } + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { - _writeBatch._writeBatch.Set(key, value, _column._columnFamily, flags); + _writeBatch.WriteBatch.Set(key, value, _column._columnFamily, flags); + } + + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) + { + _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.SetVerifyChecksums(columnsDb.VerifyChecksum); + 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 16ac59bce621..20b220b301d6 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 b2b506b1d471..f97bccfd637b 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. @@ -248,7 +249,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;" + @@ -270,4 +271,19 @@ public class DbConfig : IDbConfig public string L1OriginDbRocksDbOptions { get; set; } = ""; public string? L1OriginDbAdditionalRocksDbOptions { get; set; } + + public string LogIndexStorageDbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageDbAdditionalRocksDbOptions { get; set; } = ""; + public string LogIndexStorageMetaDbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageMetaDbAdditionalRocksDbOptions { get; set; } = ""; + public string LogIndexStorageAddressesDbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageAddressesDbAdditionalRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics0DbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics0DbAdditionalRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics1DbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics1DbAdditionalRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics2DbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics2DbAdditionalRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics3DbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics3DbAdditionalRocksDbOptions { get; set; } = ""; } diff --git a/src/Nethermind/Nethermind.Db.Rocks/Config/IDbConfig.cs b/src/Nethermind/Nethermind.Db.Rocks/Config/IDbConfig.cs index bdd18bb3afda..5c8b1211a410 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; } @@ -101,4 +102,19 @@ public interface IDbConfig : IConfig string L1OriginDbRocksDbOptions { get; set; } string? L1OriginDbAdditionalRocksDbOptions { get; set; } + + string LogIndexStorageDbRocksDbOptions { get; set; } + string LogIndexStorageDbAdditionalRocksDbOptions { get; set; } + string LogIndexStorageMetaDbRocksDbOptions { get; set; } + string LogIndexStorageMetaDbAdditionalRocksDbOptions { get; set; } + string LogIndexStorageAddressesDbRocksDbOptions { get; set; } + string LogIndexStorageAddressesDbAdditionalRocksDbOptions { get; set; } + string LogIndexStorageTopics0DbRocksDbOptions { get; set; } + string LogIndexStorageTopics0DbAdditionalRocksDbOptions { get; set; } + string LogIndexStorageTopics1DbRocksDbOptions { get; set; } + string LogIndexStorageTopics1DbAdditionalRocksDbOptions { get; set; } + string LogIndexStorageTopics2DbRocksDbOptions { get; set; } + string LogIndexStorageTopics2DbAdditionalRocksDbOptions { get; set; } + string LogIndexStorageTopics3DbRocksDbOptions { get; set; } + string LogIndexStorageTopics3DbAdditionalRocksDbOptions { get; set; } } diff --git a/src/Nethermind/Nethermind.Db.Rocks/Config/IRocksDbConfig.cs b/src/Nethermind/Nethermind.Db.Rocks/Config/IRocksDbConfig.cs index f70d96006d32..c1864fdce027 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 6524901e3578..7b8967ad4b9e 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/Config/PerTableDbConfig.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/Config/PerTableDbConfig.cs @@ -38,7 +38,7 @@ private void EnsureConfigIsAvailable(string propertyName) foreach (var prefix in _prefixes) { string prefixed = string.Concat(prefix, propertyName); - if (type.GetProperty(prefixed, BindingFlags.Public | BindingFlags.Instance) is null) + if (GetProperty(type, prefixed) is null) { throw new InvalidConfigurationException($"Configuration {propertyName} not available with prefix {prefix}. Add {prefix}{propertyName} to {nameof(IDbConfig)}.", -1); } @@ -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) { @@ -90,13 +91,13 @@ private static string ReadRocksdbOptions(IDbConfig dbConfig, string propertyName Type type = dbConfig.GetType(); PropertyInfo? propertyInfo; - string val = (string)type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance)!.GetValue(dbConfig)!; + string val = (string)GetProperty(type, propertyName)!.GetValue(dbConfig)!; foreach (var prefix in prefixes) { string prefixed = string.Concat(prefix, propertyName); - propertyInfo = type.GetProperty(prefixed, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); + propertyInfo = GetProperty(type, prefixed); if (propertyInfo is not null) { string? valObj = (string?)propertyInfo.GetValue(dbConfig); @@ -122,7 +123,7 @@ private static string ReadRocksdbOptions(IDbConfig dbConfig, string propertyName { string prefixed = string.Concat(prefix, propertyName); - propertyInfo = type.GetProperty(prefixed, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); + propertyInfo = GetProperty(type, prefixed); if (propertyInfo is not null) { if (propertyInfo.PropertyType.CanBeAssignedNull()) @@ -147,7 +148,7 @@ private static string ReadRocksdbOptions(IDbConfig dbConfig, string propertyName } // Use generic one even if its available - propertyInfo = type.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); + propertyInfo = GetProperty(type, propertyName); return (T?)propertyInfo?.GetValue(dbConfig); } catch (Exception e) @@ -156,4 +157,6 @@ private static string ReadRocksdbOptions(IDbConfig dbConfig, string propertyName } } + private static PropertyInfo? GetProperty(Type type, string name) => + type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); } diff --git a/src/Nethermind/Nethermind.Db.Rocks/Config/RocksDbConfigFactory.cs b/src/Nethermind/Nethermind.Db.Rocks/Config/RocksDbConfigFactory.cs index aa62daff031d..a2c661824f62 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; @@ -11,9 +12,40 @@ namespace Nethermind.Db.Rocks.Config; public class RocksDbConfigFactory(IDbConfig dbConfig, IPruningConfig pruningConfig, IHardwareInfo hardwareInfo, ILogManager logManager) : IRocksDbConfigFactory { private readonly ILogger _logger = logManager.GetClassLogger(); + private bool _maxOpenFilesInitialized; public IRocksDbConfig GetForDatabase(string databaseName, string? columnName) { + // Automatically adjust MaxOpenFiles if not configured (only once) + if (!_maxOpenFilesInitialized) + { + _maxOpenFilesInitialized = true; + + if (dbConfig.MaxOpenFiles is null && hardwareInfo.MaxOpenFilesLimit.HasValue) + { + int systemLimit = hardwareInfo.MaxOpenFilesLimit.Value; + // Apply 80% of system limit as safety margin to account for: + // - Multiple databases (~15) each using this limit + // - System operations and network sockets + // - Other file descriptors needed by the application + int perDbLimit = Math.Max(256, (int)(systemLimit * 0.8)); + + if (_logger.IsInfo) + { + _logger.Info($"Detected system open files limit of {systemLimit}. Setting MaxOpenFiles to {perDbLimit} per database."); + } + + 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); if (databaseName.StartsWith("State")) { diff --git a/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs b/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs index 1309aa0bafbc..cca010c66f8c 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Buffers.Binary; using System.Collections.Concurrent; using System.Collections.Generic; @@ -18,6 +19,7 @@ using ConcurrentCollections; using Nethermind.Config; using Nethermind.Core; +using Nethermind.Core.Buffers; using Nethermind.Core.Crypto; using Nethermind.Core.Exceptions; using Nethermind.Core.Extensions; @@ -29,7 +31,7 @@ namespace Nethermind.Db.Rocks; -public partial class DbOnTheRocks : IDb, ITunableDb, IReadOnlyNativeKeyValueStore +public partial class DbOnTheRocks : IDb, ITunableDb, IReadOnlyNativeKeyValueStore, ISortedKeyValueStore, IMergeableKeyValueStore, IKeyValueStoreWithSnapshot { protected ILogger _logger; @@ -66,7 +68,12 @@ public partial class DbOnTheRocks : IDb, ITunableDb, IReadOnlyNativeKeyValueStor private readonly DbSettings _settings; + // Need to keep options from GC in case of merge operator applied, as they are used in callback + // ReSharper disable once CollectionNeverQueried.Local + private readonly List _doNotGcOptions = []; + private readonly IRocksDbConfig _perTableDbConfig; + internal bool VerifyChecksum => _perTableDbConfig.VerifyChecksum ?? true; private ulong _maxBytesForLevelBase; private ulong _targetFileSizeBase; private int _minWriteBufferToMerge; @@ -88,6 +95,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, @@ -96,7 +105,7 @@ public DbOnTheRocks( IRocksDbConfigFactory rocksDbConfigFactory, ILogManager logManager, IList? columnFamilies = null, - RocksDbSharp.Native? rocksDbNative = null, + Native? rocksDbNative = null, IFileSystem? fileSystem = null, IntPtr? sharedCache = null) { @@ -104,11 +113,13 @@ public DbOnTheRocks( _settings = dbSettings; Name = _settings.DbName; _fileSystem = fileSystem ?? new FileSystem(); - _rocksDbNative = rocksDbNative ?? RocksDbSharp.Native.Instance; + _rocksDbNative = rocksDbNative ?? Native.Instance; _rocksDbConfigFactory = rocksDbConfigFactory; _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, CreateReadOptions, _iteratorManager, null); } protected virtual RocksDb DoOpen(string path, (DbOptions Options, ColumnFamilies? Families) db) @@ -120,7 +131,6 @@ protected virtual RocksDb DoOpen(string path, (DbOptions Options, ColumnFamilies private RocksDb Open(string path, (DbOptions Options, ColumnFamilies? Families) db) { RepairIfCorrupted(db.Options); - return DoOpen(path, db); } @@ -143,7 +153,7 @@ private RocksDb Init(string basePath, string dbPath, IDbConfig dbConfig, ILogMan // ReSharper disable once VirtualMemberCallInConstructor if (_logger.IsDebug) _logger.Debug($"Building options for {Name} DB"); DbOptions = new DbOptions(); - BuildOptions(_perTableDbConfig, DbOptions, sharedCache); + BuildOptions(_perTableDbConfig, DbOptions, sharedCache, _settings.MergeOperator); ColumnFamilies? columnFamilies = null; if (columnNames is not null) @@ -155,7 +165,8 @@ private RocksDb Init(string basePath, string dbPath, IDbConfig dbConfig, ILogMan ColumnFamilyOptions options = new(); IRocksDbConfig columnConfig = _rocksDbConfigFactory.GetForDatabase(Name, columnFamily); - BuildOptions(columnConfig, options, sharedCache); + IMergeOperator? mergeOperator = _settings.ColumnsMergeOperators?.GetValueOrDefault(enumColumnName); + BuildOptions(columnConfig, options, sharedCache, mergeOperator); // "default" is a special column name with rocksdb, which is what previously not specifying column goes to if (columnFamily == "Default") columnFamily = "default"; @@ -169,7 +180,7 @@ private RocksDb Init(string basePath, string dbPath, IDbConfig dbConfig, ILogMan if (dbConfig.EnableMetricsUpdater) { - DbMetricsUpdater metricUpdater = new DbMetricsUpdater(Name, DbOptions, db, null, dbConfig, _logger); + DbMetricsUpdater metricUpdater = new(Name, DbOptions, db, null, dbConfig, _logger); metricUpdater.StartUpdating(); _metricsUpdaters.Add(metricUpdater); @@ -180,7 +191,7 @@ private RocksDb Init(string basePath, string dbPath, IDbConfig dbConfig, ILogMan if (columnFamily.Name == "default") continue; if (db.TryGetColumnFamily(columnFamily.Name, out ColumnFamilyHandle handle)) { - DbMetricsUpdater columnMetricUpdater = new DbMetricsUpdater( + DbMetricsUpdater columnMetricUpdater = new( Name + "_" + columnFamily.Name, columnFamily.Options, db, handle, dbConfig, _logger); columnMetricUpdater.StartUpdating(); _metricsUpdaters.Add(columnMetricUpdater); @@ -217,7 +228,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()) { @@ -225,7 +236,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) { @@ -233,7 +244,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; @@ -244,23 +255,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; - }) - // 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. + availableMemory -= (long)metadata.metadata.FileSize; + bool take = availableMemory > 0; + if (take) + { + totalSize += (long)metadata.metadata.FileSize; + } + return take; + }) + // We reverse them again so that the lower level goes last so that it is the freshest. + // Not all 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}"); @@ -298,7 +309,8 @@ private void CreateMarkerIfCorrupt(RocksDbSharpException rocksDbException) // Don't kill tests checking corruption response if (!rocksDbException.Message.Equals("Corruption: test corruption", StringComparison.Ordinal)) { - Environment.FailFast("Fast shutdown due to DB corruption. Please restart."); + _logger.Error($"Fast shutdown due to {Name} DB corruption. Please restart."); + Environment.Exit(ExitCodes.DbCorruption); } } } @@ -329,16 +341,12 @@ protected internal void UpdateWriteMetrics() Interlocked.Increment(ref _totalWrites); } - protected virtual long FetchTotalPropertyValue(string propertyName) - { - long value = long.TryParse(_db.GetProperty(propertyName), out long parsedValue) + protected virtual long FetchTotalPropertyValue(string propertyName) => + long.TryParse(_db.GetProperty(propertyName), out long parsedValue) ? parsedValue : 0; - return value; - } - - public IDbMeta.DbMetric GatherMetric(bool includeSharedCache = false) + public IDbMeta.DbMetric GatherMetric() { if (_isDisposed) { @@ -355,7 +363,7 @@ public IDbMeta.DbMetric GatherMetric(bool includeSharedCache = false) return new IDbMeta.DbMetric() { Size = GetSize(), - CacheSize = GetCacheSize(includeSharedCache), + CacheSize = GetCacheSize(), IndexSize = GetIndexSize(), MemtableSize = GetMemtableSize(), TotalReads = _totalReads, @@ -380,11 +388,11 @@ private long GetSize() return 0; } - private long GetCacheSize(bool includeSharedCache = false) + private long GetCacheSize() { try { - if (!includeSharedCache) + if (_isUsingSharedBlockCache) { // returning 0 as we are using shared cache. return 0; @@ -446,10 +454,42 @@ public static IDictionary ExtractOptions(string dbOptions) return asDict; } - protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options options, IntPtr? sharedCache) where T : Options + 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. - // This effect the format of the SST files and usually require resync to take effect. + // This section is about the table factory and block cache, apparently. + // This affects the format of the SST files and usually requires resyncing to take effect. // Note: Keep in mind, the term 'index' here usually means mapping to a block, not to a value. #region TableFactory sections @@ -461,27 +501,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) @@ -508,21 +556,18 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio int writeBufferNumber = _maxWriteBufferNumber; _maxThisDbSize += (long)writeBufferSize * writeBufferNumber; Interlocked.Add(ref _maxRocksSize, _maxThisDbSize); - if (_logger.IsDebug) - _logger.Debug( - $"Expected max memory footprint of {Name} DB is {_maxThisDbSize / 1000 / 1000} MB ({writeBufferNumber} * {writeBufferSize / 1000 / 1000} MB + {blockCacheSize / 1000 / 1000} MB)"); + if (_logger.IsDebug) _logger.Debug($"Expected max memory footprint of {Name} DB is {_maxThisDbSize / 1000 / 1000} MB ({writeBufferNumber} * {writeBufferSize / 1000 / 1000} MB + {blockCacheSize / 1000 / 1000} MB)"); if (_logger.IsDebug) _logger.Debug($"Total max DB footprint so far is {_maxRocksSize / 1000 / 1000} MB"); } #endregion - // This section affect compactions, flushes and the LSM shape. + // This section affects compactions, flushes and the LSM shape. #region Compaction /* - * Multi-Threaded Compactions - * Compactions are needed to remove multiple copies of the same key that may occur if an application overwrites an existing key. Compactions also process deletions of keys. Compactions may occur in multiple threads if configured appropriately. + * Multi-Threaded Compactions are needed to remove multiple copies of the same key that may occur if an application overwrites an existing key. Compactions also process deletions of keys. Compactions may occur in multiple threads if configured appropriately. * The entire database is stored in a set of sstfiles. When a memtable is full, its content is written out to a file in Level-0 (L0). RocksDB removes duplicate and overwritten keys in the memtable when it is flushed to a file in L0. Some files are periodically read in and merged to form larger files - this is called compaction. - * The overall write throughput of an LSM database directly depends on the speed at which compactions can occur, especially when the data is stored in fast storage like SSD or RAM. RocksDB may be configured to issue concurrent compaction requests from multiple threads. It is observed that sustained write rates may increase by as much as a factor of 10 with multi-threaded compaction when the database is on SSDs, as compared to single-threaded compactions. + * The overall writing throughput of an LSM database directly depends on the speed at which compactions can occur, especially when the data is stored in fast storage like SSD or RAM. RocksDB may be configured to issue concurrent compaction requests from multiple threads. It is observed that sustained write rates may increase by as much as a factor of 10 with multi-threaded compaction when the database is on SSDs, as compared to single-threaded compactions. * TKS: Observed 500MB/s compared to ~100MB/s between multithreaded and single thread compactions on my machine (processor count is returning 12 for 6 cores with hyperthreading) * TKS: CPU goes to insane 30% usage on idle - compacting only app */ @@ -541,11 +586,11 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio if (dbConfig.RowCacheSize > 0) { - // Row cache is basically a per-key cache. Nothing special to it. This is different from block cache - // which cache the whole block at once, so read still need to traverse the block index, so this could be + // Row cache is basically a per-key cache. Nothing special about it. This is different from a block cache + // that caches the whole block at once, so read still needs to traverse the block index, so this could be // more CPU efficient. - // Note: Memtable also act like a per-key cache, that does not get updated on read. So in some case - // maybe it make more sense to put more memory to memtable. + // Note: Memtable also acts like a per-key cache that does not get updated on read. So in some case + // maybe it makes more sense to put more memory to memtable. _rowCache = _rocksDbNative.rocksdb_cache_create_lru(new UIntPtr(dbConfig.RowCacheSize.Value)); _rocksDbNative.rocksdb_options_set_row_cache(options.Handle, _rowCache.Value); } @@ -565,7 +610,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); @@ -576,6 +621,13 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio } } + if (mergeOperator is not null) + { + options.SetMergeOperator(new MergeOperatorAdapter(mergeOperator)); + lock (_doNotGcOptions) + _doNotGcOptions.Add(options); + } + #endregion #region read-write options @@ -592,22 +644,19 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio _lowPriorityAndNoWalWrite.DisableWal(1); _rocksDbNative.rocksdb_writeoptions_set_low_pri(_lowPriorityAndNoWalWrite.Handle, true); - _defaultReadOptions = new ReadOptions(); - _defaultReadOptions.SetVerifyChecksums(dbConfig.VerifyChecksum ?? true); + _defaultReadOptions = CreateReadOptions(); - _hintCacheMissOptions = new ReadOptions(); - _hintCacheMissOptions.SetVerifyChecksums(dbConfig.VerifyChecksum ?? true); + _hintCacheMissOptions = CreateReadOptions(); _hintCacheMissOptions.SetFillCache(false); - // When readahead flag is on, the next keys are expected to be after the current key. Increasing this value, + // When a readahead flag is on, the next keys are expected to be after the current key. Increasing this value // will increase the chances that the next keys will be in the cache, which reduces iops and latency. This // increases throughput, however, if a lot of the keys are not close to the current key, it will increase read // bandwidth requirement, since each read must be at least this size. This value is tuned for a batched trie // visitor on mainnet with 4GB memory budget and 4Gbps read bandwidth. if (dbConfig.ReadAheadSize != 0) { - _readAheadReadOptions = new ReadOptions(); - _readAheadReadOptions.SetVerifyChecksums(dbConfig.VerifyChecksum ?? true); + _readAheadReadOptions = CreateReadOptions(); _readAheadReadOptions.SetReadaheadSize(dbConfig.ReadAheadSize ?? (ulong)256.KiB()); _readAheadReadOptions.SetTailing(true); } @@ -617,49 +666,35 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio private static WriteOptions CreateWriteOptions(IRocksDbConfig dbConfig) { WriteOptions options = new(); - // potential fix for corruption on hard process termination, may cause performance degradation + // a potential fix for corruption on hard process termination may cause performance degradation options.SetSync(dbConfig.WriteAheadLogSync); return options; } - public byte[]? this[ReadOnlySpan key] + internal ReadOptions CreateReadOptions() { - get => Get(key, ReadFlags.None); - set => Set(key, value, WriteFlags.None); + ReadOptions readOptions = new(); + readOptions.SetVerifyChecksums(VerifyChecksum); + return readOptions; } - public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + byte[]? IReadOnlyKeyValueStore.Get(ReadOnlySpan key, ReadFlags flags) => _reader.Get(key, flags); + + Span IReadOnlyKeyValueStore.GetSpan(scoped ReadOnlySpan key, ReadFlags flags) => _reader.GetSpan(key, flags); + + MemoryManager? IReadOnlyKeyValueStore.GetOwnedMemory(ReadOnlySpan key, ReadFlags flags) { - return GetWithColumnFamily(key, null, _iteratorManager, flags); + Span span = ((IReadOnlyKeyValueStore)this).GetSpan(key, flags); + return span.IsNullOrEmpty() ? null : new DbSpanMemoryManager(this, span); } - internal byte[]? GetWithColumnFamily(ReadOnlySpan key, ColumnFamilyHandle? cf, IteratorManager iteratorManager, ReadFlags flags = ReadFlags.None) - { - ObjectDisposedException.ThrowIf(_isDisposing, this); + int IReadOnlyKeyValueStore.Get(scoped ReadOnlySpan key, Span output, ReadFlags flags) => _reader.Get(key, output, flags); - UpdateReadMetrics(); + bool IReadOnlyKeyValueStore.KeyExists(ReadOnlySpan key) => _reader.KeyExists(key); - try - { - if (_readAheadReadOptions is not null && (flags & ReadFlags.HintReadAhead) != 0) - { - byte[]? result = GetWithIterator(key, cf, iteratorManager, flags, out bool success); - if (success) - { - return result; - } - } + void IReadOnlyKeyValueStore.DangerousReleaseMemory(in ReadOnlySpan span) => _reader.DangerousReleaseMemory(span); - return Get(key, cf, flags); - } - catch (RocksDbSharpException e) - { - CreateMarkerIfCorrupt(e); - throw; - } - } - - private unsafe byte[]? GetWithIterator(ReadOnlySpan key, ColumnFamilyHandle? cf, IteratorManager iteratorManager, ReadFlags flags, out bool success) + internal byte[]? GetWithIterator(ReadOnlySpan key, ColumnFamilyHandle? cf, IteratorManager iteratorManager, ReadFlags flags, out bool success) { success = true; @@ -681,21 +716,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); @@ -720,10 +755,7 @@ public byte[]? this[ReadOnlySpan key] } [DoesNotReturn, StackTraceHidden] - static unsafe void ThrowRocksDbException(nint errPtr) - { - throw new RocksDbException(errPtr); - } + static void ThrowRocksDbException(nint errPtr) => throw new RocksDbException(errPtr); } /// @@ -733,6 +765,7 @@ static unsafe void ThrowRocksDbException(nint errPtr) /// /// /// + /// /// private bool TryCloseReadAhead(Iterator iterator, ReadOnlySpan key, out byte[]? result) { @@ -792,10 +825,8 @@ private bool TryCloseReadAhead(Iterator iterator, ReadOnlySpan key, out by return false; } - public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - { + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) => SetWithColumnFamily(key, null, value, flags); - } internal void SetWithColumnFamily(ReadOnlySpan key, ColumnFamilyHandle? cf, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) { @@ -821,25 +852,13 @@ internal void SetWithColumnFamily(ReadOnlySpan key, ColumnFamilyHandle? cf } } - public WriteOptions? WriteFlagsToWriteOptions(WriteFlags flags) + public WriteOptions? WriteFlagsToWriteOptions(WriteFlags flags) => flags switch { - if ((flags & WriteFlags.LowPriorityAndNoWAL) == WriteFlags.LowPriorityAndNoWAL) - { - return _lowPriorityAndNoWalWrite; - } - - if ((flags & WriteFlags.DisableWAL) == WriteFlags.DisableWAL) - { - return _noWalWrite; - } - - if ((flags & WriteFlags.LowPriority) == WriteFlags.LowPriority) - { - return _lowPriorityWriteOptions; - } - - return WriteOptions; - } + _ when (flags & WriteFlags.LowPriorityAndNoWAL) == WriteFlags.LowPriorityAndNoWAL => _lowPriorityAndNoWalWrite, + _ when (flags & WriteFlags.DisableWAL) == WriteFlags.DisableWAL => _noWalWrite, + _ when (flags & WriteFlags.LowPriority) == WriteFlags.LowPriority => _lowPriorityWriteOptions, + _ => WriteOptions + }; public KeyValuePair[] this[byte[][] keys] @@ -858,12 +877,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); @@ -871,7 +885,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()) { @@ -887,9 +901,88 @@ internal Span GetSpanWithColumnFamily(scoped ReadOnlySpan key, Colum } } - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags) + internal unsafe int GetCStyleWithColumnFamily(scoped ReadOnlySpan key, Span output, ColumnFamilyHandle? cf, ReadOptions readOptions) { + ObjectDisposedException.ThrowIf(_isDisposing, this); + + UpdateReadMetrics(); + + nint db = _db.Handle; + nint readOptionsHandle = 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, readOptionsHandle, ptr, skLength, out errPtr) + : Native.Instance.rocksdb_get_pinned_cf(db, readOptionsHandle, 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 void ThrowRocksDbException(nint errPtr) => throw new RocksDbException(errPtr); + + [DoesNotReturn, StackTraceHidden] + static 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); + + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) + { + ObjectDisposedException.ThrowIf(_isDisposing, this); + + UpdateWriteMetrics(); + + try + { + _db.Merge(key, value, null, WriteFlagsToWriteOptions(flags)); + } + catch (RocksDbSharpException e) + { + CreateMarkerIfCorrupt(e); + throw; + } + } + + internal void MergeWithColumnFamily(ReadOnlySpan key, ColumnFamilyHandle? cf, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) + { + ObjectDisposedException.ThrowIf(_isDisposing, this); + + UpdateWriteMetrics(); + + try + { + _db.Merge(key, value, cf, WriteFlagsToWriteOptions(flags)); + } + catch (RocksDbSharpException e) + { + CreateMarkerIfCorrupt(e); + throw; + } } public void DangerousReleaseMemory(in ReadOnlySpan span) @@ -985,7 +1078,11 @@ protected internal Iterator CreateIterator(bool ordered = false, ColumnFamilyHan { ReadOptions readOptions = new(); readOptions.SetTailing(!ordered); + return CreateIterator(readOptions, ch); + } + protected internal Iterator CreateIterator(ReadOptions readOptions, ColumnFamilyHandle? ch = null) + { try { return _db.NewIterator(ch, readOptions); @@ -1013,45 +1110,60 @@ public IEnumerable GetAllValues(bool ordered = false) return GetAllValuesCore(iterator); } + private void IteratorSeekToFirstWithErrorHandling(Iterator iterator) + { + try + { + iterator.SeekToFirst(); + } + catch (RocksDbSharpException e) + { + CreateMarkerIfCorrupt(e); + throw; + } + } + + private void IteratorNextWithErrorHandling(Iterator iterator) + { + try + { + iterator.Next(); + } + catch (RocksDbSharpException e) + { + CreateMarkerIfCorrupt(e); + throw; + } + } + + private void IteratorDisposeWithErrorHandling(Iterator iterator) + { + try + { + iterator.Dispose(); + } + catch (RocksDbSharpException e) + { + CreateMarkerIfCorrupt(e); + throw; + } + } + internal IEnumerable GetAllValuesCore(Iterator iterator) { try { - try - { - iterator.SeekToFirst(); - } - catch (RocksDbSharpException e) - { - CreateMarkerIfCorrupt(e); - throw; - } + IteratorSeekToFirstWithErrorHandling(iterator); while (iterator.Valid()) { yield return iterator.Value(); - try - { - iterator.Next(); - } - catch (RocksDbSharpException e) - { - CreateMarkerIfCorrupt(e); - throw; - } + IteratorNextWithErrorHandling(iterator); } } finally { - try - { - iterator.Dispose(); - } - catch (RocksDbSharpException e) - { - CreateMarkerIfCorrupt(e); - throw; - } + IteratorDisposeWithErrorHandling(iterator); } } @@ -1059,94 +1171,40 @@ internal IEnumerable GetAllKeysCore(Iterator iterator) { try { - try - { - iterator.SeekToFirst(); - } - catch (RocksDbSharpException e) - { - CreateMarkerIfCorrupt(e); - throw; - } + IteratorSeekToFirstWithErrorHandling(iterator); while (iterator.Valid()) { yield return iterator.Key(); - try - { - iterator.Next(); - } - catch (RocksDbSharpException e) - { - CreateMarkerIfCorrupt(e); - throw; - } + IteratorNextWithErrorHandling(iterator); } } finally { - try - { - iterator.Dispose(); - } - catch (RocksDbSharpException e) - { - CreateMarkerIfCorrupt(e); - throw; - } + IteratorDisposeWithErrorHandling(iterator); } } public IEnumerable> GetAllCore(Iterator iterator) { + ObjectDisposedException.ThrowIf(_isDisposing, this); + try { - ObjectDisposedException.ThrowIf(_isDisposing, this); - - try - { - iterator.SeekToFirst(); - } - catch (RocksDbSharpException e) - { - CreateMarkerIfCorrupt(e); - throw; - } + IteratorSeekToFirstWithErrorHandling(iterator); while (iterator.Valid()) { yield return new KeyValuePair(iterator.Key(), iterator.Value()); - - try - { - iterator.Next(); - } - catch (RocksDbSharpException e) - { - CreateMarkerIfCorrupt(e); - throw; - } + IteratorNextWithErrorHandling(iterator); } } finally { - try - { - iterator.Dispose(); - } - catch (RocksDbSharpException e) - { - CreateMarkerIfCorrupt(e); - throw; - } + IteratorDisposeWithErrorHandling(iterator); } } - public bool KeyExists(ReadOnlySpan key) - { - return KeyExistsWithColumn(key, null); - } - protected internal bool KeyExistsWithColumn(ReadOnlySpan key, ColumnFamilyHandle? cf) { ObjectDisposedException.ThrowIf(_isDisposing, this); @@ -1181,8 +1239,8 @@ internal class RocksDbWriteBatch : IWriteBatch /// /// Because of how rocksdb parallelize writes, a large write batch can stall other new concurrent writes, so - /// we writes the batch in smaller batches. This removes atomicity so its only turned on when NoWAL flag is on. - /// It does not work as well as just turning on unordered_write, but Snapshot and Iterator can still works. + /// we write the batch in smaller batches. This removes atomicity so it's only turned on when the NoWAL flag is on. + /// It does not work as well as just turning on unordered_write, but Snapshot and Iterator can still work. /// private const int MaxWritesOnNoWal = 256; private int _writeCount; @@ -1217,6 +1275,12 @@ private static void ReturnWriteBatch(WriteBatch batch) _reusableWriteBatch = batch; } + public void Clear() + { + ObjectDisposedException.ThrowIf(_dbOnTheRocks._isDisposed, _dbOnTheRocks); + _rocksBatch.Clear(); + } + public void Dispose() { ObjectDisposedException.ThrowIf(_dbOnTheRocks._isDisposed, _dbOnTheRocks); @@ -1265,14 +1329,23 @@ public void Set(ReadOnlySpan key, ReadOnlySpan value, ColumnFamilyHa if ((flags & WriteFlags.DisableWAL) != 0) FlushOnTooManyWrites(); } - public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - { + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) => Set(key, value, null, flags); - } - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => Set(key, value, null, flags); + + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => + Merge(key, value, null, flags); + + public void Merge(ReadOnlySpan key, ReadOnlySpan value, ColumnFamilyHandle? cf = null, WriteFlags flags = WriteFlags.None) + { + ObjectDisposedException.ThrowIf(_isDisposed, this); + + _rocksBatch.Merge(key, value, cf); + _writeFlags = flags; + + if ((flags & WriteFlags.DisableWAL) != 0) FlushOnTooManyWrites(); } private void FlushOnTooManyWrites() @@ -1301,6 +1374,13 @@ public void Flush(bool onlyWal = false) InnerFlush(onlyWal); } + public void FlushWithColumnFamily(ColumnFamilyHandle familyHandle) + { + ObjectDisposedException.ThrowIf(_isDisposing, this); + + InnerFlush(familyHandle); + } + public virtual void Compact() { _db.CompactRange(Keccak.Zero.BytesToArray(), Keccak.MaxValue.BytesToArray()); @@ -1323,6 +1403,18 @@ private void InnerFlush(bool onlyWal) } } + private void InnerFlush(ColumnFamilyHandle columnFamilyHandle) + { + try + { + _rocksDbNative.rocksdb_flush_cf(_db.Handle, FlushOptions.DefaultFlushOptions.Handle, columnFamilyHandle.Handle); + } + catch (RocksDbSharpException e) + { + CreateMarkerIfCorrupt(e); + } + } + public void Clear() { Dispose(); @@ -1360,18 +1452,13 @@ private class FlushOptions { internal static FlushOptions DefaultFlushOptions { get; } = new(); - public FlushOptions() - { - Handle = RocksDbSharp.Native.Instance.rocksdb_flushoptions_create(); - } - - public IntPtr Handle { get; private set; } + public IntPtr Handle { get; private set; } = Native.Instance.rocksdb_flushoptions_create(); ~FlushOptions() { if (Handle != IntPtr.Zero) { - RocksDbSharp.Native.Instance.rocksdb_flushoptions_destroy(Handle); + Native.Instance.rocksdb_flushoptions_destroy(Handle); Handle = IntPtr.Zero; } } @@ -1436,15 +1523,15 @@ public virtual void Tune(ITunableDb.TuneType type) // See https://github.com/EighteenZi/rocksdb_wiki/blob/master/RocksDB-Tuning-Guide.md switch (type) { - // Depending on tune type, allow num of L0 files to grow causing compaction to occur in larger size. This + // Depending on tune type, allow num of L0 files to grow causing compaction to occur in a larger size. This // reduces write amplification at the expense of read response time and amplification while the tune is // active. Additionally, the larger compaction causes larger spikes of IO, larger memory usage, and may temporarily - // use up large amount of disk space. User may not want to enable this if they plan to run a validator node - // while the node is still syncing, or run another node on the same machine. Specifying a rate limit + // use up a large amount of disk space. User may not want to enable this if they plan to run a validator node + // while the node is still syncing or run another node on the same machine. Specifying a rate limit // smoothens this spike somewhat by not blocking writes while allowing compaction to happen in background // at 1/10th the specified speed (if rate limited). // - // Total writes written on different tune during mainnet sync in TB. + // Total writes written on different tunes during mainnet sync in TB. // +-----------------------+-------+-------+-------+-------+-------+---------+ // | L0FileNumTarget | Total | State | Code | Header| Blocks| Receipts | // +-----------------------+-------+-------+-------+-------+-------+---------+ @@ -1455,45 +1542,45 @@ public virtual void Tune(ITunableDb.TuneType type) // | DisableCompaction | 2.215 | 0.36 | 0.031 | 0.137 | 1.14 | 0.547 | // +-----------------------+-------+-------+-------+-------+-------+---------+ // Note, in practice on my machine, the reads does not reach the SSD. Read measured from SSD is much lower - // than read measured from process. It is likely that most files are cached as I have 128GB of RAM. + // than read measured from a process. It is likely that most files are cached as I have 128GB of RAM. // Also notice that the heavier the tune, the higher the reads. case ITunableDb.TuneType.WriteBias: - // Keep the same l1 size but apply other adjustment which should increase buffer number and make - // l0 the same size as l1, but keep the LSM the same. This improve flush parallelization, and + // Keep the same l1 size but apply other adjustment which should increase the buffer number and make + // l0 the same size as l1 but keep the LSM the same. This improves flush parallelization and // write amplification due to mismatch of l0 and l1 size, but does not reduce compaction from other // levels. ApplyOptions(GetHeavyWriteOptions(_maxBytesForLevelBase)); break; case ITunableDb.TuneType.HeavyWrite: - // Compaction spikes are clear at this point. Will definitely affect attestation performance. - // Its unclear if it improve or slow down sync time. Seems to be the sweet spot. + // Compaction spikes are clear at this point. Will definitely affect attestations performance. + // It's unclear if it improves or slows down sync time. Seems to be the sweet spot. ApplyOptions(GetHeavyWriteOptions((ulong)2.GiB())); break; case ITunableDb.TuneType.AggressiveHeavyWrite: - // For when, you are desperate, but don't wanna disable compaction completely, because you don't want + // For when you are desperate, but don't wanna disable compaction completely, because you don't want // peers to drop. Tend to be faster than disabling compaction completely, except if your ratelimit // is a bit low and your compaction is lagging behind, which will trigger slowdown, so sync will hang // intermittently, but at least peer count is stable. ApplyOptions(GetHeavyWriteOptions((ulong)16.GiB())); break; case ITunableDb.TuneType.DisableCompaction: - // Completely disable compaction. On mainnet, max num of l0 files for state seems to be about 10800. - // Blocksdb are way more at 53000. Final compaction for state db need 30 minute, while blocks db need - // 13 hour. Receipts db don't show up in metrics likely because its a column db. + // Completely disable compaction. On mainnet, the max num of l0 files for state seems to be about 10800. + // Blocksdb are way more at 53000. Final compaction for state db needs 30 minutes, while blocks db need + // 13 hours. Receipts db don't show up in metrics likely because it's a column db. // Ram usage at that time was 86 GB. The default buffer size for blocks on mainnet is too low // to make this work reasonably well. - // L0 to L1 compaction is known to be slower than other level so its - // Snap sync performance suffer as it does have some read during stitching. - // If you don't specify a lower open files limit, it has a tendency to crash, like.. the whole system - // crash. I don't have any open file limit at OS level. - // Also, if a peer send a packet that causes a query to the state db during snap sync like GetNodeData - // or some of the tx filter querying state, It'll cause the network stack to hang and triggers a + // L0 to L1 compaction is known to be slower than other levels, so its + // Snap sync performance suffers as it does have some read during stitching. + // If you don't specify a lower open files limit, it tends to crash, like... the whole system + // crashes. I don't have any open file limit at OS level. + // Also, if a peer sends a packet that causes a query to the state db during snap sync like GetNodeData + // or some of the tx filter querying state, It'll cause the network stack to hang and triggers // large peer drops. Also happens on lesser tune, but weaker. - // State sync essentially hang until that completes because its read heavy, and the uncompacted db is + // State sync essentially hangs until that completes because its read heavy, and the uncompacted db is // slow to a halt. // Additionally, the number of open files handles measured from collectd jumped massively higher. Some // user config may not be able to handle this. - // With all those cons, this result in the minimum write amplification possible via tweaking compaction + // With all those cons, this results in the minimum writes amplification possible via tweaking compaction // without changing memory budget. Not recommended for mainnet, unless you are very desperate. ApplyOptions(GetDisableCompactionOptions()); break; @@ -1512,15 +1599,11 @@ public virtual void Tune(ITunableDb.TuneType type) _currentTune = type; } - protected virtual void ApplyOptions(IDictionary options) - { - _db.SetOptions(options); - } + protected virtual void ApplyOptions(IDictionary options) => _db.SetOptions(options); - private IDictionary GetStandardOptions() - { + private IDictionary GetStandardOptions() => // Defaults are from rocksdb source code - return new Dictionary() + new Dictionary() { { "write_buffer_size", _writeBufferSize.ToString() }, { "max_write_buffer_number", _maxWriteBufferNumber.ToString() }, @@ -1529,7 +1612,7 @@ private IDictionary GetStandardOptions() { "level0_slowdown_writes_trigger", 20.ToString() }, // Very high, so that after moving from HeavyWrite, we don't immediately hang. - // This does means that under very rare case, the l0 file can accumulate, which slow down the db + // This does mean that under a very rare case, the l0 file can accumulate, which slows down the db // until they get compacted. { "level0_stop_writes_trigger", 1024.ToString() }, @@ -1542,13 +1625,11 @@ private IDictionary GetStandardOptions() { "soft_pending_compaction_bytes_limit", 64.GiB().ToString() }, { "hard_pending_compaction_bytes_limit", 256.GiB().ToString() }, }; - } - private IDictionary GetHashDbOptions() - { - return new Dictionary() + private IDictionary GetHashDbOptions() => + new Dictionary() { - // Some database config is slightly faster on hash db database. These are applied when hash db is detected + // Some database config is slightly faster on a hash db database. These are applied when hash db is detected // to prevent unexpected regression. { "table_factory.block_size", "4096" }, { "table_factory.block_restart_interval", "16" }, @@ -1556,35 +1637,34 @@ private IDictionary GetHashDbOptions() { "max_bytes_for_level_multiplier", "10" }, { "max_bytes_for_level_base", "256000000" }, }; - } /// - /// Allow num of l0 file to grow very large. This dramatically increase read response time by about - /// (l0FileNumTarget / (default num (4) + max level usually (4)). but it saves write bandwidth as l0->l1 happens - /// in larger size. In addition to that, the large base l1 size means the number of level is a bit lower. - /// Note: Regardless of max_open_files config, the number of files handle jumped by this number when compacting. It - /// could be that l0->l1 compaction does not (or cant?) follow the max_open_files limit. + /// Allow number of l0 files to grow very large. This dramatically increases read response time by about + /// (l0FileNumTarget / (default num (4) + max level usually (4)), but it saves write bandwidth as l0->l1 happens + /// in larger size. In addition to that, the large base l1 size means the number of levels is a bit lower. + /// Note: Regardless of max_open_files config, the number of files handles jumped by this number when compacting. It + /// could be that l0->l1 compaction does not (or can't?) follow the max_open_files limit. /// - /// + /// /// This caps the maximum allowed number of l0 files, which is also the read response time amplification. /// /// private IDictionary GetHeavyWriteOptions(ulong l0SizeTarget) { // Make buffer (probably) smaller so that it does not take too much memory to have many of them. - // More buffer means more parallel flush, but each read have to go through all buffer one by one much like l0 + // More buffer means more parallel flush, but each read has to go through all buffers one by one, much like l0 // but no io, only cpu. - // bufferSize*maxBufferNumber = 16MB*Core count, which is the max memory used, which tend to be the case as its now - // stalled by compaction instead of flush. - // The buffer is not compressed unlike l0File, so to account for it, its size need to be slightly larger. + // bufferSize*maxBufferNumber = 16MB*Core count, which is the max memory used, which tends to be the case as it's now + // stalled by compaction instead of a flush. + // The buffer is not compressed unlike l0File, so to account for it, its size needs to be slightly larger. ulong targetFileSize = (ulong)16.MiB(); ulong bufferSize = (ulong)(targetFileSize / _perTableDbConfig.CompressibilityHint); ulong l0FileSize = targetFileSize * (ulong)_minWriteBufferToMerge; ulong maxBufferNumber = (ulong)Environment.ProcessorCount; - // Guide recommend to have l0 and l1 to be the same size. They have to be compacted together so if l1 is larger, + // Guide recommends having l0 and l1 to be the same size. They have to be compacted together, so if l1 is larger, // the extra size in l1 is basically extra rewrites. If l0 is larger... then I don't know why not. Even so, it seems to - // always get triggered when l0 size exceed max_bytes_for_level_base even if file num is less than l0FileNumTarget. + // always get triggered when l0 size exceeds max_bytes_for_level_base even if the file number is less than l0FileNumTarget. ulong l0FileNumTarget = l0SizeTarget / l0FileSize; ulong l1SizeTarget = l0SizeTarget; @@ -1612,10 +1692,10 @@ private IDictionary GetDisableCompactionOptions() IDictionary heavyWriteOption = GetHeavyWriteOptions((ulong)32.GiB()); heavyWriteOption["disable_auto_compactions"] = "true"; - // Increase the size of the write buffer, which reduces the number of l0 file by 4x. This does slows down + // Increase the size of the write buffer, which reduces the number of l0 files by 4x. This does slow down // the memtable a little bit. So if you are not write limited, you'll get memtable limited instead. // This does increase the total memory buffer size, but counterintuitively, this reduces overall memory usage - // as it ran out of bloom filter cache so it need to do actual IO. + // as it ran out of bloom filter cache, so it needs to do actual IO. heavyWriteOption["write_buffer_size"] = 64.MiB().ToString(); return heavyWriteOption; @@ -1625,17 +1705,17 @@ private IDictionary GetDisableCompactionOptions() private static IDictionary GetBlobFilesOptions() { // Enable blob files, see: https://rocksdb.org/blog/2021/05/26/integrated-blob-db.html - // This is very useful for blocks, as it almost eliminate 95% of the compaction as the main db no longer + // This is very useful for blocks, as it almost eliminates 95% of the compaction as the main db no longer // store the actual data, but only points to blob files. This config reduces total blocks db writes from about - // 4.6 TB to 0.76 TB, where even the the WAL took 0.45 TB (wal is not compressed), with peak writes of about 300MBps, + // 4.6 TB to 0.76 TB, where even the WAL took 0.45 TB (wal is not compressed), with peak writes of about 300MBps, // it may not even saturate a SATA SSD on a 1GBps internet. - // You don't want to turn this on on other DB as it does add an indirection which take up an additional iop. + // You don't want to turn this on other DB as it does add an indirection which take up an additional iop. // But for large values like blocks (3MB decompressed to 8MB), the response time increase is negligible. - // However without a large buffer size, it will create tens of thousands of small files. There are - // various workaround it, but it all increase total writes, which defeats the purpose. - // Additionally, as the `max_bytes_for_level_base` is set to very low, existing user will suddenly - // get a lot of compaction. So cant turn this on all the time. Turning this back off, will just put back + // However, without a large buffer size, it will create tens of thousands of small files. There are + // various workaround it, but it all increases total writes, which defeats the purpose. + // Additionally, as the `max_bytes_for_level_base` is set to very low, existing users will suddenly + // get a lot of compaction. So can't turn this on all the time. Turning this back off will just put back // new data to SST files. return new Dictionary() @@ -1643,9 +1723,9 @@ private static IDictionary GetBlobFilesOptions() { "enable_blob_files", "true" }, { "blob_compression_type", "kSnappyCompression" }, - // Make file size big, so we have less of them. + // Make the file size big, so we have less of them. { "write_buffer_size", 256.MiB().ToString() }, - // Current memtable + 2 concurrent writes. Can't have too many of these as it take up RAM. + // Current memtable + 2 concurrent writes. Can't have too many of these as it takes up RAM. { "max_write_buffer_number", 3.ToString() }, // These two are SST files instead of the blobs, which are now much smaller. @@ -1658,9 +1738,9 @@ private static IDictionary GetBlobFilesOptions() /// Iterators should not be kept for long as it will pin some memory block and sst file. This would show up as /// temporary higher disk usage or memory usage. /// - /// This class handles a periodic timer which periodically dispose all iterator. + /// This class handles a periodic timer that periodically disposes all iterators. /// - internal class IteratorManager : IDisposable + public class IteratorManager : IDisposable { private readonly ManagedIterators _readaheadIterators = new(); private readonly ManagedIterators _readaheadIterators2 = new(); @@ -1703,35 +1783,23 @@ public void Dispose() public RentWrapper Rent(ReadFlags flags) { - - ManagedIterators iterators = _readaheadIterators; - if ((flags & ReadFlags.HintReadAhead2) != 0) - { - iterators = _readaheadIterators2; - } - else if ((flags & ReadFlags.HintReadAhead3) != 0) - { - iterators = _readaheadIterators3; - } - + ManagedIterators iterators = GetIterators(flags); IteratorHolder holder = iterators.Value!; // If null, we create a new one. Iterator? iterator = Interlocked.Exchange(ref holder.Iterator, null); return new RentWrapper(iterator ?? _rocksDb.NewIterator(_cf, _readOptions), flags, this); } - private void Return(Iterator iterator, ReadFlags flags) + private ManagedIterators GetIterators(ReadFlags flags) => flags switch { - ManagedIterators iterators = _readaheadIterators; - if ((flags & ReadFlags.HintReadAhead2) != 0) - { - iterators = _readaheadIterators2; - } - else if ((flags & ReadFlags.HintReadAhead3) != 0) - { - iterators = _readaheadIterators3; - } + _ when (flags & ReadFlags.HintReadAhead2) != 0 => _readaheadIterators2, + _ when (flags & ReadFlags.HintReadAhead3) != 0 => _readaheadIterators3, + _ => _readaheadIterators + }; + private void Return(Iterator iterator, ReadFlags flags) + { + ManagedIterators iterators = GetIterators(flags); IteratorHolder holder = iterators.Value!; // We don't keep using the same iterator for too long. @@ -1745,7 +1813,7 @@ private void Return(Iterator iterator, ReadFlags flags) holder.Usage++; Iterator? oldIterator = Interlocked.Exchange(ref holder.Iterator, iterator); - // Well... this is weird. I'll just dispose it. + // Well... this is weird. I'll just dispose of it. oldIterator?.Dispose(); } @@ -1753,28 +1821,21 @@ public readonly struct RentWrapper(Iterator iterator, ReadFlags flags, IteratorM { public Iterator Iterator => iterator; - public void Dispose() - { - manager.Return(iterator, flags); - } + public void Dispose() => manager.Return(iterator, flags); } // Note: use of threadlocal is very important as the seek forward is fast, but the seek backward is not fast. - private sealed class ManagedIterators : ThreadLocal + private sealed class ManagedIterators() : ThreadLocal(static () => new IteratorHolder(), trackAllValues: true) { private bool _disposed = false; - public ManagedIterators() : base(static () => new IteratorHolder(), trackAllValues: true) - { - } - public void ClearIterators() { if (_disposed) return; - if (Values is null) return; - foreach (IteratorHolder iterator in Values) + if (Values is not { } values) return; + foreach (IteratorHolder iterator in values) { - iterator.Dispose(); + iterator?.Dispose(); } } @@ -1786,7 +1847,7 @@ public void DisposeAll() protected override void Dispose(bool disposing) { - // Note: This is called from finalizer thread, so we can't use foreach to dispose all values + // Note: This is called from finalizer thread, so we can't use foreach to dispose of all values Value?.Dispose(); Value = null!; _disposed = true; @@ -1805,4 +1866,69 @@ public void Dispose() } } } + + public byte[]? FirstKey + { + get + { + using Iterator iterator = _db.NewIterator(); + iterator.SeekToFirst(); + return iterator.Valid() ? iterator.GetKeySpan().ToArray() : null; + } + } + + public byte[]? LastKey + { + get + { + using Iterator iterator = _db.NewIterator(); + iterator.SeekToLast(); + return iterator.Valid() ? iterator.GetKeySpan().ToArray() : null; + } + } + + public ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey) => GetViewBetween(firstKey, lastKey, null); + + internal ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey, ColumnFamilyHandle? cf) + { + ReadOptions readOptions = CreateReadOptions(); + + IntPtr iterateLowerBound; + IntPtr iterateUpperBound; + + 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 = CreateIterator(readOptions, cf); + return new RocksdbSortedView(iterator, iterateLowerBound, iterateUpperBound); + } + + public IKeyValueStoreSnapshot CreateSnapshot() + { + Snapshot snapshot = _db.CreateSnapshot(); + return new RocksDbSnapshot(this, () => + { + ReadOptions readOptions = CreateReadOptions(); + 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 000000000000..a47654fbd373 --- /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/MergeOperatorAdapter.cs b/src/Nethermind/Nethermind.Db.Rocks/MergeOperatorAdapter.cs new file mode 100644 index 000000000000..548d3d9556a8 --- /dev/null +++ b/src/Nethermind/Nethermind.Db.Rocks/MergeOperatorAdapter.cs @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Nethermind.Core.Collections; +using RocksDbSharp; + +namespace Nethermind.Db.Rocks; + +// Also see RocksDbSharp.MergeOperatorImpl +internal class MergeOperatorAdapter(IMergeOperator inner) : MergeOperator +{ + public string Name => inner.Name; + + // TODO: fix and return array ptr instead of copying to unmanaged memory? + private static unsafe nint GetResult(ArrayPoolList? data, out nint resultLength, out byte success) + { + if (data is null) + { + success = 0; + resultLength = nint.Zero; + return nint.Zero; + } + + using (data) + { + void* resultPtr = NativeMemory.Alloc((uint)data.Count); + var result = new Span(resultPtr, data.Count); + data.AsSpan().CopyTo(result); + + resultLength = result.Length; + + // Fixing RocksDbSharp invalid callback signature, TODO: submit an issue/PR + Unsafe.SkipInit(out success); + Unsafe.As(ref success) = 1; + + return (nint)resultPtr; + } + } + + public unsafe nint PartialMerge(nint key, nuint keyLength, nint operandsList, nint operandsListLength, int numOperands, out byte success, out nint newValueLength) + { + var keyBytes = new Span((void*)key, (int)keyLength); + var enumerator = new RocksDbMergeEnumerator(new((void*)operandsList, numOperands), new((void*)operandsListLength, numOperands)); + + ArrayPoolList? result = inner.PartialMerge(keyBytes, enumerator); + return GetResult(result, out newValueLength, out success); + } + + public unsafe nint FullMerge(nint key, nuint keyLength, nint existingValue, nuint existingValueLength, nint operandsList, nint operandsListLength, int numOperands, out byte success, out nint newValueLength) + { + var keyBytes = new ReadOnlySpan((void*)key, (int)keyLength); + bool hasExistingValue = existingValue != nint.Zero; + Span existingValueBytes = hasExistingValue ? new((void*)existingValue, (int)existingValueLength) : []; + var enumerator = new RocksDbMergeEnumerator(existingValueBytes, hasExistingValue, new((void*)operandsList, numOperands), new((void*)operandsListLength, numOperands)); + + ArrayPoolList? result = inner.FullMerge(keyBytes, enumerator); + return GetResult(result, out newValueLength, out success); + } + + unsafe void MergeOperator.DeleteValue(nint value, nuint valueLength) => NativeMemory.Free((void*)value); +} diff --git a/src/Nethermind/Nethermind.Db.Rocks/RocksDbExtensions.cs b/src/Nethermind/Nethermind.Db.Rocks/RocksDbExtensions.cs index ed06c29f1efa..91341a735219 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/RocksDbExtensions.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/RocksDbExtensions.cs @@ -28,9 +28,6 @@ internal static unsafe Span GetSpan(this RocksDb db, scoped ReadOnlySpan(); + 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 000000000000..ef40401db615 --- /dev/null +++ b/src/Nethermind/Nethermind.Db.Rocks/RocksDbReader.cs @@ -0,0 +1,122 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Runtime.InteropServices; +using Nethermind.Core; +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 new file mode 100644 index 000000000000..02e9a027681d --- /dev/null +++ b/src/Nethermind/Nethermind.Db.Rocks/RocksdbSortedView.cs @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Runtime.InteropServices; +using Nethermind.Core; +using RocksDbSharp; + +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, 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) + { + if (_started) + throw new InvalidOperationException($"{nameof(StartBefore)} can only be called before starting iteration."); + + _iterator.SeekForPrev(value); + return _started = _iterator.Valid(); + } + + public bool MoveNext() + { + if (!_started) + { + _iterator.SeekToFirst(); + _started = true; + } + else + { + _iterator.Next(); + } + return _iterator.Valid(); + } + + public ReadOnlySpan CurrentKey => _iterator.GetKeySpan(); + public ReadOnlySpan CurrentValue => _iterator.GetValueSpan(); +} diff --git a/src/Nethermind/Nethermind.Db.Rocks/Statistics/DbMetricsUpdater.cs b/src/Nethermind/Nethermind.Db.Rocks/Statistics/DbMetricsUpdater.cs index db5ffcb16004..e541f05e0997 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 8ddcde89941a..25de2cef6612 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 98279f33c4ad..a94366c63761 100644 --- a/src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs +++ b/src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using Nethermind.Core; +using Nethermind.Core.Buffers; using Nethermind.Core.Extensions; using Nethermind.JsonRpc; using Nethermind.JsonRpc.Client; @@ -13,35 +15,25 @@ namespace Nethermind.Db.Rpc { - public class RpcDb : IDb + public class RpcDb( + string dbName, + IJsonSerializer jsonSerializer, + IJsonRpcClient rpcClient, + ILogManager logManager, + IDb recordDb) + : IDb { - private readonly string _dbName; - private readonly IJsonSerializer _jsonSerializer; - private readonly ILogger _logger; - private readonly IJsonRpcClient _rpcClient; - private readonly IDb _recordDb; - - public RpcDb(string dbName, IJsonSerializer jsonSerializer, IJsonRpcClient rpcClient, ILogManager logManager, IDb recordDb) - { - _dbName = dbName; - _rpcClient = rpcClient ?? throw new ArgumentNullException(nameof(rpcClient)); - _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer)); - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - _recordDb = recordDb; - } + private readonly IJsonSerializer _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer)); + private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + private readonly IJsonRpcClient _rpcClient = rpcClient ?? throw new ArgumentNullException(nameof(rpcClient)); public void Dispose() { _logger.Info($"Disposing RPC DB {Name}"); - _recordDb.Dispose(); + recordDb.Dispose(); } - public long GetSize() => 0; - public long GetCacheSize() => 0; - public long GetIndexSize() => 0; - public long GetMemtableSize() => 0; - - public string Name { get; } = "RpcDb"; + public string Name => "RpcDb"; public byte[] this[ReadOnlySpan key] { @@ -49,75 +41,43 @@ public byte[] this[ReadOnlySpan key] set => Set(key, value); } - public void Set(ReadOnlySpan key, byte[] value, WriteFlags flags = WriteFlags.None) - { - throw new InvalidOperationException("RPC DB does not support writes"); - } - - public byte[] Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - { - return GetThroughRpc(key); - } - + public void Set(ReadOnlySpan key, byte[] value, WriteFlags flags = WriteFlags.None) => ThrowWritesNotSupported(); + public byte[] Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => GetThroughRpc(key); public KeyValuePair[] this[byte[][] keys] => keys.Select(k => new KeyValuePair(k, GetThroughRpc(k))).ToArray(); - - public void Remove(ReadOnlySpan key) - { - throw new InvalidOperationException("RPC DB does not support writes"); - } - - public bool KeyExists(ReadOnlySpan key) - { - return GetThroughRpc(key) is not null; - } - - public IDb Innermost => this; // record db is just a helper DB here - public void Flush() { } + public void Remove(ReadOnlySpan key) => ThrowWritesNotSupported(); + public bool KeyExists(ReadOnlySpan key) => GetThroughRpc(key) is not null; public void Flush(bool onlyWal = false) { } - public void Clear() { } - - public IEnumerable> GetAll(bool ordered = false) => _recordDb.GetAll(); - - public IEnumerable GetAllKeys(bool ordered = false) => _recordDb.GetAllKeys(); - - public IEnumerable GetAllValues(bool ordered = false) => _recordDb.GetAllValues(); - + public IEnumerable> GetAll(bool ordered = false) => recordDb.GetAll(); + public IEnumerable GetAllKeys(bool ordered = false) => recordDb.GetAllKeys(); + public IEnumerable GetAllValues(bool ordered = false) => recordDb.GetAllValues(); public IWriteBatch StartWriteBatch() { - throw new InvalidOperationException("RPC DB does not support writes"); + ThrowWritesNotSupported(); + // Make compiler happy + return null!; } private byte[] GetThroughRpc(ReadOnlySpan key) { - string responseJson = _rpcClient.Post("debug_getFromDb", _dbName, key.ToHexString()).Result; + string responseJson = _rpcClient.Post("debug_getFromDb", dbName, key.ToHexString()).Result; JsonRpcSuccessResponse response = _jsonSerializer.Deserialize(responseJson); byte[] value = null; if (response.Result is not null) { value = Bytes.FromHexString((string)response.Result); - if (_recordDb is not null) + if (recordDb is not null) { - _recordDb[key] = value; + recordDb[key] = value; } } return value; } - public Span GetSpan(ReadOnlySpan key) - { - return Get(key); - } - - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags) - { - Set(key, value.ToArray(), writeFlags); - } - - public void DangerousReleaseMemory(in ReadOnlySpan span) - { - } + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags 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 fa6e59d0b24c..7e975e7a3ea5 100644 --- a/src/Nethermind/Nethermind.Db.Test/ColumnsDbTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/ColumnsDbTests.cs @@ -5,7 +5,6 @@ using System.IO; using FluentAssertions; using Nethermind.Core; -using Nethermind.Core.Extensions; using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; using Nethermind.Db.Rocks; @@ -107,4 +106,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/CompressingStoreTests.cs b/src/Nethermind/Nethermind.Db.Test/CompressingStoreTests.cs index 117a4b6d52b8..cb7d63a2d4ba 100644 --- a/src/Nethermind/Nethermind.Db.Test/CompressingStoreTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/CompressingStoreTests.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Test; @@ -71,7 +72,15 @@ public void EOAWithSPan() ctx.Compressed.PutSpan(Key, encoded.Bytes); Assert.That(encoded.Bytes, Is.EqualTo(ctx.Compressed[Key]).AsCollection); - Assert.That(encoded.Bytes, Is.EqualTo(ctx.Compressed.GetSpan(Key).ToArray()).AsCollection); + Span span = ctx.Compressed.GetSpan(Key); + try + { + Assert.That(encoded.Bytes, Is.EqualTo(span.ToArray()).AsCollection); + } + finally + { + ctx.Compressed.DangerousReleaseMemory(span); + } ctx.Wrapped[Key]!.Length.Should().Be(5); } diff --git a/src/Nethermind/Nethermind.Db.Test/Config/PerTableDbConfigTests.cs b/src/Nethermind/Nethermind.Db.Test/Config/PerTableDbConfigTests.cs index fac5614b55c3..67bc076d8834 100644 --- a/src/Nethermind/Nethermind.Db.Test/Config/PerTableDbConfigTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/Config/PerTableDbConfigTests.cs @@ -5,7 +5,6 @@ using System.Reflection; using FluentAssertions; using Nethermind.Db.Rocks.Config; -using NSubstitute.Extensions; using NUnit.Framework; namespace Nethermind.Db.Test.Config; diff --git a/src/Nethermind/Nethermind.Db.Test/DbOnTheRocksTests.cs b/src/Nethermind/Nethermind.Db.Test/DbOnTheRocksTests.cs index ed9f36e4b072..479c2e3a4609 100644 --- a/src/Nethermind/Nethermind.Db.Test/DbOnTheRocksTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/DbOnTheRocksTests.cs @@ -13,7 +13,6 @@ using Nethermind.Core; using Nethermind.Core.Buffers; using Nethermind.Core.Crypto; -using Nethermind.Core.Exceptions; using Nethermind.Core.Extensions; using Nethermind.Core.Test; using Nethermind.Db.Rocks; @@ -31,6 +30,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 +38,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 +124,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 +160,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 +369,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([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 }); - _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 })); + 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 +411,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()); } } @@ -387,6 +488,49 @@ public void Smoke_test_iterator() allValues[0].Value.Should().BeEquivalentTo(new byte[] { 4, 5, 6 }); } + [Test] + public void IteratorWorks() + { + _db.Should().BeAssignableTo(); + ISortedKeyValueStore sortedKeyValue = (ISortedKeyValueStore)_db; + + int entryCount = 3; + byte i; + for (i = 0; i < entryCount; i++) + { + _db[[i, i, i]] = [i, i, i]; + } + + i--; + + 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.Should().Be((byte)entryCount); + } + + CheckView(sortedKeyValue); + + using var snapshot = ((IKeyValueStoreWithSnapshot)_db).CreateSnapshot(); + for (i = 0; i < entryCount; i++) + { + _db[[i, i, i]] = [(byte)(i + 1), (byte)(i + 1), (byte)(i + 1)]; + } + + CheckView((ISortedKeyValueStore)snapshot); + } + [Test] public void TestExtractOptions() { @@ -398,12 +542,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 27a1344fc3fb..8b264a294a43 100644 --- a/src/Nethermind/Nethermind.Db.Test/DbTrackerTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/DbTrackerTests.cs @@ -1,9 +1,24 @@ +// 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.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 +29,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 +50,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 7656307b45a6..77cd44d8df1b 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/LogIndex/LogIndexStorageCompactorTests.cs b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageCompactorTests.cs new file mode 100644 index 000000000000..e23c4d8a1cba --- /dev/null +++ b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageCompactorTests.cs @@ -0,0 +1,184 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Db.LogIndex; +using Nethermind.Logging; +using NSubstitute; +using NUnit.Framework; +using static Nethermind.Db.LogIndex.LogIndexStorage; + +namespace Nethermind.Db.Test.LogIndex; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class LogIndexStorageCompactorTests +{ + private const int RaceConditionTestRepeat = 3; + + private static ILogIndexStorage MockStorage(int? min = null, int? max = null) + { + ILogIndexStorage storage = Substitute.For(); + storage.MinBlockNumber.Returns(min); + storage.MaxBlockNumber.Returns(max); + return storage; + } + + private static Compactor CreateCompactor(ILogIndexStorage storage, IDbMeta? db = null, int compactionDistance = 100) => + new(storage, db ?? new FakeDb(), LimboLogs.Instance.GetClassLogger(), compactionDistance); + + private static Compactor CreateCompactor(ILogIndexStorage storage, int compactionDistance = 100) => + CreateCompactor(storage, db: null, compactionDistance: compactionDistance); + + [TestCase(0, 50, 0, 50, 100, ExpectedResult = false, Description = "No change from baseline")] + [TestCase(0, 0, 0, 99, 100, ExpectedResult = false, Description = "99 blocks forward, threshold 100")] + [TestCase(0, 0, 0, 100, 100, ExpectedResult = true, Description = "Exactly at threshold")] + [TestCase(100, 200, 50, 250, 100, ExpectedResult = true, Description = "Both directions sum to threshold")] + public async Task TryEnqueue_Respects_CompactionDistance( + int initMin, int initMax, int newMin, int newMax, int compactionDistance + ) + { + ILogIndexStorage storage = MockStorage(min: initMin, max: initMax); + using Compactor compactor = CreateCompactor(storage, compactionDistance); + + storage.MinBlockNumber.Returns(newMin); + storage.MaxBlockNumber.Returns(newMax); + + bool result = compactor.TryEnqueue(); + + await compactor.StopAsync(); + return result; + } + + [Test] + [Repeat(RaceConditionTestRepeat)] + public async Task TryEnqueue_During_Compact_Does_Not_Run_Compact_Concurrently() + { + const int compactionDistance = 10; + var compactionDelay = TimeSpan.FromMilliseconds(200); + + ILogIndexStorage storage = MockStorage(min: 0, max: 0); + FakeDb db = new(compactionDelay); + using Compactor compactor = CreateCompactor(storage, db, compactionDistance); + + // Trigger first compaction + storage.MaxBlockNumber.Returns(compactionDistance); + Assert.That(compactor.TryEnqueue(), Is.True); + + await Task.Delay(compactionDelay / 4); + + // Try to cause a second compaction + storage.MaxBlockNumber.Returns(storage.MaxBlockNumber + compactionDistance); + compactor.TryEnqueue(); + + await compactor.ForceAsync(); + await compactor.StopAsync(); + } + + [TestCase(false)] + [TestCase(true)] + [Repeat(RaceConditionTestRepeat)] + [SuppressMessage("ReSharper", "AccessToDisposedClosure")] + public async Task ForceAsync_Does_Not_Run_Compact_Concurrently(bool duringCompact) + { + const int compactionDistance = 10; + var compactionDelay = TimeSpan.FromMilliseconds(200); + + ILogIndexStorage storage = MockStorage(min: 0, max: 0); + FakeDb db = new(compactionDelay); + using Compactor compactor = CreateCompactor(storage, db, compactionDistance); + + if (duringCompact) + { + storage.MaxBlockNumber.Returns(compactionDistance); + compactor.TryEnqueue(); + + await Task.Delay(compactionDelay / 4); + } + + const int concurrentCalls = 5; + await Task.WhenAll(Enumerable.Range(0, concurrentCalls).Select(_ => Task.Run(compactor.ForceAsync)).ToArray()); + + await compactor.StopAsync(); + } + + [Test] + public async Task TryEnqueue_Resets_Baseline_After_Enqueue() + { + const int compactionDistance = 10; + + ILogIndexStorage storage = MockStorage(min: 0, max: 0); + using Compactor compactor = CreateCompactor(storage, compactionDistance); + + storage.MaxBlockNumber.Returns(compactionDistance); + Assert.That(compactor.TryEnqueue(), Is.True); + + await Task.Delay(100); + + storage.MaxBlockNumber.Returns(storage.MaxBlockNumber + compactionDistance / 2); + Assert.That(compactor.TryEnqueue(), Is.False); + + await Task.Delay(100); + + storage.MaxBlockNumber.Returns(storage.MaxBlockNumber + compactionDistance / 2); + Assert.That(compactor.TryEnqueue(), Is.True); + + await compactor.StopAsync(); + } + + [Test] + public async Task TryEnqueue_Returns_False_After_Stop() + { + const int compactionDistance = 10; + + ILogIndexStorage storage = MockStorage(min: 0, max: 0); + using Compactor compactor = CreateCompactor(storage, new NonCompactableDb(), compactionDistance); + + await compactor.StopAsync(); + + storage.MaxBlockNumber.Returns(compactionDistance); + Assert.That(compactor.TryEnqueue(), Is.False); + } + + // Fails on compaction attempt + private class NonCompactableDb : IDbMeta + { + private class CompactionException : Exception; + + public void Compact() => throw new CompactionException(); + public void Flush(bool onlyWal = false) { } + } + + // Simulates compaction with Thread.Sleep and fail on concurrent calls + private class FakeDb(TimeSpan? compactDelay = null) : IDbMeta + { + private class ConcurrentCompactionException : Exception; + + private readonly TimeSpan _compactDelay = compactDelay ?? TimeSpan.Zero; + + private int _compacting; + + public void Compact() + { + if (Interlocked.CompareExchange(ref _compacting, 1, 0) != 0) + throw new ConcurrentCompactionException(); + + try + { + if (_compactDelay > TimeSpan.Zero) + Thread.Sleep(_compactDelay); + } + finally + { + Interlocked.Exchange(ref _compacting, 0); + } + } + + public void Flush(bool onlyWal = false) { } + } +} diff --git a/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageIntegrationTests.cs b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageIntegrationTests.cs new file mode 100644 index 000000000000..86e75574ea49 --- /dev/null +++ b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageIntegrationTests.cs @@ -0,0 +1,1110 @@ +// 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.IO; +using System.Linq; +using System.Runtime.ExceptionServices; +using System.Threading; +using System.Threading.Tasks; +using MathNet.Numerics.Random; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test; +using Nethermind.Core.Test.Builders; +using Nethermind.Db.LogIndex; +using Nethermind.Db.Rocks; +using Nethermind.Db.Rocks.Config; +using Nethermind.Logging; +using Nethermind.TurboPForBindings; +using NUnit.Framework; +using NUnit.Framework.Interfaces; +using static Nethermind.Db.LogIndex.LogIndexStorage; + +namespace Nethermind.Db.Test.LogIndex +{ + [TestFixtureSource(nameof(TestCases))] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] + public class LogIndexStorageIntegrationTests(LogIndexStorageIntegrationTests.TestData testData) + { + private const int RaceConditionTestRepeat = 3; + +#if DEBUG + private static bool LogStatistics => true; +#else + private static bool LogStatistics => false; +#endif + + public static readonly TestFixtureData[] TestCases = + [ + new(new TestData(10, 100) { Compression = CompressionAlgorithm.Best.Key }), + new(new TestData(5, 200) { Compression = nameof(TurboPFor.p4nd1enc128v32) }), + new(new TestData(10, 100) { Compression = CompressionAlgorithm.Best.Key, ExtendedGetRanges = true }) { RunState = RunState.Explicit }, + new(new TestData(100, 100) { Compression = nameof(TurboPFor.p4nd1enc128v32) }) { RunState = RunState.Explicit }, + new(new TestData(100, 200) { Compression = CompressionAlgorithm.Best.Key }) { RunState = RunState.Explicit } + ]; + + private string _dbPath = null!; + private IDbFactory _dbFactory = null!; + private readonly List _createdStorages = []; + + private ILogIndexStorage CreateLogIndexStorage( + int compactionDistance = 262_144, int compressionParallelism = 16, int maxReorgDepth = 64, IDbFactory? dbFactory = null, + string? compressionAlgo = null, int? failOnBlock = null, int? failOnCallN = null, bool failOnMerge = false + ) + { + LogIndexConfig config = new() + { + Enabled = true, + CompactionDistance = compactionDistance, + MaxCompressionParallelism = compressionParallelism, + MaxReorgDepth = maxReorgDepth, + CompressionAlgorithm = compressionAlgo ?? testData.Compression + }; + + ILogIndexStorage storage = failOnBlock is null && failOnCallN is null + ? new LogIndexStorage(dbFactory ?? _dbFactory, LimboLogs.Instance, config) + : new SaveFailingLogIndexStorage(dbFactory ?? _dbFactory, LimboLogs.Instance, config) + { + FailOnBlock = failOnBlock ?? 0, + FailOnCallN = failOnCallN ?? 0, + FailOnMerge = failOnMerge + }; + + return storage.AddTo(_createdStorages); + } + + [SetUp] + public void Setup() + { + _dbPath = $"{nameof(LogIndexStorageIntegrationTests)}/{Guid.NewGuid():N}"; + + if (Directory.Exists(_dbPath)) + Directory.Delete(_dbPath, true); + + Directory.CreateDirectory(_dbPath); + + var config = new DbConfig(); + var configFactory = new RocksDbConfigFactory(config, new PruningConfig(), new TestHardwareInfo(0), LimboLogs.Instance); + _dbFactory = new RocksDbFactory(configFactory, config, new HyperClockCacheWrapper(), new TestLogManager(), _dbPath); + } + + [TearDown] + public async Task TearDown() + { + foreach (ILogIndexStorage storage in _createdStorages) + { + await using (storage) + await storage.StopAsync(); + } + + if (!Directory.Exists(_dbPath)) + return; + + try + { + Directory.Delete(_dbPath, true); + } + catch + { + // ignore + } + } + + [OneTimeSetUp] + //[OneTimeTearDown] // Causes dispose issues, seems to be executed out-of-order + public static void RemoveRootFolder() + { + if (!Directory.Exists(nameof(LogIndexStorageIntegrationTests))) + return; + + try + { + Directory.Delete(nameof(LogIndexStorageIntegrationTests), true); + } + catch + { + // ignore + } + } + + [Combinatorial] + public async Task Set_Get_Test( + [Values(100, 200, int.MaxValue)] int compactionDistance, + [Values(1, 8, 16)] byte ioParallelism, + [Values] bool isBackwardsSync, + [Values] bool compact + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance, ioParallelism); + + BlockReceipts[][] batches = isBackwardsSync ? Reverse(testData.Batches) : testData.Batches; + await AddReceiptsAsync(logIndexStorage, batches, isBackwardsSync); + + if (compact) + await CompactAsync(logIndexStorage); + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task SetIntersecting_Get_Test( + [Values(100, 200, int.MaxValue)] int compactionDistance, + [Values(1, 8, 16)] byte ioParallelism, + [Values] bool isBackwardsSync, + [Values] bool compact + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance, ioParallelism); + + BlockReceipts[][] batches = isBackwardsSync ? Reverse(testData.Batches) : testData.Batches; + batches = Intersect(batches); + await AddReceiptsAsync(logIndexStorage, batches, isBackwardsSync); + + if (compact) + await CompactAsync(logIndexStorage); + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task BackwardsSet_Set_Get_Test( + [Values(100, 200, int.MaxValue)] int compactionDistance + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + BlockReceipts[][] batches = testData.Batches; + var half = batches.Length / 2; + + for (var i = 0; i < half + 1; i++) + { + if (half + i < batches.Length) + await AddReceiptsAsync(logIndexStorage, [batches[half + i]], isBackwardsSync: false); + if (i != 0 && half - i >= 0) + await AddReceiptsAsync(logIndexStorage, Reverse([batches[half - i]]), isBackwardsSync: true); + } + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + [Repeat(RaceConditionTestRepeat)] + [SuppressMessage("ReSharper", "AccessToDisposedClosure")] + public async Task Concurrent_BackwardsSet_Set_Get_Test( + [Values(100, int.MaxValue)] int compactionDistance + ) + { + await using (ILogIndexStorage setStorage = CreateLogIndexStorage(compactionDistance)) + { + int half = testData.Batches.Length / 2; + BlockReceipts[][] batches = testData.Batches + .Select((b, i) => i >= half ? b : b.Reverse().ToArray()) + .ToArray(); + + var forwardTask = Task.Run(async () => + { + for (int i = half; i < batches.Length; i++) + { + BlockReceipts[] batch = batches[i]; + await AddReceiptsAsync(setStorage, [batch], isBackwardsSync: false); + + Assert.That(setStorage.MinBlockNumber, Is.LessThanOrEqualTo(batch[0].BlockNumber)); + Assert.That(setStorage.MaxBlockNumber, Is.EqualTo(batch[^1].BlockNumber)); + } + }); + + var backwardTask = Task.Run(async () => + { + for (int i = half - 1; i >= 0; i--) + { + BlockReceipts[] batch = batches[i]; + await AddReceiptsAsync(setStorage, [batch], isBackwardsSync: true); + + Assert.That(setStorage.MinBlockNumber, Is.EqualTo(batch[^1].BlockNumber)); + Assert.That(setStorage.MaxBlockNumber, Is.GreaterThanOrEqualTo(batch[0].BlockNumber)); + } + }); + + await Task.WhenAll(forwardTask, backwardTask); + } + + // Create new storage to force-load everything from DB + await using (ILogIndexStorage testStorage = CreateLogIndexStorage(compactionDistance)) + VerifyReceipts(testStorage, testData); + } + + [Combinatorial] + [Repeat(RaceConditionTestRepeat)] + [SuppressMessage("ReSharper", "AccessToDisposedClosure")] + public async Task Concurrent_BackwardSet_Reorg_Get_Test( + [Values(1, 5, 10)] int reorgDepth, + [Values(100, int.MaxValue)] int compactionDistance + ) + { + int half = testData.Batches.Length / 2; + BlockReceipts[][] forwardBatches = testData.Batches.Skip(half).ToArray(); + BlockReceipts[][] backwardBatches = testData.Batches.Take(half).ToArray(); + + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + // Add forward blocks first to establish the head of the chain + await AddReceiptsAsync(logIndexStorage, forwardBatches, isBackwardsSync: false); + + BlockReceipts[] reorgBlocks = forwardBatches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); + + var reorgTask = Task.Run(async () => + { + foreach (BlockReceipts block in reorgBlocks) + await logIndexStorage.RemoveReorgedAsync(block); + }); + + var backwardTask = Task.Run(async () => + { + foreach (BlockReceipts[] batch in Reverse(backwardBatches)) + await AddReceiptsAsync(logIndexStorage, [batch], isBackwardsSync: true); + }); + + await Task.WhenAll(reorgTask, backwardTask); + + VerifyReceipts(logIndexStorage, testData, excludedBlocks: reorgBlocks, maxBlock: reorgBlocks[0].BlockNumber - 1); + } + + [Combinatorial] + public async Task Set_ReorgLast_Get_Test( + [Values(1, 5, 20)] int reorgDepth, + [Values(100, int.MaxValue)] int compactionDistance + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + BlockReceipts[] reorgBlocks = testData.Batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); + foreach (BlockReceipts block in reorgBlocks) + await logIndexStorage.RemoveReorgedAsync(block); + + VerifyReceipts(logIndexStorage, testData, excludedBlocks: reorgBlocks, maxBlock: reorgBlocks[0].BlockNumber - 1); + } + + [Ignore("Out-of-order reorgs are not supported ATM: only the first (by write order) Reorg operand per key is applied by MergeOperator.")] + [Combinatorial] + public async Task Set_ReorgOutOfOrder_Get_Test( + [Values(2, 5, 10)] int reorgDepth, + [Values(100, int.MaxValue)] int compactionDistance + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + BlockReceipts[] reverseReorg = testData.Batches.SelectMany(b => b).Reverse().Take(reorgDepth).ToArray(); + + foreach (BlockReceipts block in reverseReorg) + await logIndexStorage.RemoveReorgedAsync(block); + + // Full data verification: would fail because MergeOperator only applies the first Reorg operand + // (the highest block in descending order), leaving intermediate blocks as stale data. + VerifyReceipts(logIndexStorage, testData, excludedBlocks: reverseReorg, maxBlock: reverseReorg[0].BlockNumber - 1); + } + + [Combinatorial] + public async Task Set_ReorgAndSetLast_Get_Test( + [Values(1, 5, 20)] int reorgDepth, + [Values(100, int.MaxValue)] int compactionDistance + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + BlockReceipts[] reorgBlocks = testData.Batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); + foreach (BlockReceipts block in reorgBlocks) + { + await logIndexStorage.RemoveReorgedAsync(block); + await logIndexStorage.AddReceiptsAsync([block], false); + } + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task Set_ReorgLast_SetLast_Get_Test( + [Values(1, 5, 20)] int reorgDepth, + [Values(100, int.MaxValue)] int compactionDistance + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + BlockReceipts[] reorgBlocks = testData.Batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); + + foreach (BlockReceipts block in reorgBlocks) + await logIndexStorage.RemoveReorgedAsync(block); + + await logIndexStorage.AddReceiptsAsync(reorgBlocks, false); + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task Set_ReorgNonexistent_Get_Test( + [Values(1, 5)] int reorgDepth, + [Values(100, int.MaxValue)] int compactionDistance + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + var lastBlock = testData.Batches[^1][^1].BlockNumber; + BlockReceipts[] reorgBlocks = GenerateBlocks(new Random(4242), lastBlock - reorgDepth + 1, reorgDepth); + foreach (BlockReceipts block in reorgBlocks) + await logIndexStorage.RemoveReorgedAsync(block); + + // Need custom check because Reorg updates the last block even if it's "nonexistent" + Assert.That(logIndexStorage.MaxBlockNumber, Is.EqualTo(lastBlock - reorgDepth)); + + VerifyReceipts(logIndexStorage, testData, excludedBlocks: reorgBlocks, validateMinMax: false); + } + + [TestCase(1, 1)] + [TestCase(32, 64)] + [TestCase(64, 64)] + [TestCase(65, 64, Explicit = true)] + public async Task Set_Compact_ReorgLast_Get_Test(int reorgDepth, int maxReorgDepth) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(maxReorgDepth: maxReorgDepth); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + await CompactAsync(logIndexStorage); + + BlockReceipts[] reorgBlocks = testData.Batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); + foreach (BlockReceipts block in reorgBlocks) + await logIndexStorage.RemoveReorgedAsync(block); + + var lastBlock = testData.Batches[^1][^1].BlockNumber; + VerifyReceipts(logIndexStorage, testData, maxBlock: lastBlock - reorgDepth); + } + + [Combinatorial] + public async Task Set_PeriodicReorg_Get_Test( + [Values(10, 70)] int reorgFrequency, + [Values(1, 5)] int maxReorgDepth, + [Values] bool compactAfter + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(); + + var random = new Random(42); + var allReorgBlocks = new List(); + var allAddedBlocks = new List(); + + foreach (BlockReceipts[][] batches in testData.Batches.GroupBy(b => b[0].BlockNumber / reorgFrequency).Select(g => g.ToArray())) + { + await AddReceiptsAsync(logIndexStorage, batches); + + var reorgDepth = random.Next(1, maxReorgDepth); + BlockReceipts[] reorgBlocks = batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); + BlockReceipts[] addedBlocks = GenerateBlocks(random, reorgBlocks.First().BlockNumber, reorgBlocks.Length); + + allReorgBlocks.AddRange(reorgBlocks); + allAddedBlocks.AddRange(addedBlocks); + + foreach (BlockReceipts block in reorgBlocks) + await logIndexStorage.RemoveReorgedAsync(block); + + if (compactAfter) + await CompactAsync(logIndexStorage); + + await logIndexStorage.AddReceiptsAsync(addedBlocks, false); + } + + VerifyReceipts(logIndexStorage, testData, excludedBlocks: allReorgBlocks, addedBlocks: allAddedBlocks); + } + + [Ignore("Not supported, but is probably not needed.")] + [Combinatorial] + public async Task Set_ConsecutiveReorgsLast_Get_Test( + [Values(new[] { 2, 1 }, new[] { 1, 2 })] int[] reorgDepths, + [Values] bool compactBetween + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + BlockReceipts[] testBlocks = testData.Batches.SelectMany(b => b).ToArray(); + + foreach (var reorgDepth in reorgDepths) + { + foreach (BlockReceipts block in testBlocks.TakeLast(reorgDepth).ToArray()) + await logIndexStorage.RemoveReorgedAsync(block); + + if (compactBetween) + await CompactAsync(logIndexStorage); + } + + VerifyReceipts(logIndexStorage, testData, maxBlock: testBlocks[^1].BlockNumber - reorgDepths.Max()); + } + + [Combinatorial] + public async Task SetMultiInstance_Get_Test( + [Values(100, int.MaxValue)] int compactionDistance, + [Values] bool isBackwardsSync + ) + { + var half = testData.Batches.Length / 2; + + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + await AddReceiptsAsync(logIndexStorage, testData.Batches.Take(half)); + + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + await AddReceiptsAsync(logIndexStorage, testData.Batches.Skip(half)); + + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task RepeatedSet_Get_Test( + [Values(100, int.MaxValue)] int compactionDistance, + [Values] bool isBackwardsSync + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task RepeatedSetMultiInstance_Get_Test( + [Values(100, int.MaxValue)] int compactionDistance, + [Values] bool isBackwardsSync + ) + { + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task Set_NewInstance_Get_Test( + [Values(1, 8)] int ioParallelism, + [Values(100, int.MaxValue)] int compactionDistance, + [Values] bool isBackwardsSync + ) + { + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + [Repeat(RaceConditionTestRepeat)] + [SuppressMessage("ReSharper", "AccessToDisposedClosure")] + public async Task Set_ConcurrentGet_Test( + [Values(1, 200, int.MaxValue)] int compactionDistance, + [Values] bool isBackwardsSync + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + using var getCancellation = new CancellationTokenSource(); + CancellationToken token = getCancellation.Token; + + ConcurrentBag exceptions = []; + Thread[] getThreads = + [ + new(() => VerifyReceiptsPartialLoop(new Random(42), logIndexStorage, testData, exceptions, token)), + new(() => VerifyReceiptsPartialLoop(new Random(4242), logIndexStorage, testData, exceptions, token)), + new(() => VerifyReceiptsPartialLoop(new Random(424242), logIndexStorage, testData, exceptions, token)) + ]; + getThreads.ForEach(t => t.Start()); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + await getCancellation.CancelAsync(); + getThreads.ForEach(t => t.Join()); + + if (exceptions.FirstOrDefault() is { } exception) + ExceptionDispatchInfo.Capture(exception).Throw(); + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task SetFailure_Get_Test( + [Values(1, 20, 51, 100)] int failOnCallN, + [Values] bool isBackwardsSync + ) + { + BlockReceipts[][] batches = isBackwardsSync ? Reverse(testData.Batches) : testData.Batches; + var midBlock = testData.Batches[^1][^1].BlockNumber / 2; + + await using ILogIndexStorage failLogIndexStorage = CreateLogIndexStorage(failOnBlock: midBlock, failOnCallN: failOnCallN); + + Exception exception = Assert.ThrowsAsync(() => AddReceiptsAsync(failLogIndexStorage, batches, isBackwardsSync)); + Assert.That(exception, Has.Message.EqualTo(SaveFailingLogIndexStorage.FailMessage)); + + VerifyReceipts( + failLogIndexStorage, testData, + minBlock: failLogIndexStorage.MinBlockNumber ?? 0, maxBlock: failLogIndexStorage.MaxBlockNumber ?? 0 + ); + } + + [Combinatorial] + public async Task SetFailure_Set_Get_Test( + [Values(1, 20, 51, 100)] int failOnCallN, + [Values] bool isBackwardsSync + ) + { + BlockReceipts[][] batches = isBackwardsSync ? Reverse(testData.Batches) : testData.Batches; + var midBlock = testData.Batches[^1][^1].BlockNumber / 2; + + await using (ILogIndexStorage failLogIndexStorage = CreateLogIndexStorage(failOnBlock: midBlock, failOnCallN: failOnCallN)) + { + Exception exception = Assert.ThrowsAsync(() => AddReceiptsAsync(failLogIndexStorage, batches, isBackwardsSync)); + Assert.That(exception, Has.Message.EqualTo(SaveFailingLogIndexStorage.FailMessage)); + } + + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(); + await AddReceiptsAsync(logIndexStorage, batches, isBackwardsSync); + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + [SuppressMessage("ReSharper", "AccessToDisposedClosure")] + public async Task SetMergeFailure_AnyWrite_Test( + [Values(1, 20, 51, 100)] int failOnCallN + ) + { + var midBlock = testData.Batches[^1][^1].BlockNumber / 2; + await using var storage = (LogIndexStorage)CreateLogIndexStorage(failOnBlock: midBlock, failOnCallN: failOnCallN, failOnMerge: true); + + try + { + await AddReceiptsAsync(storage, testData.Batches); + + // force compaction if the error hasn't propagated already + await storage.CompactAsync(); + } + catch (LogIndexStateException) + { + // Expected + } + + Assert.That(storage.HasBackgroundError, Is.True); + + IEnumerable sinceLastBatch = testData.Batches.SelectMany(b => b) + .SkipWhile(b => b.BlockNumber < storage.MaxBlockNumber); + + await Assert.ThatAsync( + () => storage.AddReceiptsAsync(sinceLastBatch.Skip(1).Take(10).ToArray(), false), + Throws.InstanceOf().And.Message.Contain("merge") + ); + + await Assert.ThatAsync( + () => storage.RemoveReorgedAsync(sinceLastBatch.First()), + Throws.InstanceOf().And.Message.Contain("merge") + ); + + await Assert.ThatAsync( + () => storage.CompactAsync(), + Throws.InstanceOf().And.Message.Contain("merge") + ); + + Assert.DoesNotThrowAsync(() => storage.StopAsync()); + Assert.DoesNotThrowAsync(() => storage.StopAsync()); + } + + [Combinatorial] + public async Task Set_AlgoChange_Test() + { + if (CompressionAlgorithm.Supported.Count < 2) + Assert.Ignore("Less than 2 supported compression algorithms"); + + await using (var logIndexStorage = CreateLogIndexStorage()) + await AddReceiptsAsync(logIndexStorage, [testData.Batches[0]]); + + var oldAlgo = testData.Compression ?? CompressionAlgorithm.Best.Key; + var newAlgo = CompressionAlgorithm.Supported.First(c => c.Key != oldAlgo).Key; + + NotSupportedException exception = Assert.Throws(() => CreateLogIndexStorage(compressionAlgo: newAlgo)); + Assert.That(exception, Has.Message.Contain(oldAlgo).And.Message.Contain(newAlgo)); + } + + private static BlockReceipts[] GenerateBlocks(Random random, int from, int count) => + new TestData(random, 1, count, startNum: from).Batches[0]; + + private static async Task AddReceiptsAsync(ILogIndexStorage logIndexStorage, IEnumerable batches, bool isBackwardsSync = false) + { + long timestamp = Stopwatch.GetTimestamp(); + + LogIndexUpdateStats totalStats = new(logIndexStorage); + (int count, int length) = (0, 0); + foreach (BlockReceipts[] batch in batches) + { + count++; + length = batch.Length; + await logIndexStorage.AddReceiptsAsync(batch, isBackwardsSync, totalStats); + } + + if (LogStatistics) + { + await TestContext.Out.WriteLineAsync( + $""" + x{count} {nameof(LogIndexStorage.AddReceiptsAsync)}([{length}], {isBackwardsSync}) in {Stopwatch.GetElapsedTime(timestamp)}: + {totalStats:d} + {'\t'}DB size: {logIndexStorage.GetDbSize()} + + """ + ); + } + } + + private static void VerifyReceipts(ILogIndexStorage logIndexStorage, TestData testData, + Dictionary>? excludedAddresses = null, + Dictionary>>? excludedTopics = null, + Dictionary>? addedAddresses = null, + Dictionary>>? addedTopics = null, + int? minBlock = null, int? maxBlock = null, + bool validateMinMax = true + ) + { + minBlock ??= testData.Batches[0][0].BlockNumber; + maxBlock ??= testData.Batches[^1][^1].BlockNumber; + + if (validateMinMax) + { + using (Assert.EnterMultipleScope()) + { + Assert.That(logIndexStorage.MinBlockNumber, Is.EqualTo(minBlock)); + Assert.That(logIndexStorage.MaxBlockNumber, Is.EqualTo(maxBlock)); + } + } + + foreach ((Address address, HashSet blocks) in testData.AddressMap) + { + IEnumerable expectedBlocks = blocks; + + if (excludedAddresses != null && excludedAddresses.TryGetValue(address, out HashSet addressExcludedBlocks)) + expectedBlocks = expectedBlocks.Except(addressExcludedBlocks); + + if (addedAddresses != null && addedAddresses.TryGetValue(address, out HashSet addressAddedBlocks)) + expectedBlocks = expectedBlocks.Concat(addressAddedBlocks); + + expectedBlocks = expectedBlocks.Order(); + + if (minBlock > testData.Batches[0][0].BlockNumber) + expectedBlocks = expectedBlocks.SkipWhile(b => b < minBlock); + + if (maxBlock < testData.Batches[^1][^1].BlockNumber) + expectedBlocks = expectedBlocks.TakeWhile(b => b <= maxBlock); + + expectedBlocks = expectedBlocks.ToArray(); + + foreach (var (from, to) in testData.Ranges) + { + Assert.That( + logIndexStorage.GetBlockNumbersFor(address, from, to), + Is.EqualTo(expectedBlocks.SkipWhile(i => i < from).TakeWhile(i => i <= to)), + $"Address: {address}, from {from} to {to}" + ); + } + } + + foreach ((int idx, Dictionary> byTopic) in testData.TopicMap) + { + foreach ((Hash256 topic, HashSet blocks) in byTopic) + { + IEnumerable expectedBlocks = blocks; + + if (excludedTopics != null && excludedTopics[idx].TryGetValue(topic, out HashSet topicExcludedBlocks)) + expectedBlocks = expectedBlocks.Except(topicExcludedBlocks); + + if (addedTopics != null && addedTopics[idx].TryGetValue(topic, out HashSet topicAddedBlocks)) + expectedBlocks = expectedBlocks.Concat(topicAddedBlocks); + + expectedBlocks = expectedBlocks.Order(); + + if (minBlock > testData.Batches[0][0].BlockNumber) + expectedBlocks = expectedBlocks.SkipWhile(b => b < minBlock); + + if (maxBlock < testData.Batches[^1][^1].BlockNumber) + expectedBlocks = expectedBlocks.TakeWhile(b => b <= maxBlock); + + expectedBlocks = expectedBlocks.ToArray(); + + foreach (var (from, to) in testData.Ranges) + { + Assert.That( + logIndexStorage.GetBlockNumbersFor(idx, topic, from, to), + Is.EqualTo(expectedBlocks.SkipWhile(i => i < from).TakeWhile(i => i <= to)), + $"Topic: [{idx}] {topic}, {from} - {to}" + ); + } + } + } + } + + private static void VerifyReceipts(ILogIndexStorage logIndexStorage, TestData testData, + IEnumerable? excludedBlocks, IEnumerable? addedBlocks = null, + int? minBlock = null, int? maxBlock = null, + bool validateMinMax = true) + { + var excludeMaps = excludedBlocks == null ? default : TestData.GenerateMaps(excludedBlocks); + var addMaps = addedBlocks == null ? default : TestData.GenerateMaps(addedBlocks); + + VerifyReceipts( + logIndexStorage, testData, + excludedAddresses: excludeMaps.address, excludedTopics: excludeMaps.topic, + addedAddresses: addMaps.address, addedTopics: addMaps.topic, + minBlock: minBlock, maxBlock: maxBlock, + validateMinMax: validateMinMax + ); + } + + private static void VerifyReceiptsPartialLoop(Random random, ILogIndexStorage logIndexStorage, TestData testData, + ConcurrentBag exceptions, CancellationToken cancellationToken) + { + try + { + (List
addresses, List<(int, Hash256)> topics) = (testData.Addresses, testData.Topics); + + while (!cancellationToken.IsCancellationRequested) + { + if (addresses.Count != 0) + { + Address address = random.NextFrom(addresses); + HashSet expectedBlocks = testData.AddressMap[address]; + + if (logIndexStorage.MinBlockNumber is not { } min || logIndexStorage.MaxBlockNumber is not { } max) + continue; + + Assert.That( + logIndexStorage.GetBlockNumbersFor(address, min, max), + Is.EqualTo(expectedBlocks.SkipWhile(i => i < min).TakeWhile(i => i <= max)), + $"Address: {address}, available: {min} - {max}" + ); + } + + if (topics.Count != 0) + { + (int idx, Hash256 topic) = random.NextFrom(topics); + HashSet expectedBlocks = testData.TopicMap[idx][topic]; + + if (logIndexStorage.MinBlockNumber is not { } min || logIndexStorage.MaxBlockNumber is not { } max) + continue; + + Assert.That( + logIndexStorage.GetBlockNumbersFor(idx, topic, min, max), + Is.EqualTo(expectedBlocks.SkipWhile(i => i < min).TakeWhile(i => i <= max)), + $"Topic: [{idx}] {topic}, available: {min} - {max}" + ); + } + } + } + catch (Exception ex) + { + exceptions.Add(ex); + } + } + + private static BlockReceipts[][] Reverse(BlockReceipts[][] batches) + { + int length = batches.Length; + BlockReceipts[][] result = new BlockReceipts[length][]; + + int index = 0; + foreach (BlockReceipts[] batch in batches.Reverse()) + result[index++] = batch.Reverse().ToArray(); + + return result; + } + + private static BlockReceipts[][] Intersect(BlockReceipts[][] batches) + { + BlockReceipts[][] result = new BlockReceipts[batches.Length + 1][]; + + for (int i = 0; i < result.Length; i++) + { + if (i == 0) + result[i] = batches[i]; + else if (i == batches.Length) + result[i] = batches[^1].Skip(batches[^2].Length / 2).ToArray(); + else + result[i] = batches[i - 1].Skip(batches[i - 1].Length / 2).Concat(batches[i].Take(batches[i].Length / 2)).ToArray(); + } + + return result; + } + + private static async Task CompactAsync(ILogIndexStorage logIndexStorage) + { + long timestamp = Stopwatch.GetTimestamp(); + await ((LogIndexStorage)logIndexStorage).CompactAsync(); + + if (LogStatistics) + { + await TestContext.Out.WriteLineAsync( + $""" + {nameof(LogIndexStorage.CompactAsync)}() in {Stopwatch.GetElapsedTime(timestamp)}: + {'\t'}DB size: {logIndexStorage.GetDbSize()} + + """ + ); + } + } + + public class TestData + { + private readonly int _batchCount; + private readonly int _blocksPerBatch; + private readonly int _startNum; + + // Lazy avoids generating all the data just to display test cases in the runner + private readonly Lazy _batches; + public BlockReceipts[][] Batches => _batches.Value; + + private List
? _addresses; + private List<(int, Hash256)>? _topics; + + private readonly Lazy> _ranges; + public IEnumerable<(int from, int to)> Ranges => _ranges.Value; + + public Dictionary> AddressMap { get; private set; } = new(); + public Dictionary>> TopicMap { get; private set; } = new(); + + public List
Addresses + { + get + { + _ = Batches; + return _addresses!; + } + } + + public List<(int, Hash256)> Topics + { + get + { + _ = Batches; + return _topics!; + } + } + + public bool ExtendedGetRanges { get; init; } + public string? Compression { get; init; } + + public TestData(Random random, int batchCount, int blocksPerBatch, int startNum = 0) + { + _batchCount = batchCount; + _blocksPerBatch = blocksPerBatch; + _startNum = startNum; + + _batches = new(() => GenerateBatches(random, batchCount, blocksPerBatch, startNum)); + _ranges = new(() => ExtendedGetRanges ? GenerateExtendedRanges() : GenerateSimpleRanges()); + } + + public TestData(int batchCount, int blocksPerBatch, int startNum = 0) : this(new(42), batchCount, blocksPerBatch, startNum) { } + + private BlockReceipts[][] GenerateBatches(Random random, int batchCount, int blocksPerBatch, int startNum = 0) + { + var batches = new BlockReceipts[batchCount][]; + var blocksCount = batchCount * blocksPerBatch; + + Address[] customAddresses = + [ + Address.Zero, Address.MaxValue, + new(new byte[] { 1 }.PadLeft(Address.Size)), new(new byte[] { 1, 1 }.PadLeft(Address.Size)), + new(new byte[] { 1 }.PadRight(Address.Size)), new(new byte[] { 1, 1 }.PadRight(Address.Size)), + new(new byte[] { 0 }.PadLeft(Address.Size, 0xFF)), new(new byte[] { 0 }.PadRight(Address.Size, 0xFF)), + ]; + + Hash256[] customTopics = + [ + Hash256.Zero, new(Array.Empty().PadRight(Hash256.Size, 0xFF)), + new(new byte[] { 0 }.PadLeft(Hash256.Size)), new(new byte[] { 1 }.PadLeft(Hash256.Size)), + new(new byte[] { 0 }.PadRight(Hash256.Size)), new(new byte[] { 1 }.PadRight(Hash256.Size)), + new(new byte[] { 0 }.PadLeft(Hash256.Size, 0xFF)), new(new byte[] { 0 }.PadRight(Hash256.Size, 0xFF)), + ]; + + Address[] addresses = Enumerable.Repeat(0, Math.Max(10, blocksCount / 5) - customAddresses.Length) + //var addresses = Enumerable.Repeat(0, 0) + .Select(_ => new Address(random.NextBytes(Address.Size))) + .Concat(customAddresses) + .ToArray(); + Hash256[] topics = Enumerable.Repeat(0, addresses.Length * 7 - customTopics.Length) + //var topics = Enumerable.Repeat(0, 0) + .Select(_ => new Hash256(random.NextBytes(Hash256.Size))) + .Concat(customTopics) + .ToArray(); + + // Generate batches + int blockNum = startNum; + for (int i = 0; i < batches.Length; i++) + { + BlockReceipts[] batch = batches[i] = new BlockReceipts[blocksPerBatch]; + + for (int j = 0; j < batch.Length; j++) + batch[j] = new(blockNum++, GenerateReceipts(random, addresses, topics)); + } + + var maps = GenerateMaps(batches.SelectMany(b => b)); + + (AddressMap, TopicMap) = (maps.address, maps.topic); + + _addresses = maps.address.Keys.ToList(); + _topics = maps.topic.SelectMany(byIdx => byIdx.Value.Select(byTpc => (byIdx.Key, byTpc.Key))).ToList(); + + return batches; + } + + public static (Dictionary> address, Dictionary>> topic) GenerateMaps( + IEnumerable blocks) + { + Dictionary> address = new(); + Dictionary>> topic = new(); + + foreach (BlockReceipts block in blocks) + { + foreach (TxReceipt txReceipt in block.Receipts) + { + foreach (LogEntry log in txReceipt.Logs!) + { + HashSet addressMap = address.GetOrAdd(log.Address, static _ => []); + addressMap.Add(block.BlockNumber); + + for (int i = 0; i < log.Topics.Length; i++) + { + Dictionary> topicI = topic.GetOrAdd(i, static _ => []); + HashSet topicMap = topicI.GetOrAdd(log.Topics[i], static _ => []); + topicMap.Add(block.BlockNumber); + } + } + } + } + + return (address, topic); + } + + private static TxReceipt[] GenerateReceipts(Random random, Address[] addresses, Hash256[] topics) + { + (int min, int max) logsPerBlock = (0, 200); + (int min, int max) logsPerTx = (0, 10); + + LogEntry[] logs = Enumerable + .Repeat(0, random.Next(logsPerBlock.min, logsPerBlock.max + 1)) + .Select(_ => Build.A.LogEntry + .WithAddress(random.NextFrom(addresses)) + .WithTopics(topics.Length == 0 + ? [] + : Enumerable.Repeat(0, random.Next(4)).Select(_ => random.NextFrom(topics)).ToArray() + ).TestObject + ).ToArray(); + + List receipts = new(); + for (var i = 0; i < logs.Length;) + { + int count = random.Next(logsPerTx.min, Math.Min(logsPerTx.max, logs.Length - i) + 1); + Range range = i..(i + count); + + receipts.Add(new() { Logs = logs[range] }); + i = range.End.Value; + } + + return receipts.ToArray(); + } + + private static HashSet<(int from, int to)> GenerateSimpleRanges(int min, int max) + { + int quarter = (max - min) / 4; + return [(0, int.MaxValue), (min, max), (min + quarter, max - quarter)]; + } + + private static HashSet<(int from, int to)> GenerateExtendedRanges(int min, int max) + { + HashSet<(int, int)> ranges = new(); + + int[] edges = [min - 1, min, min + 1, max - 1, max + 1]; + ranges.AddRange(edges.SelectMany(_ => edges, static (x, y) => (x, y))); + + const int step = 100; + for (var i = min; i <= max; i += step) + { + var middles = new[] { i - step, i - 1, i, i + 1, i + step }; + ranges.AddRange(middles.SelectMany(_ => middles, static (x, y) => (x, y))); + } + + return ranges; + } + + private HashSet<(int from, int to)> GenerateSimpleRanges() => GenerateSimpleRanges( + _startNum, _startNum + _batchCount * _blocksPerBatch - 1 + ); + + private HashSet<(int from, int to)> GenerateExtendedRanges() => GenerateExtendedRanges( + _startNum, _startNum + _batchCount * _blocksPerBatch - 1 + ); + + public override string ToString() => + $"{_batchCount} * {_blocksPerBatch} blocks (ex-ranges: {ExtendedGetRanges}, compression: {Compression})"; + } + + private class SaveFailingLogIndexStorage(IDbFactory dbFactory, ILogManager logManager, ILogIndexConfig config) + : LogIndexStorage(dbFactory, logManager, config) + { + public const string FailMessage = "Test exception."; + + public int FailOnBlock { get; init; } + public int FailOnCallN { get; init; } + public bool FailOnMerge { get; init; } + + private int _count; + private bool _corrupted; + + protected override void MergeBlockNumbers(IWriteBatch dbBatch, ReadOnlySpan key, List numbers, bool isBackwardSync, LogIndexUpdateStats? stats) + { + var isFailBlock = + FailOnBlock >= Math.Min(numbers[0], numbers[^1]) && + FailOnBlock <= Math.Max(numbers[0], numbers[^1]); + + if (isFailBlock && Interlocked.Increment(ref _count) >= FailOnCallN && !Interlocked.Exchange(ref _corrupted, true)) + { + if (FailOnMerge) + { + // Force "invalid order" in MergeOperator + int invalidBlockNum = isBackwardSync ? int.MaxValue : 0; + base.MergeBlockNumbers(dbBatch, key, [invalidBlockNum], isBackwardSync, stats); + } + else + { + throw new(FailMessage); + } + } + + base.MergeBlockNumbers(dbBatch, key, numbers, isBackwardSync, stats); + } + } + } +} diff --git a/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageTestExtensions.cs b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageTestExtensions.cs new file mode 100644 index 000000000000..dccf8578a996 --- /dev/null +++ b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageTestExtensions.cs @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2026 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; +using Nethermind.Db.LogIndex; + +namespace Nethermind.Db.Test.LogIndex; + +public static class LogIndexStorageTestExtensions +{ + extension(ILogIndexStorage storage) + { + public List GetBlockNumbersFor(Address address, int from, int to) + { + var result = new List(); + using IEnumerator enumerator = storage.GetEnumerator(address, from, to); + + while (enumerator.MoveNext()) + result.Add(enumerator.Current); + + return result; + } + + public List GetBlockNumbersFor(int index, Hash256 topic, int from, int to) + { + var result = new List(); + using IEnumerator enumerator = storage.GetEnumerator(index, topic, from, to); + + while (enumerator.MoveNext()) + result.Add(enumerator.Current); + + return result; + } + + public Task AddReceiptsAsync(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats = null) + { + LogIndexAggregate aggregate = storage.Aggregate(batch, isBackwardSync, stats); + return storage.AddReceiptsAsync(aggregate, stats); + } + } +} diff --git a/src/Nethermind/Nethermind.Db.Test/LogIndex/MergeOperatorTests.cs b/src/Nethermind/Nethermind.Db.Test/LogIndex/MergeOperatorTests.cs new file mode 100644 index 000000000000..a7d9386face6 --- /dev/null +++ b/src/Nethermind/Nethermind.Db.Test/LogIndex/MergeOperatorTests.cs @@ -0,0 +1,247 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Linq; +using System.Runtime.InteropServices; +using ConcurrentCollections; +using MathNet.Numerics.Random; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Extensions; +using Nethermind.Db.LogIndex; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Db.Test.LogIndex; + +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class MergeOperatorTests +{ + [TestCase( + null, + new[] { "1, 2", "3", "4" }, + "1, 2, 3, 4" + )] + [TestCase( + "1", + new[] { "2, 3", "4" }, + "1, 2, 3, 4" + )] + [TestCase( + "1, 2", + new[] { "3, 4", "Reorg:3", "5, 6" }, + "1, 2, 5, 6" + )] + [TestCase( + "1, 2", + new[] { "3, 4", "Reorg:2", "5, 6" }, + "1, 5, 6" + )] + [TestCase( + "1, 2", + new[] { "3, 4", "Reorg:3", "Reorg:4", "5, 6" }, + "1, 2, 5, 6" + )] + [TestCase( + "1, 2", + new[] { "3, 4", "Reorg:4", "Reorg:3", "5, 6" }, + "1, 2, 5, 6", + Ignore = "Subsequent reverse reorgs are not supported." + )] + [TestCase( + "1, 2", + new[] { "3, 4", "Reorg:3", "5, 6", "Reorg:5", "6, 7" }, + "1, 2, 6, 7" + )] + [TestCase( + "1, 2, 3", + new[] { "4, 5", "Truncate:4", "6, 7" }, + "5, 6, 7" + )] + [TestCase( + "1, 2, 3", + new[] { "4, 5", "Truncate:3", "6, 7" }, + "4, 5, 6, 7" + )] + [TestCase( + "1, 2, 3", + new[] { "4, 5", "Truncate:3", "Truncate:4", "6, 7" }, + "5, 6, 7" + )] + [TestCase( + "1, 2, 3", + new[] { "4, 5", "Truncate:4", "Truncate:3", "6, 7" }, + "5, 6, 7" + )] + [TestCase( + "1, 2, 3", + new[] { "4, 5", "Truncate:3", "6, 7", "Truncate:6" }, + "7" + )] + public void FullMergeForward(string? existing, string[] operands, string expected) + { + LogIndexStorage.MergeOperator op = CreateOperator(); + CreateEnumerator(Serialize(existing), operands.Select(Serialize).ToArray(), out RocksDbMergeEnumerator enumerator); + + byte[] key = GenerateKey(Address.Size, isBackward: false); + using ArrayPoolList merged = op.FullMerge(key, enumerator); + Assert.That( + Deserialize(merged?.ToArray()), + Is.EqualTo(expected.Split(',').Select(int.Parse).ToArray()) + ); + } + + [TestCase( + null, + new[] { "4", "3", "2, 1" }, + "4, 3, 2, 1" + )] + [TestCase( + "4", + new[] { "3, 2", "1" }, + "4, 3, 2, 1" + )] + [TestCase( + "7, 6, 5", + new[] { "4, 3", "Truncate:4", "2, 1" }, + "3, 2, 1" + )] + [TestCase( + "7, 6, 5", + new[] { "4, 3", "Truncate:5", "2, 1" }, + "4, 3, 2, 1" + )] + [TestCase( + "7, 6, 5", + new[] { "4, 3", "Truncate:5", "Truncate:4", "2, 1" }, + "3, 2, 1" + )] + [TestCase( + "7, 6, 5", + new[] { "4, 3", "Truncate:4", "Truncate:5", "2, 1" }, + "3, 2, 1" + )] + [TestCase( + "7, 6, 5", + new[] { "4, 3", "Truncate:5", "2, 1", "Truncate:2" }, + "1" + )] + public void FullMergeBackward(string? existing, string[] operands, string expected) + { + LogIndexStorage.MergeOperator op = CreateOperator(); + CreateEnumerator(Serialize(existing), operands.Select(Serialize).ToArray(), out RocksDbMergeEnumerator enumerator); + + byte[] key = GenerateKey(Address.Size, isBackward: true); + ArrayPoolList merged = op.FullMerge(key, enumerator); + Assert.That( + Deserialize(merged?.ToArray()), + Is.EqualTo(expected.Split(',').Select(int.Parse).ToArray()) + ); + } + + [TestCase( + new[] { "1", "2", "3", "4" }, + "1, 2, 3, 4" + )] + [TestCase( + new[] { "1", "2, 3", "4" }, + "1, 2, 3, 4" + )] + public void PartialMergeForward(string[] operands, string expected) + { + LogIndexStorage.MergeOperator op = CreateOperator(); + CreateEnumerator(null, operands.Select(Serialize).ToArray(), out RocksDbMergeEnumerator enumerator); + + byte[] key = GenerateKey(Address.Size, isBackward: false); + using ArrayPoolList merged = op.PartialMerge(key, enumerator); + Assert.That( + Deserialize(merged?.ToArray()), + Is.EqualTo(expected.Split(',').Select(int.Parse).ToArray()) + ); + } + + [TestCase( + new[] { "4", "3", "2", "1" }, + "4, 3, 2, 1" + )] + [TestCase( + new[] { "4", "3, 2", "1" }, + "4, 3, 2, 1" + )] + public void PartialMergeBackward(string[] operands, string expected) + { + LogIndexStorage.MergeOperator op = CreateOperator(); + CreateEnumerator(null, operands.Select(Serialize).ToArray(), out RocksDbMergeEnumerator enumerator); + + byte[] key = GenerateKey(Address.Size, isBackward: true); + using ArrayPoolList merged = op.PartialMerge(key, enumerator); + Assert.That( + Deserialize(merged?.ToArray()), + Is.EqualTo(expected.Split(',').Select(int.Parse).ToArray()) + ); + } + + [OneTimeTearDown] + public static void OneTimeTearDown() => Handles.ForEach(h => h.Free()); + + private static readonly LogIndexStorage.ICompressor Compressor = new LogIndexStorage.NoOpCompressor(); + + private static readonly ConcurrentHashSet Handles = []; + + private static LogIndexStorage.MergeOperator CreateOperator() + { + ILogIndexStorage storage = Substitute.For(); + return new(storage, Compressor, 0); + } + + private static void CreateEnumerator(byte[]? existingValue, byte[][] operands, out RocksDbMergeEnumerator enumerator) + { + var operandsPtrs = new IntPtr[operands.Length]; + var operandsLengths = operands.Select(x => (long)x.Length).ToArray(); + + for (int i = 0; i < operands.Length; i++) + { + var handle = GCHandle.Alloc(operands[i], GCHandleType.Pinned); + Handles.Add(handle); + + operandsPtrs[i] = handle.AddrOfPinnedObject(); + operandsLengths[i] = operands[i].Length; + } + + enumerator = existingValue is null + ? new(Span.Empty, false, operandsPtrs, operandsLengths) + : new(existingValue, true, operandsPtrs, operandsLengths); + } + + private static byte[] GenerateKey(int prefixSize, bool isBackward) => Random.Shared + .NextBytes(prefixSize + LogIndexStorage.BlockNumberSize) + .Concat(isBackward ? LogIndexStorage.Postfix.BackwardMerge : LogIndexStorage.Postfix.ForwardMerge) + .ToArray(); + + private static byte[]? Serialize(string? input) => + input is null ? null : Bytes.Concat(input.Split(',').Select(s => s.Trim()).Select(s => s switch + { + _ when int.TryParse(s, out int blockNum) => blockNum.ToLittleEndianByteArray(), + _ when TryParseMergeOp(s, out Span op) => op.ToArray(), + _ => throw new FormatException($"Invalid operand: \"{input}\".") + }).ToArray()); + + private static bool TryParseMergeOp(string input, out Span bytes) + { + bytes = default; + + var parts = input.Split(":"); + if (parts.Length != 2) return false; + + if (!Enum.TryParse(parts[0], out LogIndexStorage.MergeOp op)) return false; + if (!int.TryParse(parts[1], out int blockNum)) return false; + + var buffer = new byte[LogIndexStorage.MergeOps.Size]; + bytes = LogIndexStorage.MergeOps.Create(op, blockNum, buffer); + return true; + } + + private static int[]? Deserialize(byte[]? input) => input is null ? null : MemoryMarshal.Cast(input).ToArray(); +} diff --git a/src/Nethermind/Nethermind.Db.Test/RocksDbConfigFactoryTests.cs b/src/Nethermind/Nethermind.Db.Test/RocksDbConfigFactoryTests.cs index 61222f9c4046..518ec46fedc3 100644 --- a/src/Nethermind/Nethermind.Db.Test/RocksDbConfigFactoryTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/RocksDbConfigFactoryTests.cs @@ -1,14 +1,11 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using FluentAssertions; -using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Core.Test; using Nethermind.Db.Rocks.Config; using Nethermind.Logging; -using NSubstitute; using NUnit.Framework; namespace Nethermind.Db.Test; @@ -23,7 +20,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); } @@ -58,4 +54,66 @@ public void WillOverrideStateConfigWhenDirtyCachesTooHigh() IRocksDbConfig config = factory.GetForDatabase("State0", null); config.WriteBufferSize.Should().Be((ulong)500.MB()); } + + [Test] + public void WillAutomaticallySetMaxOpenFilesWhenNotConfigured() + { + var dbConfig = new DbConfig(); + var factory = new RocksDbConfigFactory(dbConfig, new PruningConfig(), new TestHardwareInfo(0, 10000), LimboLogs.Instance); + IRocksDbConfig config = factory.GetForDatabase("State0", null); + // With system limit of 10000, should set per-db limit to 10000 * 0.8 = 8000 + config.MaxOpenFiles.Should().Be(8000); + } + + [Test] + public void WillNotOverrideUserConfiguredMaxOpenFiles() + { + var dbConfig = new DbConfig(); + dbConfig.MaxOpenFiles = 3000; + var factory = new RocksDbConfigFactory(dbConfig, new PruningConfig(), new TestHardwareInfo(0, 10000), LimboLogs.Instance); + IRocksDbConfig config = factory.GetForDatabase("State0", null); + // Should keep user-configured value + config.MaxOpenFiles.Should().Be(3000); + } + + [Test] + public void WillNotSetMaxOpenFilesWhenSystemLimitUnknown() + { + var dbConfig = new DbConfig(); + var factory = new RocksDbConfigFactory(dbConfig, new PruningConfig(), new TestHardwareInfo(0, null), LimboLogs.Instance); + IRocksDbConfig config = factory.GetForDatabase("State0", null); + // Should be null when system limit is unknown + config.MaxOpenFiles.Should().BeNull(); + } + + [Test] + public void WillEnforceMinimumMaxOpenFiles() + { + var dbConfig = new DbConfig(); + // Very low system limit (e.g., 1000) should still give minimum of 256 + var factory = new RocksDbConfigFactory(dbConfig, new PruningConfig(), new TestHardwareInfo(0, 1000), LimboLogs.Instance); + IRocksDbConfig config = factory.GetForDatabase("State0", null); + // 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/Rpc/RpcDbFactoryTests.cs b/src/Nethermind/Nethermind.Db.Test/Rpc/RpcDbFactoryTests.cs index 09b1d477fbbd..3a45977f531a 100644 --- a/src/Nethermind/Nethermind.Db.Test/Rpc/RpcDbFactoryTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/Rpc/RpcDbFactoryTests.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.IO.Abstractions; using Autofac; using FluentAssertions; using Nethermind.Core; diff --git a/src/Nethermind/Nethermind.Db.Test/SimpleFilePublicKeyDbTests.cs b/src/Nethermind/Nethermind.Db.Test/SimpleFilePublicKeyDbTests.cs index a99ef9c7c44f..2f8ba50333c9 100644 --- a/src/Nethermind/Nethermind.Db.Test/SimpleFilePublicKeyDbTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/SimpleFilePublicKeyDbTests.cs @@ -51,6 +51,69 @@ public void Save_and_load() } } + [Test] + public void Set_updates_existing_key_with_different_value() + { + using TempPath tempPath = TempPath.GetTempFile(SimpleFilePublicKeyDb.DbFileName); + tempPath.Dispose(); + + SimpleFilePublicKeyDb filePublicKeyDb = new("Test", Path.GetTempPath(), LimboLogs.Instance); + + byte[] key = [1, 2, 3]; + byte[] originalValue = [10, 20, 30]; + byte[] updatedValue = [40, 50, 60]; + + filePublicKeyDb[key] = originalValue; + filePublicKeyDb[key] = updatedValue; + + Assert.That(filePublicKeyDb[key], Is.EqualTo(updatedValue)); + } + + [Test] + public void Set_with_identical_value_does_not_trigger_persistence() + { + using TempPath tempPath = TempPath.GetTempFile(SimpleFilePublicKeyDb.DbFileName); + tempPath.Dispose(); + + byte[] key = [1, 2, 3]; + byte[] originalValue = [10, 20, 30]; + + SimpleFilePublicKeyDb filePublicKeyDb = new("Test", Path.GetTempPath(), LimboLogs.Instance); + filePublicKeyDb[key] = originalValue; + using (filePublicKeyDb.StartWriteBatch()) { } + + // Delete the file to detect if a second flush writes anything + File.Delete(Path.Combine(Path.GetTempPath(), SimpleFilePublicKeyDb.DbFileName)); + + // Set the same value again — should not mark pending changes + filePublicKeyDb[key] = [10, 20, 30]; + using (filePublicKeyDb.StartWriteBatch()) { } + + // File should not be recreated since there were no pending changes + Assert.That(File.Exists(Path.Combine(Path.GetTempPath(), SimpleFilePublicKeyDb.DbFileName)), Is.False); + } + + [Test] + public void Set_persists_updated_value_after_reload() + { + using TempPath tempPath = TempPath.GetTempFile(SimpleFilePublicKeyDb.DbFileName); + tempPath.Dispose(); + + byte[] key = [1, 2, 3]; + byte[] originalValue = [10, 20, 30]; + byte[] updatedValue = [40, 50, 60]; + + SimpleFilePublicKeyDb filePublicKeyDb = new("Test", Path.GetTempPath(), LimboLogs.Instance); + filePublicKeyDb[key] = originalValue; + using (filePublicKeyDb.StartWriteBatch()) { } + + filePublicKeyDb[key] = updatedValue; + using (filePublicKeyDb.StartWriteBatch()) { } + + SimpleFilePublicKeyDb reloaded = new("Test", Path.GetTempPath(), LimboLogs.Instance); + Assert.That(reloaded[key], Is.EqualTo(updatedValue)); + } + [Test] public void Clear() { diff --git a/src/Nethermind/Nethermind.Db.Test/StandardDbInitializerTests.cs b/src/Nethermind/Nethermind.Db.Test/StandardDbInitializerTests.cs index e61cf1a1078f..9cdf0b1829cc 100644 --- a/src/Nethermind/Nethermind.Db.Test/StandardDbInitializerTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/StandardDbInitializerTests.cs @@ -18,7 +18,6 @@ using Nethermind.Db.Rocks.Config; using Nethermind.Init.Modules; using Nethermind.Logging; -using NSubstitute; using NUnit.Framework; namespace Nethermind.Db.Test diff --git a/src/Nethermind/Nethermind.Db/Blooms/BloomStorage.cs b/src/Nethermind/Nethermind.Db/Blooms/BloomStorage.cs index 50fffb6da711..f21903dabf70 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 616298983d6d..301da2f7b1d4 100644 --- a/src/Nethermind/Nethermind.Db/CompressingDb.cs +++ b/src/Nethermind/Nethermind.Db/CompressingDb.cs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using Nethermind.Core; +using Nethermind.Core.Buffers; using Nethermind.Core.Extensions; namespace Nethermind.Db @@ -18,68 +20,55 @@ public static class KeyValueStoreCompressingExtensions /// A wrapped db. public static IDb WithEOACompressed(this IDb @this) => new EOACompressingDb(@this); - private class EOACompressingDb : IDb, ITunableDb + // TODO: consider wrapping IDbWithSpan to make the read with a span, with no alloc for reading? + private class EOACompressingDb(IDb wrapped) : IDb, ITunableDb { - private readonly IDb _wrapped; - - public EOACompressingDb(IDb wrapped) - { - // TODO: consider wrapping IDbWithSpan to make the read with a span, with no alloc for reading? - _wrapped = wrapped; - } - public byte[]? this[ReadOnlySpan key] { - get => Decompress(_wrapped[key]); - set => _wrapped[key] = Compress(value); + get => Decompress(wrapped[key]); + set => wrapped[key] = Compress(value); } - public IWriteBatch StartWriteBatch() => new WriteBatch(_wrapped.StartWriteBatch()); + public IWriteBatch StartWriteBatch() => new WriteBatch(wrapped.StartWriteBatch()); - private class WriteBatch : IWriteBatch + private class WriteBatch(IWriteBatch wrapped) : IWriteBatch { - private readonly IWriteBatch _wrapped; - - public WriteBatch(IWriteBatch wrapped) => _wrapped = wrapped; + public void Dispose() => wrapped.Dispose(); - public void Dispose() => _wrapped.Dispose(); + public void Clear() => wrapped.Clear(); public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - => _wrapped.Set(key, Compress(value), flags); + => wrapped.Set(key, Compress(value), flags); public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { - _wrapped.PutSpan(key, Compress(value, stackalloc byte[value.Length]), flags); - } + => wrapped.PutSpan(key, Compress(value, stackalloc byte[value.Length]), flags); - public bool PreferWriteByArray => _wrapped.PreferWriteByArray; + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) + => throw new InvalidOperationException("EOA compressing DB does not support merging"); + + public bool PreferWriteByArray => wrapped.PreferWriteByArray; public byte[]? this[ReadOnlySpan key] { - set => _wrapped[key] = Compress(value); + set => wrapped[key] = Compress(value); } } - /// /// The end of rlp of an EOA account, an empty and an empty . /// - private static ReadOnlySpan EmptyCodeHashStorageRoot => new byte[] - { + private static ReadOnlySpan EmptyCodeHashStorageRoot => + [ 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112 - }; + ]; private const byte PreambleLength = 1; private const byte PreambleIndex = 0; private const byte PreambleValue = 0; - private static byte[]? Compress(byte[]? bytes) - { - if (bytes is null) return null; - return Compress(bytes, stackalloc byte[bytes.Length]).ToArray(); - } + private static byte[]? Compress(byte[]? bytes) => bytes is null ? null : Compress(bytes, stackalloc byte[bytes.Length]).ToArray(); private static ReadOnlySpan Compress(ReadOnlySpan bytes, Span compressed) { @@ -118,55 +107,56 @@ private static ReadOnlySpan Compress(ReadOnlySpan bytes, Span return decompressed; } - public void Dispose() => _wrapped.Dispose(); + public void Dispose() => wrapped.Dispose(); - public string Name => _wrapped.Name; + public string Name => wrapped.Name; public KeyValuePair[] this[byte[][] keys] => throw new NotImplementedException(); - public IEnumerable> GetAll(bool ordered = false) => _wrapped.GetAll(ordered) + public IEnumerable> GetAll(bool ordered = false) => wrapped.GetAll(ordered) .Select(static kvp => new KeyValuePair(kvp.Key, Decompress(kvp.Value))); public IEnumerable GetAllKeys(bool ordered = false) => - _wrapped.GetAllKeys(ordered).Select(Decompress); + wrapped.GetAllKeys(ordered); public IEnumerable GetAllValues(bool ordered = false) => - _wrapped.GetAllValues(ordered).Select(Decompress); + wrapped.GetAllValues(ordered).Select(Decompress); - public void Remove(ReadOnlySpan key) => _wrapped.Remove(key); + public void Remove(ReadOnlySpan key) => wrapped.Remove(key); - public bool KeyExists(ReadOnlySpan key) => _wrapped.KeyExists(key); + public bool KeyExists(ReadOnlySpan key) => wrapped.KeyExists(key); - public void Flush(bool onlyWal) => _wrapped.Flush(onlyWal); + public void Flush(bool onlyWal) => wrapped.Flush(onlyWal); - public void Clear() => _wrapped.Clear(); + 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); + => wrapped.Set(key, Compress(value), flags); public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - => Decompress(_wrapped.Get(key, flags)); + => Decompress(wrapped.Get(key, flags)); + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => + wrapped.PutSpan(key, Compress(value, stackalloc byte[value.Length]), flags); - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { - _wrapped.PutSpan(key, Compress(value, stackalloc byte[value.Length]), flags); - } - - public Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - { + public Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => // Can't properly implement span for reading. As the decompressed span is different from the span // from DB, it would crash on DangerouslyReleaseMemory. - return Decompress(Get(key, flags)); + Decompress(Get(key, flags)); + + public MemoryManager? GetOwnedMemory(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + { + byte[]? data = Decompress(Get(key, flags)); + return data is null or { Length: 0 } ? null : new ArrayMemoryManager(data); } - public bool PreferWriteByArray => _wrapped.PreferWriteByArray; + public bool PreferWriteByArray => wrapped.PreferWriteByArray; public void Tune(ITunableDb.TuneType type) { - if (_wrapped is ITunableDb tunable) + if (wrapped is ITunableDb tunable) tunable.Tune(type); } } diff --git a/src/Nethermind/Nethermind.Db/DbNames.cs b/src/Nethermind/Nethermind.Db/DbNames.cs index e0f96bbc6468..9dd16b3ddc01 100644 --- a/src/Nethermind/Nethermind.Db/DbNames.cs +++ b/src/Nethermind/Nethermind.Db/DbNames.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only namespace Nethermind.Db @@ -18,6 +18,8 @@ public static class DbNames public const string Metadata = "metadata"; public const string BlobTransactions = "blobTransactions"; public const string DiscoveryNodes = "discoveryNodes"; + public const string DiscoveryV5Nodes = "discoveryV5Nodes"; public const string PeersDb = "peers"; + public const string LogIndex = "logIndex"; } } diff --git a/src/Nethermind/Nethermind.Db/DbTracker.cs b/src/Nethermind/Nethermind.Db/DbTracker.cs deleted file mode 100644 index 95829f161bea..000000000000 --- 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 fb2c98942716..cac0113d670a 100755 --- a/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs +++ b/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using System.Threading; using Nethermind.Core; @@ -17,7 +18,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 { @@ -71,6 +72,17 @@ public Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadF return value; } + public MemoryManager? GetOwnedMemory(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + { + MemoryManager? memoryManager = _currentDb.GetOwnedMemory(key, flags); + if (memoryManager is not null && _pruningContext?.DuplicateReads == true && (flags & ReadFlags.SkipDuplicateRead) == 0) + { + Duplicate(_pruningContext.CloningDb, key, memoryManager.GetSpan(), WriteFlags.None); + } + + return memoryManager; + } + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { _currentDb.Set(key, value, flags); // we are writing to the main DB @@ -103,6 +115,12 @@ private void Duplicate(IWriteOnlyKeyValueStore db, ReadOnlySpan key, ReadO _updateDuplicateWriteMetrics?.Invoke(); } + private void DuplicateMerge(IMergeableKeyValueStore db, ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags) + { + db.Merge(key, value, flags); + _updateDuplicateWriteMetrics?.Invoke(); + } + // we also need to duplicate writes that are in batches public IWriteBatch StartWriteBatch() => _pruningContext is null @@ -115,10 +133,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; @@ -140,6 +155,8 @@ public void Remove(ReadOnlySpan key) public bool KeyExists(ReadOnlySpan key) => _currentDb.KeyExists(key); + public void DangerousReleaseMemory(in ReadOnlySpan span) => _currentDb.DangerousReleaseMemory(span); + // inner DB's can be deleted in the future and // we cannot expose a DB that will potentially be later deleted public IDb Innermost => this; @@ -317,11 +334,23 @@ public void Dispose() _clonedWriteBatch.Dispose(); } + public void Clear() + { + _writeBatch.Clear(); + _clonedWriteBatch.Clear(); + } + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { _writeBatch.Set(key, value, flags); _db.Duplicate(_clonedWriteBatch, key, value, flags); } + + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) + { + _writeBatch.Merge(key, value, flags); + _db.DuplicateMerge(_clonedWriteBatch, key, value, flags); + } } public void Tune(ITunableDb.TuneType type) diff --git a/src/Nethermind/Nethermind.Db/IColumnsDb.cs b/src/Nethermind/Nethermind.Db/IColumnsDb.cs index 4f1eddcbd441..e7ec61e8fe27 100644 --- a/src/Nethermind/Nethermind.Db/IColumnsDb.cs +++ b/src/Nethermind/Nethermind.Db/IColumnsDb.cs @@ -13,10 +13,18 @@ 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); + void Clear(); + } + + + 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 73f9e4ba1718..cc2a6fa26c56 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 3a45f50e476a..2ce63caca620 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/IMergeOperator.cs b/src/Nethermind/Nethermind.Db/IMergeOperator.cs new file mode 100644 index 000000000000..3a2367636fd9 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/IMergeOperator.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core.Collections; +using Nethermind.Db.LogIndex; + +namespace Nethermind.Db; + +public interface IMergeOperator +{ + string Name { get; } + LogIndexUpdateStats Stats { get; } + LogIndexUpdateStats GetAndResetStats(); + + ArrayPoolList? FullMerge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator); + ArrayPoolList? PartialMerge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator); +} diff --git a/src/Nethermind/Nethermind.Db/IPruningConfig.cs b/src/Nethermind/Nethermind.Db/IPruningConfig.cs index 1330d97b540d..efd534d69528 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")] @@ -100,4 +100,10 @@ The max number of parallel tasks that can be used by full pruning. [ConfigItem(Description = "Maximum number of block in commit buffer before blocking.", DefaultValue = "128", HiddenFromDocs = true)] int MaxBufferedCommitCount { get; set; } + + [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/InMemoryColumnBatch.cs b/src/Nethermind/Nethermind.Db/InMemoryColumnBatch.cs index 9cff6e2fa957..45575158a8d0 100644 --- a/src/Nethermind/Nethermind.Db/InMemoryColumnBatch.cs +++ b/src/Nethermind/Nethermind.Db/InMemoryColumnBatch.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Nethermind.Core; +using Nethermind.Core.Collections; namespace Nethermind.Db { @@ -23,6 +24,14 @@ public IWriteBatch GetColumnBatch(TKey key) return writeBatch; } + public void Clear() + { + foreach (IWriteBatch batch in _underlyingBatch) + { + batch.Clear(); + } + } + public void Dispose() { foreach (IWriteBatch batch in _underlyingBatch) diff --git a/src/Nethermind/Nethermind.Db/InMemoryWriteBatch.cs b/src/Nethermind/Nethermind.Db/InMemoryWriteBatch.cs index 7c43ec39039c..c8054ac8b6d2 100644 --- a/src/Nethermind/Nethermind.Db/InMemoryWriteBatch.cs +++ b/src/Nethermind/Nethermind.Db/InMemoryWriteBatch.cs @@ -29,10 +29,20 @@ public void Dispose() GC.SuppressFinalize(this); } + public void Clear() + { + _currentItems.Clear(); + } + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { _currentItems[key.ToArray()] = value; _writeFlags = flags; } + + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) + { + throw new NotSupportedException("Merging is not supported by this implementation."); + } } } diff --git a/src/Nethermind/Nethermind.Db/LogIndex/AverageStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/AverageStats.cs new file mode 100644 index 000000000000..ba679987db6e --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/AverageStats.cs @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading; + +namespace Nethermind.Db.LogIndex; + +/// +/// Aggregates average of multiple incrementally-added values. +/// +public class AverageStats +{ + private long _total; + private int _count; + + public void Include(long value) + { + Interlocked.Add(ref _total, value); + Interlocked.Increment(ref _count); + } + + public double Average => _count == 0 ? 0 : (double)_total / _count; + + public override string ToString() => $"{Average:F2} ({_count:N0})"; + + public void Combine(AverageStats stats) + { + _total += stats._total; + _count += stats._count; + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/BlockReceipts.cs b/src/Nethermind/Nethermind.Db/LogIndex/BlockReceipts.cs new file mode 100644 index 000000000000..332c17e88ea2 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/BlockReceipts.cs @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Nethermind.Db.LogIndex; + +public readonly record struct BlockReceipts(int BlockNumber, TxReceipt[] Receipts); diff --git a/src/Nethermind/Nethermind.Db/LogIndex/CompactingStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/CompactingStats.cs new file mode 100644 index 000000000000..2a2b73718dcc --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/CompactingStats.cs @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Db.LogIndex; + +public class CompactingStats +{ + public ExecTimeStats Total { get; set; } = new(); + + public void Combine(CompactingStats other) + { + Total.Combine(other.Total); + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/Compactor.cs b/src/Nethermind/Nethermind.Db/LogIndex/Compactor.cs new file mode 100644 index 000000000000..03561369a2dc --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/Compactor.cs @@ -0,0 +1,203 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using Nethermind.Logging; + +[assembly: InternalsVisibleTo("Nethermind.Db.Test")] +namespace Nethermind.Db.LogIndex; + +partial class LogIndexStorage +{ + /// + /// Periodically forces background log index compaction for every N added blocks. + /// + internal class Compactor : ICompactor + { + private int? _lastAtMin; + private int? _lastAtMax; + + private CompactingStats _stats = new(); + private readonly ILogIndexStorage _storage; + private readonly IDbMeta _rootDb; + private readonly ILogger _logger; + private readonly int _compactionDistance; + + private readonly CancellationTokenSource _cts = new(); + + /// + /// Bounded(1) compaction work queue consumed by .
+ /// null — fire-and-forget compaction enqueued by ;
+ /// not null — caller-awaitable compaction enqueued by . + ///
+ private readonly Channel _channel = Channel.CreateBounded(1); + + private volatile TaskCompletionSource? _pendingForcedCompaction; + private readonly Task _compactionTask; + + public Compactor(ILogIndexStorage storage, IDbMeta rootDb, ILogger logger, int compactionDistance) + { + _storage = storage; + _rootDb = rootDb; + _logger = logger; + + if (compactionDistance < 1) throw new ArgumentException("Compaction distance must be a positive value.", nameof(compactionDistance)); + _compactionDistance = compactionDistance; + + _lastAtMin = storage.MinBlockNumber; + _lastAtMax = storage.MaxBlockNumber; + + _compactionTask = DoCompactAsync(); + } + + public CompactingStats GetAndResetStats() => Interlocked.Exchange(ref _stats, new()); + + // Not thread-safe + public bool TryEnqueue() + { + if (_cts.IsCancellationRequested) + return false; + + _lastAtMin ??= _storage.MinBlockNumber; + _lastAtMax ??= _storage.MaxBlockNumber; + + var uncompacted = 0; + if (_storage.MinBlockNumber is { } storageMin && storageMin < _lastAtMin) + uncompacted += _lastAtMin.Value - storageMin; + if (_storage.MaxBlockNumber is { } storageMax && storageMax > _lastAtMax) + uncompacted += storageMax - _lastAtMax.Value; + + if (uncompacted < _compactionDistance) + return false; + + if (!_channel.Writer.TryWrite(null)) + return false; + + _lastAtMin = _storage.MinBlockNumber; + _lastAtMax = _storage.MaxBlockNumber; + return true; + } + + public async Task StopAsync() + { + await _cts.CancelAsync(); + _channel.Writer.TryComplete(); + await _compactionTask; + } + + public async Task ForceAsync() + { + // Coalesce concurrent calls — all callers share a single compaction + TaskCompletionSource tcs = new(TaskCreationOptions.RunContinuationsAsynchronously); + TaskCompletionSource? existing = Interlocked.CompareExchange(ref _pendingForcedCompaction, tcs, null); + + if (existing is not null) + { + await existing.Task; + return _stats; + } + + try + { + await _channel.Writer.WriteAsync(tcs, _cts.Token); + } + catch (Exception ex) + { + Interlocked.CompareExchange(ref _pendingForcedCompaction, null, tcs); + if (ex is OperationCanceledException) + tcs.TrySetCanceled(); + else + tcs.TrySetException(ex); + + throw; + } + + await tcs.Task; + return _stats; + } + + private async Task DoCompactAsync() + { + CancellationToken cancellation = _cts.Token; + try + { + await foreach (TaskCompletionSource? tcs in _channel.Reader.ReadAllAsync(cancellation)) + { + try + { + if (_logger.IsInfo) _logger.Info($"Log index: compaction started, DB size: {_storage.GetDbSize()}"); + + var timestamp = Stopwatch.GetTimestamp(); + _rootDb.Compact(); + + TimeSpan elapsed = Stopwatch.GetElapsedTime(timestamp); + _stats.Total.Include(elapsed); + + if (_logger.IsInfo) _logger.Info($"Log index: compaction ended in {elapsed}, DB size: {_storage.GetDbSize()}"); + + tcs?.TrySetResult(); + } + catch (OperationCanceledException) + { + tcs?.TrySetCanceled(); + } + catch (Exception ex) + { + tcs?.TrySetException(ex); + (_storage as LogIndexStorage)?.OnBackgroundError(ex); + + await _cts.CancelAsync(); + _channel.Writer.TryComplete(); + + break; + } + finally + { + if (tcs is not null) + Interlocked.CompareExchange(ref _pendingForcedCompaction, null, tcs); + } + } + } + catch (OperationCanceledException) + { + if (_logger.IsDebug) _logger.Debug("Log index: compaction loop canceled"); + } + finally + { + while (_channel.Reader.TryRead(out TaskCompletionSource? remaining) && remaining is not null) + { + remaining.TrySetCanceled(); + Interlocked.CompareExchange(ref _pendingForcedCompaction, null, remaining); + } + } + } + + public void Dispose() + { + _cts.Dispose(); + _channel.Writer.TryComplete(); + } + } + + private class NoOpCompactor : ICompactor + { + public CompactingStats GetAndResetStats() => new(); + public bool TryEnqueue() => false; + public Task StopAsync() => Task.CompletedTask; + public Task ForceAsync() => Task.FromResult(new CompactingStats()); + public void Dispose() { } + } + + internal interface ICompactor : IDisposable + { + CompactingStats GetAndResetStats(); + bool TryEnqueue(); + Task StopAsync(); + Task ForceAsync(); + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/CompressionAlgorithm.cs b/src/Nethermind/Nethermind.Db/LogIndex/CompressionAlgorithm.cs new file mode 100644 index 000000000000..3c0a3b83280b --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/CompressionAlgorithm.cs @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using static Nethermind.TurboPForBindings.TurboPFor; + +namespace Nethermind.Db.LogIndex; + +partial class LogIndexStorage +{ + /// + /// Represents compression algorithm to be used by log index. + /// + public class CompressionAlgorithm( + string name, + CompressionAlgorithm.CompressFunc compressionFunc, + CompressionAlgorithm.DecompressFunc decompressionFunc + ) + { + public delegate nuint CompressFunc(ReadOnlySpan @in, nuint n, Span @out); + public delegate nuint DecompressFunc(ReadOnlySpan @in, nuint n, Span @out); + + private static readonly Dictionary SupportedMap = new(); + + public static IReadOnlyDictionary Supported => SupportedMap; + + public static KeyValuePair Best => + SupportedMap.TryGetValue(nameof(p4nd1enc256v32), out CompressionAlgorithm p256) + ? KeyValuePair.Create(nameof(p4nd1enc256v32), p256) + : KeyValuePair.Create(nameof(p4nd1enc128v32), SupportedMap[nameof(p4nd1enc128v32)]); + + static CompressionAlgorithm() + { + SupportedMap.Add( + nameof(p4nd1enc128v32), + new(nameof(p4nd1enc128v32), p4nd1enc128v32, p4nd1dec128v32) + ); + + if (Supports256Blocks) + { + SupportedMap.Add( + nameof(p4nd1enc256v32), + new(nameof(p4nd1enc256v32), p4nd1enc256v32, p4nd1dec256v32) + ); + } + } + + public string Name => name; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public nuint Compress(ReadOnlySpan @in, nuint n, Span @out) => compressionFunc(@in, n, @out); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public nuint Decompress(ReadOnlySpan @in, nuint n, Span @out) => decompressionFunc(@in, n, @out); + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/Compressor.cs b/src/Nethermind/Nethermind.Db/LogIndex/Compressor.cs new file mode 100644 index 000000000000..3d6bf0ae947c --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/Compressor.cs @@ -0,0 +1,208 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using System.Threading.Tasks.Dataflow; +using Nethermind.Core; +using Nethermind.Core.Extensions; + +namespace Nethermind.Db.LogIndex; + +partial class LogIndexStorage +{ + /// + /// Does background compression for keys with the number of blocks above the threshold. + /// + /// + /// Consumes "transient" keys with value being too big (see ) + /// from and performs compression in the background.
+ /// Can utilize multiple threads, as per . + /// + /// + /// For each "transient" key in the queue performs the following: + /// + /// reads the latest (uncompressed) value from the database; + /// truncates potentially reorgable blocks from the sequence (as compressed values become immutable); + /// reverts sequence if needed - so the new value is always in ascending order; + /// compresses the sequence using specified TurboPFor ; + /// stores the compressed value at a new pair using {address-or-topic} || ({first-block-number-in-the-sequence} + 1) as a key; + /// queues truncation for the "transient" key via - to remove finalized blocks from the old sequence. + /// + /// + /// Last 2 operations are done via a single to maintain data consistency. + ///
+ private class Compressor : ICompressor + { + private readonly int _minLengthToCompress; + + // Used instead of a channel to prevent duplicates + private readonly ConcurrentDictionary _compressQueue = new(Bytes.EqualityComparer); + private readonly ConcurrentDictionary.AlternateLookup> _compressQueueLookup; + private readonly LogIndexStorage _storage; + private readonly ActionBlock<(int?, byte[])> _processing; + private readonly ManualResetEventSlim _startEvent = new(false); + private readonly ManualResetEventSlim _queueEmptyEvent = new(true); + + private int _processingCount; + private PostMergeProcessingStats _stats = new(); + + public PostMergeProcessingStats GetAndResetStats() + { + _stats.QueueLength = _processing.InputCount; + return Interlocked.Exchange(ref _stats, new()); + } + + public Compressor(LogIndexStorage storage, int compressionDistance, int parallelism) + { + _compressQueueLookup = _compressQueue.GetAlternateLookup>(); + _storage = storage; + + _minLengthToCompress = compressionDistance * BlockNumberSize; + + if (parallelism < 1) throw new ArgumentException("Compression parallelism degree must be a positive value.", nameof(parallelism)); + _processing = new(x => CompressValue(x.Item1, x.Item2), new() { MaxDegreeOfParallelism = parallelism, BoundedCapacity = 10_000 }); + } + + public bool TryEnqueue(int? topicIndex, ReadOnlySpan dbKey, ReadOnlySpan dbValue) + { + if (dbValue.Length < _minLengthToCompress) + return false; + + if (_compressQueueLookup.TryGetValue(dbKey, out _)) + return false; + + byte[] dbKeyArr = dbKey.ToArray(); + if (!_compressQueue.TryAdd(dbKeyArr, true)) + return false; + + if (_processing.Post((topicIndex, dbKeyArr))) + return true; + + _compressQueue.TryRemove(dbKeyArr, out _); + return false; + } + + public async Task EnqueueAsync(int? topicIndex, byte[] dbKey) + { + await _processing.SendAsync((topicIndex, dbKey)); + _queueEmptyEvent.Reset(); + } + + public Task WaitUntilEmptyAsync(TimeSpan waitTime, CancellationToken cancellationToken) => + _queueEmptyEvent.WaitHandle.WaitOneAsync(waitTime, cancellationToken); + + private void CompressValue(int? topicIndex, byte[] dbKey) + { + if (_storage.HasBackgroundError) + return; + + Interlocked.Increment(ref _processingCount); + + try + { + _startEvent.Wait(); + + if (_storage.HasBackgroundError) + return; + + long execTimestamp = Stopwatch.GetTimestamp(); + IDb db = _storage.GetDb(topicIndex); + + long timestamp = Stopwatch.GetTimestamp(); + Span dbValue = db.Get(dbKey); + _stats.DBReading.Include(Stopwatch.GetElapsedTime(timestamp)); + + // Do not compress blocks that can be reorged, as compressed data is immutable + if (!UseBackwardSyncFor(dbKey)) + dbValue = _storage.RemoveReorgableBlocks(dbValue); + + if (dbValue.Length < _minLengthToCompress) + return; + + int truncateBlock = ReadLastBlockNumber(dbValue); + + ReverseBlocksIfNeeded(dbValue); + + int postfixBlock = ReadBlockNumber(dbValue); + + ReadOnlySpan key = ExtractKey(dbKey); + Span dbKeyComp = stackalloc byte[key.Length + BlockNumberSize]; + key.CopyTo(dbKeyComp); + WriteKeyBlockNumber(dbKeyComp[key.Length..], postfixBlock); + + timestamp = Stopwatch.GetTimestamp(); + dbValue = _storage.CompressDbValue(dbKey, dbValue); + _stats.CompressingValue.Include(Stopwatch.GetElapsedTime(timestamp)); + + // Put compressed value at a new key and clear the uncompressed one + timestamp = Stopwatch.GetTimestamp(); + using (IWriteBatch batch = db.StartWriteBatch()) + { + Span truncateOp = MergeOps.Create(MergeOp.Truncate, truncateBlock, stackalloc byte[MergeOps.Size]); + batch.PutSpan(dbKeyComp, dbValue); + batch.Merge(dbKey, truncateOp); + } + + _stats.DBSaving.Include(Stopwatch.GetElapsedTime(timestamp)); + + Interlocked.Increment(ref topicIndex is null ? ref _stats.CompressedAddressKeys : ref _stats.CompressedTopicKeys); + _stats.Total.Include(Stopwatch.GetElapsedTime(execTimestamp)); + } + catch (Exception ex) + { + _storage.OnBackgroundError(ex); + } + finally + { + _compressQueue.TryRemove(dbKey, out _); + + int processingCount = Interlocked.Decrement(ref _processingCount); + + if (_processing.InputCount == 0 && processingCount == 0) + _queueEmptyEvent.Set(); + } + } + + public void Start() => _startEvent.Set(); + + public Task StopAsync() + { + _processing.Complete(); + return _processing.Completion; // Wait for the compression queue to finish + } + + public void Dispose() + { + _startEvent.Dispose(); + _queueEmptyEvent.Dispose(); + } + } + + public sealed class NoOpCompressor : ICompressor + { + private PostMergeProcessingStats Stats { get; } = new(); + public PostMergeProcessingStats GetAndResetStats() => Stats; + public bool TryEnqueue(int? topicIndex, ReadOnlySpan dbKey, ReadOnlySpan dbValue) => false; + public Task EnqueueAsync(int? topicIndex, byte[] dbKey) => Task.CompletedTask; + public Task WaitUntilEmptyAsync(TimeSpan waitTime, CancellationToken cancellationToken) => Task.CompletedTask; + public void Start() { } + public Task StopAsync() => Task.CompletedTask; + public void Dispose() { } + } + + public interface ICompressor : IDisposable + { + PostMergeProcessingStats GetAndResetStats(); + + bool TryEnqueue(int? topicIndex, ReadOnlySpan dbKey, ReadOnlySpan dbValue); + Task EnqueueAsync(int? topicIndex, byte[] dbKey); + Task WaitUntilEmptyAsync(TimeSpan waitTime = default, CancellationToken cancellationToken = default); + + void Start(); + Task StopAsync(); + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/DisabledLogIndexStorage.cs b/src/Nethermind/Nethermind.Db/LogIndex/DisabledLogIndexStorage.cs new file mode 100644 index 000000000000..9c483a550533 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/DisabledLogIndexStorage.cs @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Crypto; + +namespace Nethermind.Db.LogIndex; + +public sealed class DisabledLogIndexStorage : ILogIndexStorage +{ + public bool Enabled => false; + + public string GetDbSize() => "0 B"; + + public int? MaxBlockNumber => null; + public int? MinBlockNumber => null; + + public IEnumerator GetEnumerator(Address address, int from, int to) => + throw new NotSupportedException(); + + public IEnumerator GetEnumerator(int topicIndex, Hash256 topic, int from, int to) => + throw new NotSupportedException(); + + public LogIndexAggregate Aggregate(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats = null) => + throw new NotSupportedException(); + + public Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null) => + throw new NotSupportedException(); + + public Task RemoveReorgedAsync(BlockReceipts block) => + throw new NotSupportedException(); + + public ValueTask DisposeAsync() => ValueTask.CompletedTask; + public Task StopAsync() => Task.CompletedTask; +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/ExecTimeStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/ExecTimeStats.cs new file mode 100644 index 000000000000..19c00334ee3e --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/ExecTimeStats.cs @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading; + +namespace Nethermind.Db.LogIndex; + +/// +/// Aggregates average and total execution time of multiple executions of the same operation. +/// +public class ExecTimeStats +{ + private long _totalTicks; + private int _count; + + public void Include(TimeSpan elapsed) + { + Interlocked.Add(ref _totalTicks, elapsed.Ticks); + Interlocked.Increment(ref _count); + } + + public TimeSpan Total => TimeSpan.FromTicks(_totalTicks); + public TimeSpan Average => _count == 0 ? TimeSpan.Zero : TimeSpan.FromTicks((long)((double)_totalTicks / _count)); + + private string Format(TimeSpan value) => value switch + { + { TotalDays: >= 1 } x => $"{x.TotalDays:F2}d", + { TotalHours: >= 1 } x => $"{x.TotalHours:F2}h", + { TotalMinutes: >= 1 } x => $"{x.TotalMinutes:F2}m", + { TotalSeconds: >= 1 } x => $"{x.TotalSeconds:F2}s", + { TotalMilliseconds: >= 1 } x => $"{x.TotalMilliseconds:F1}ms", + { TotalMicroseconds: >= 1 } x => $"{x.TotalMicroseconds:F1}μs", + var x => $"{x.TotalNanoseconds:F1}ns" + }; + + public override string ToString() => $"{Format(Average)} ({_count:N0}) [{Format(Total)}]"; + + public void Combine(ExecTimeStats stats) + { + _totalTicks += stats._totalTicks; + _count += stats._count; + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexConfig.cs b/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexConfig.cs new file mode 100644 index 000000000000..53cb3b45148c --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexConfig.cs @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Config; +using Nethermind.TurboPForBindings; + +namespace Nethermind.Db.LogIndex; + +public interface ILogIndexConfig : IConfig +{ + [ConfigItem( + Description = "Whether log index should be enabled.", + DefaultValue = "false" + )] + public bool Enabled { get; set; } + + [ConfigItem( + Description = "Log index is reset on startup if enabled.", + DefaultValue = "false" + )] + public bool Reset { get; set; } + + [ConfigItem( + Description = "Max allowed reorg depth for the index.", + DefaultValue = "64", + HiddenFromDocs = true + )] + public int? MaxReorgDepth { get; set; } + + [ConfigItem( + Description = "Maximum number of blocks with receipts to add to index per iteration.", + DefaultValue = "256", + HiddenFromDocs = true + )] + public int MaxBatchSize { get; set; } + + [ConfigItem( + Description = "Maximum number of batches to queue for aggregation.", + DefaultValue = "16", + HiddenFromDocs = true + )] + public int MaxAggregationQueueSize { get; set; } + + [ConfigItem( + Description = "Maximum number of aggregated batches to queue for inclusion to the index.", + DefaultValue = "16", + HiddenFromDocs = true + )] + public int MaxSavingQueueSize { get; set; } + + [ConfigItem( + Description = "Maximum degree of parallelism for fetching receipts.", + DefaultValue = "Max(ProcessorCount / 2, 1)", + HiddenFromDocs = true + )] + public int MaxReceiptsParallelism { get; set; } + + [ConfigItem( + Description = "Maximum degree of parallelism for aggregating batches.", + DefaultValue = "Max(ProcessorCount / 2, 1)", + HiddenFromDocs = true + )] + public int MaxAggregationParallelism { get; set; } + + [ConfigItem( + Description = "Maximum degree of parallelism for compressing overgrown key values.", + DefaultValue = "Max(ProcessorCount / 2, 1)", + HiddenFromDocs = true + )] + public int MaxCompressionParallelism { get; set; } + + [ConfigItem( + Description = "Minimum number of blocks under a single key to compress.", + DefaultValue = "128", + HiddenFromDocs = true + )] + public int CompressionDistance { get; set; } + + [ConfigItem( + Description = "Number of newly added blocks after which to run DB compaction.", + DefaultValue = "262,144", + HiddenFromDocs = true + )] + public int CompactionDistance { get; set; } + + [ConfigItem( + Description = "Compression algorithm to use for block numbers.", + DefaultValue = nameof(TurboPFor.p4nd1enc256v32) + " if supported, otherwise " + nameof(TurboPFor.p4nd1enc128v32), + HiddenFromDocs = true + )] + string? CompressionAlgorithm { get; set; } + + [ConfigItem( + Description = "Whether to show detailed stats in progress logs.", + DefaultValue = "false", + HiddenFromDocs = true + )] + bool DetailedLogs { get; set; } + + [ConfigItem( + Description = "Whether to verify that eth_getLogs response generated using index matches one generated without.", + DefaultValue = "false", + HiddenFromDocs = true + )] + bool VerifyRpcResponse { get; set; } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexStorage.cs b/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexStorage.cs new file mode 100644 index 000000000000..af084f85c310 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexStorage.cs @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.ServiceStopper; + +namespace Nethermind.Db.LogIndex; + +public interface ILogIndexStorage : IAsyncDisposable, IStoppableService +{ + bool Enabled { get; } + + /// + /// Max block number added to the index. + /// + int? MaxBlockNumber { get; } + + /// + /// Min block number added to the index. + /// + int? MinBlockNumber { get; } + + /// + /// Gets enumerator of block numbers between and + /// where given has occurred. + /// + IEnumerator GetEnumerator(Address address, int from, int to); + + /// + /// Gets enumerator of block numbers between and + /// where given has occurred at the given . + /// + IEnumerator GetEnumerator(int topicIndex, Hash256 topic, int from, int to); + + /// + /// Aggregates receipts from the into in-memory . + /// + LogIndexAggregate Aggregate(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats = null); + + /// + /// Adds receipts from the to the index. + /// + Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null); + + /// + /// Removes reorged from the index. + /// This must be called for each reorged block in a sequential ascending order. + /// + Task RemoveReorgedAsync(BlockReceipts block); + + string GetDbSize(); +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexAggregate.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexAggregate.cs new file mode 100644 index 000000000000..bd0f40280484 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexAggregate.cs @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Linq; +using Nethermind.Core; +using Nethermind.Core.Crypto; + +namespace Nethermind.Db.LogIndex; + +/// +/// Set of in-memory dictionaries mapping each address/topic to a sequence of block numbers +/// from the - range. +/// +public struct LogIndexAggregate(int firstBlockNum, int lastBlockNum) +{ + private Dictionary>? _address; + private Dictionary>[]? _topic; + + public int FirstBlockNum { get; } = firstBlockNum; + public int LastBlockNum { get; } = lastBlockNum; + + public Dictionary> Address => _address ??= new(); + + public Dictionary>[] Topic => _topic ??= Enumerable.Range(0, LogIndexStorage.MaxTopics) + .Select(static _ => new Dictionary>()) + .ToArray(); + + public bool IsEmpty => (_address is null || _address.Count == 0) && (_topic is null || _topic[0].Count == 0); + public int TopicCount => _topic is { Length: > 0 } ? _topic.Sum(static t => t.Count) : 0; + + public LogIndexAggregate(IReadOnlyList batch) : this(batch[0].BlockNumber, batch[^1].BlockNumber) { } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexColumns.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexColumns.cs new file mode 100644 index 000000000000..7492a1aca278 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexColumns.cs @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Db.LogIndex; + +public enum LogIndexColumns +{ + Meta, + Addresses, + Topics0, + Topics1, + Topics2, + Topics3 +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexConfig.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexConfig.cs new file mode 100644 index 000000000000..5208f5ba2003 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexConfig.cs @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Config; + +namespace Nethermind.Db.LogIndex; + +[ConfigCategory(Description = "Configuration of the log index behaviour.")] +public class LogIndexConfig : ILogIndexConfig +{ + public bool Enabled { get; set; } = false; + public bool Reset { get; set; } = false; + + // set from PruningConfig via decorator + public int? MaxReorgDepth { get; set; } + + public int MaxBatchSize { get; set; } = 256; + public int MaxAggregationQueueSize { get; set; } = 16; + public int MaxSavingQueueSize { get; set; } = 16; + + public int MaxReceiptsParallelism { get; set; } = Math.Max(Environment.ProcessorCount / 2, 1); + public int MaxAggregationParallelism { get; set; } = Math.Max(Environment.ProcessorCount / 2, 1); + public int MaxCompressionParallelism { get; set; } = Math.Max(Environment.ProcessorCount / 2, 1); + + public int CompressionDistance { get; set; } = 128; + public int CompactionDistance { get; set; } = 262_144; + + public string? CompressionAlgorithm { get; set; } = LogIndexStorage.CompressionAlgorithm.Best.Key; + + public bool DetailedLogs { get; set; } = false; + public bool VerifyRpcResponse { get; set; } = false; +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexEnumerator.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexEnumerator.cs new file mode 100644 index 000000000000..1eb40242a6ca --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexEnumerator.cs @@ -0,0 +1,153 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections; +using System.Collections.Generic; +using Nethermind.Core; +using Nethermind.Core.Collections; + +namespace Nethermind.Db.LogIndex; + +public partial class LogIndexStorage +{ + // TODO: pre-fetch next value? + /// + /// Enumerates block numbers from for the given key, + /// within the specified from/to range. + /// + public sealed class LogIndexEnumerator : IEnumerator + { + private const int CompletedIndex = int.MinValue; + + private readonly CompressionAlgorithm _compressionAlgorithm; + private readonly byte[] _key; + private readonly int _from; + private readonly int _to; + private readonly ISortedView _view; + + private ArrayPoolList? _value; + private int _index; + + public LogIndexEnumerator(ISortedKeyValueStore db, CompressionAlgorithm compressionAlgorithm, byte[] key, int from, int to) + { + if (from < 0) from = 0; + if (to < from) throw new ArgumentException("To must be greater or equal to from.", nameof(to)); + + _key = key; + (_from, _to) = (from, to); + _compressionAlgorithm = compressionAlgorithm; + + ReadOnlySpan fromKey = CreateDbKey(_key, Postfix.BackwardMerge, stackalloc byte[MaxDbKeyLength]); + ReadOnlySpan toKey = CreateDbKey(_key, Postfix.UpperBound, stackalloc byte[MaxDbKeyLength]); + _view = db.GetViewBetween(fromKey, toKey); + } + + private bool IsWithinRange() + { + int current = Current; + return current >= _from && current <= _to; + } + + public bool MoveNext() => _index != CompletedIndex && (_value is null ? TryStart() : TryMove()); + + private bool TryStart() + { + if (TryStartView()) + { + SetValue(); + _index = FindFromIndex(); + } + else // End immediately + { + _index = CompletedIndex; + return false; + } + + // Shift the view until we can start at `from` + while (Current < _from && _view.MoveNext()) + { + SetValue(); + _index = FindFromIndex(); + } + + // Check if the end of the range is reached + if (!IsWithinRange()) + { + _index = CompletedIndex; + return false; + } + + return true; + } + + private bool TryMove() + { + _index++; + + // Shift the view until we can continue + while (_index >= _value!.Count && _view.MoveNext()) + { + SetValue(); + _index = 0; + } + + // Check if the end of the range is reached + if (!IsWithinRange()) + { + _index = CompletedIndex; + return false; + } + + return true; + } + + private bool TryStartView() + { + ReadOnlySpan startKey = CreateDbKey(_key, _from, stackalloc byte[MaxDbKeyLength]); + + // need to start either just before the startKey + // or at the beginning of a view otherwise + return _view.StartBefore(startKey) || _view.MoveNext(); + } + + private void SetValue() + { + _value?.Dispose(); + + ReadOnlySpan viewValue = _view.CurrentValue; + + if (IsCompressed(viewValue, out var length)) + { + // +1 fixes TurboPFor reading outside of array bounds + _value = new(capacity: length + 1, count: length); + DecompressDbValue(_compressionAlgorithm, viewValue, _value.AsSpan()); + } + else + { + length = viewValue.Length / BlockNumberSize; + _value = new(capacity: length, count: length); + ReadBlockNumbers(viewValue, _value.AsSpan()); + } + + ReverseBlocksIfNeeded(_value.AsSpan()); + } + + private int FindFromIndex() + { + int index = BinarySearch(_value!.AsSpan(), _from); + return index >= 0 ? index : ~index; + } + + public void Reset() => throw new NotSupportedException($"{nameof(LogIndexEnumerator)} can not be reset."); + + public int Current => _value is not null && _index >= 0 && _index < _value.Count ? _value[_index] : -1; + object? IEnumerator.Current => Current; + + public void Dispose() + { + _view.Dispose(); + _value?.Dispose(); + } + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStateException.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStateException.cs new file mode 100644 index 000000000000..113ebfb045b7 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStateException.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Db.LogIndex; + +public class LogIndexStateException(string message, ReadOnlySpan key = default) : Exception(message) +{ + public byte[]? Key { get; } = key.Length == 0 ? null : key.ToArray(); + + public override string Message => Key is null + ? base.Message + : $"{base.Message} (Key: {Convert.ToHexString(Key)})"; +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStorage.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStorage.cs new file mode 100644 index 000000000000..fc8e6a8f73ff --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStorage.cs @@ -0,0 +1,1006 @@ +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Logging; + +namespace Nethermind.Db.LogIndex +{ + // TODO: use uint instead of int for block number? + /// + /// Database for log index, mapping addresses/topics to a set of blocks they occur in. + /// + /// + /// + /// + /// Uses 6 column families (see ): + /// + /// 1 for metadata (for now stores only the earliest and the latest added block numbers); + /// 1 for address mappings; + /// 4 for topic mappings (separate 1 for each topic position). + /// + /// + /// + /// + /// Each unique filter (topic/address) has a set of DB mappings filter -> block numbers: + /// + /// + /// 1 for newly coming numbers from backward sync;
+ /// key here is formed as filter || ;
+ /// value is a sequence of concatenated block numbers in strictly descending order using little-endian encoding; + ///
+ /// + /// 1 for newly coming numbers from forward sync;
+ /// key is filter || ;
+ /// value is a sequence of concatenated block numbers in strictly ascending order using little-endian encoding; + ///
+ /// + /// any number of "finalized" mappings, storing block numbers in strictly ascending order, compressed via TurboPFor;
+ /// key is filter || ({first-block-number-in-the-sequence} + 1) with number being encoded in big-endian (for correct RocksDB sorting);
+ /// value is a sequence of block numbers compressed via TurboPFor (see ). + ///
+ ///
+ /// Keys (1) and (2) are called "transient", as their content can change frequently, + /// while keys from (3) - "finalized", as their data is immutable.
+ /// Block sequences are not-intersecting and following a strict order, such that:
+ /// + /// max({backward-sync-numbers}) < any({finalized-numbers}) < min({forward-sync-numbers}) + /// + ///
+ /// + /// + /// New blocks are added in batches in a strictly ascending or descending (backward sync) order without gaps.
+ /// Process is separated into 2 steps to improve parallelization: + /// + /// in-memory dictionary is aggregated, mapping filter to a sequence of block numbers (see ); + /// each dictionary pair is saved to DB via call (see ). + /// + /// Whole block batch is added in a single cross-family .
+ /// Merging always happens to a "transient" (backward- or forward-sync) key, depending on the direction (passed as a parameter during aggregation). + /// After that, is responsible for concatenating sequences into a single uncompressed DB value, + /// and is forming "finalized" compressed values when "transient" sequences grow too big. + ///
+ /// + /// + /// Fetching block numbers for the given filters (see ) + /// is done by iterating keys via (with the provided filter as a prefix), + /// decomposing DB values back into block number sequences, and returning obtained numbers in ascending order. + /// Check for details. + /// + ///
+ public partial class LogIndexStorage : ILogIndexStorage + { + private static class SpecialKey + { + public static readonly byte[] Version = "ver"u8.ToArray(); + public static readonly byte[] MinBlockNum = "min"u8.ToArray(); + public static readonly byte[] MaxBlockNum = "max"u8.ToArray(); + public static readonly byte[] CompressionAlgo = "alg"u8.ToArray(); + } + + public static class Postfix + { + // Any ordered prefix seeking will start on it + public static readonly byte[] BackwardMerge = Enumerable.Repeat((byte)0, BlockNumberSize).ToArray(); + + // Any ordered prefix seeking will end on it + public static readonly byte[] ForwardMerge = Enumerable.Repeat(byte.MaxValue, BlockNumberSize).ToArray(); + + // Exclusive upper bound for iterator seek, so that ForwardMerge will be the last key + public static readonly byte[] UpperBound = Enumerable.Repeat(byte.MaxValue, BlockNumberSize).Concat([byte.MinValue]).ToArray(); + } + + [InlineArray(MaxTopics)] + private struct TopicBatches + { + private IWriteBatch _element; + } + + [InlineArray(MaxTopics + 1)] + private struct AllMergeOperators + { + private IMergeOperator _element; + } + + private ref struct DbBatches : IDisposable + { + private bool _completed; + + private readonly IColumnsWriteBatch _batch; + public IWriteBatch Meta { get; } + public IWriteBatch Address { get; } + public readonly TopicBatches Topics; + + public DbBatches(IColumnsDb rootDb) + { + _batch = rootDb.StartWriteBatch(); + + Meta = _batch.GetColumnBatch(LogIndexColumns.Meta); + Address = _batch.GetColumnBatch(LogIndexColumns.Addresses); + for (var topicIndex = 0; topicIndex < MaxTopics; topicIndex++) + Topics[topicIndex] = _batch.GetColumnBatch(GetColumn(topicIndex)); + } + + // Require explicit Commit call instead of committing on Dispose + public void Commit() + { + if (_completed) return; + _completed = true; + + _batch.Dispose(); + } + + public void Dispose() + { + if (_completed) return; + _completed = true; + + _batch.Clear(); + _batch.Dispose(); + } + } + + private static readonly byte[] VersionBytes = [1]; + + public const int MaxTopics = 4; + + public bool Enabled { get; } + + public const int BlockNumberSize = sizeof(int); + private const int MaxKeyLength = Hash256.Size + 1; // Math.Max(Address.Size, Hash256.Size) + private const int MaxDbKeyLength = MaxKeyLength + BlockNumberSize; + + private static readonly ArrayPool Pool = ArrayPool.Shared; + + private readonly IColumnsDb _rootDb; + private readonly IDb _metaDb; + private readonly IDb _addressDb; + private readonly IDb[] _topicDbs; + + private IEnumerable DBColumns + { + get + { + yield return _metaDb; + yield return _addressDb; + + foreach (IDb topicDb in _topicDbs) + yield return topicDb; + } + } + + private readonly ILogger _logger; + + private readonly int _maxReorgDepth; + + private readonly AllMergeOperators _mergeOperators; + private readonly ICompressor _compressor; + private readonly ICompactor _compactor; + private readonly CompressionAlgorithm _compressionAlgorithm; + + private readonly Lock _rangeInitLock = new(); + + private int? _maxBlock; + private int? _minBlock; + + public int? MaxBlockNumber => _maxBlock; + public int? MinBlockNumber => _minBlock; + + private Exception? _lastBackgroundError; + public bool HasBackgroundError => _lastBackgroundError is not null; + + /// + /// Whether a first batch was already added. + /// + private bool FirstBlockAdded => _minBlock is not null || _maxBlock is not null; + + /// + /// Guarantees / initialization won't be run concurrently. + /// + private readonly SemaphoreSlim _initSemaphore = new(1, 1); + + /// + /// Used for blocking concurrent executions and + /// ensuring the current iteration is completed before stopping/disposing. + /// + private readonly SemaphoreSlim _forwardWriteSemaphore = new(1, 1); + private readonly SemaphoreSlim _backwardWriteSemaphore = new(1, 1); + + private bool _stopped; + private bool _disposed; + + public LogIndexStorage(IDbFactory dbFactory, ILogManager logManager, ILogIndexConfig config) + { + try + { + Enabled = config.Enabled; + + _maxReorgDepth = config.MaxReorgDepth!.Value; + + _logger = logManager.GetClassLogger(); + + _compressor = config.CompressionDistance > 0 + ? new Compressor(this, config.CompressionDistance, config.MaxCompressionParallelism) + : new NoOpCompressor(); + + for (int i = -1; i < MaxTopics; i++) + _mergeOperators[i + 1] = new MergeOperator(this, _compressor, topicIndex: i < 0 ? null : i); + + _rootDb = CreateRootDb(dbFactory, config.Reset); + _metaDb = GetMetaDb(_rootDb); + _addressDb = _rootDb.GetColumnDb(LogIndexColumns.Addresses); + _topicDbs = Enumerable.Range(0, MaxTopics).Select(topicIndex => _rootDb.GetColumnDb(GetColumn(topicIndex))).ToArray(); + + _compactor = config.CompactionDistance > 0 + ? new Compactor(this, _rootDb, _logger, config.CompactionDistance) + : new NoOpCompactor(); + + _compressionAlgorithm = SelectCompressionAlgorithm(config.CompressionAlgorithm); + + (_minBlock, _maxBlock) = (LoadRangeBound(SpecialKey.MinBlockNum), LoadRangeBound(SpecialKey.MaxBlockNum)); + + if (Enabled) + _compressor.Start(); + } + catch // TODO: do not throw errors from constructor? + { + DisposeCore(); + throw; + } + } + + private IColumnsDb CreateRootDb(IDbFactory dbFactory, bool reset) + { + (IColumnsDb root, IDb meta) = CreateDb(); + + if (reset) + return ResetAndCreateNew(root, "Log index: resetting data per configuration..."); + + Span versionBytes = meta.GetSpan(SpecialKey.Version); + try + { + if (versionBytes.IsEmpty) // DB is empty + { + meta.Set(SpecialKey.Version, VersionBytes); + return root; + } + + return versionBytes.SequenceEqual(VersionBytes) + ? root + : ResetAndCreateNew(root, $"Log index: version is incorrect: {versionBytes[0]} <> {VersionBytes[0]}, resetting data..."); + } + finally + { + meta.DangerousReleaseMemory(versionBytes); + } + + IColumnsDb ResetAndCreateNew(IColumnsDb db, string message) + { + if (_logger.IsWarn) + _logger.Warn(message); + + db.Clear(); + + // `Clear` removes the DB folder, need to create a new instance + db.Dispose(); + (db, meta) = CreateDb(); + + meta.Set(SpecialKey.Version, VersionBytes); + return db; + } + + (IColumnsDb root, IDb meta) CreateDb() + { + IColumnsDb db = dbFactory.CreateColumnsDb(new("logIndexStorage", DbNames.LogIndex) + { + ColumnsMergeOperators = Enumerable.Range(-1, MaxTopics + 1).ToDictionary( + topicIndex => $"{GetColumn(topicIndex < 0 ? null : topicIndex)}", + topicIndex => _mergeOperators[topicIndex + 1] + ) + }); + + return (db, GetMetaDb(db)); + } + } + + private CompressionAlgorithm SelectCompressionAlgorithm(string? configAlgoName) + { + CompressionAlgorithm? configAlgo = null; + if (configAlgoName is not null && !CompressionAlgorithm.Supported.TryGetValue(configAlgoName, out configAlgo)) + { + throw new NotSupportedException( + $"Configured compression algorithm ({configAlgoName}) is not supported on this platform." + ); + } + + Span algoBytes = _metaDb.GetSpan(SpecialKey.CompressionAlgo); + string usedAlgoName; + try + { + if (algoBytes.IsEmpty) // DB is empty + { + KeyValuePair selected = configAlgo is not null + ? KeyValuePair.Create(configAlgoName, configAlgo) + : CompressionAlgorithm.Best; + + _metaDb.Set(SpecialKey.CompressionAlgo, Encoding.ASCII.GetBytes(selected.Key)); + return selected.Value; + } + + usedAlgoName = Encoding.ASCII.GetString(algoBytes); + } + finally + { + _metaDb.DangerousReleaseMemory(algoBytes); + } + + if (!CompressionAlgorithm.Supported.TryGetValue(usedAlgoName, out CompressionAlgorithm usedAlgo)) + { + throw new NotSupportedException( + $"Used compression algorithm ({usedAlgoName}) is not supported on this platform. " + + "Log index must be reset to use a different compression algorithm." + ); + } + + configAlgoName ??= usedAlgoName; + if (usedAlgoName != configAlgoName) + { + throw new NotSupportedException( + $"Used compression algorithm ({usedAlgoName}) is different from the one configured ({configAlgoName}). " + + "Log index must be reset to use a different compression algorithm." + ); + } + + return usedAlgo; + } + + private static void ForceMerge(IDb db) + { + // Fetching RocksDB key values forces it to merge corresponding parts + db.GetAllValues().ForEach(static _ => { }); + } + + public Task StopAsync() => StopAsync(acquireLock: true); + + private async Task StopAsync(bool acquireLock) + { + if (Interlocked.Exchange(ref _stopped, true)) + { + return; + } + + if (acquireLock) + { + await _forwardWriteSemaphore.WaitAsync(); + await _backwardWriteSemaphore.WaitAsync(); + } + + try + { + // Disposing RocksDB during any write operation will cause 0xC0000005, so stop them all + await Task.WhenAll( + _compactor.StopAsync(), + _compressor.StopAsync() + ); + + if (_logger.IsInfo) _logger.Info("Log index storage stopped"); + } + finally + { + if (acquireLock) + { + _forwardWriteSemaphore.Release(); + _backwardWriteSemaphore.Release(); + } + } + } + + private void ThrowIfStopped() + { + if (_stopped) + throw new InvalidOperationException("Log index storage is stopped."); + } + + private void OnBackgroundError(Exception error) + { + _lastBackgroundError = error; + + if (_logger.IsError) + _logger.Error($"Error in {typeof(TCaller).Name}", error); + } + + private void ThrowIfHasError() + { + if (_lastBackgroundError is { } error) + ExceptionDispatchInfo.Throw(error); + } + + async ValueTask IAsyncDisposable.DisposeAsync() + { + if (Interlocked.Exchange(ref _disposed, true)) + { + return; + } + + await _forwardWriteSemaphore.WaitAsync(); + await _backwardWriteSemaphore.WaitAsync(); + + await StopAsync(acquireLock: false); + + // No need to free semaphores now + DisposeCore(); + } + + private void DisposeCore() + { + _forwardWriteSemaphore.Dispose(); + _backwardWriteSemaphore.Dispose(); + _compressor?.Dispose(); + _compactor?.Dispose(); + DBColumns?.DisposeItems(); + _rootDb?.Dispose(); + } + + private int? LoadRangeBound(ReadOnlySpan key) + { + Span value = _metaDb.GetSpan(key); + try + { + return !value.IsEmpty ? ReadBlockNumber(value) : null; + } + finally + { + _metaDb.DangerousReleaseMemory(value); + } + } + + private void UpdateRange(int minBlock, int maxBlock, bool isBackwardSync) + { + if (!FirstBlockAdded) + { + using Lock.Scope _ = _rangeInitLock.EnterScope(); // May not be needed, but added for safety + (_minBlock, _maxBlock) = (minBlock, maxBlock); + return; + } + + // Update fields separately for each direction + // so that concurrent different direction sync won't overwrite each other + if (isBackwardSync) _minBlock = minBlock; + else _maxBlock = maxBlock; + } + + private static int SaveRangeBound(IWriteOnlyKeyValueStore dbBatch, byte[] key, int value) + { + Span buffer = stackalloc byte[BlockNumberSize]; + WriteBlockNumber(buffer, value); + dbBatch.PutSpan(key, buffer); + return value; + } + + private (int min, int max) SaveRange(DbBatches batches, int firstBlock, int lastBlock, bool isBackwardSync, bool isReorg = false) + { + int batchMin = Math.Min(firstBlock, lastBlock); + int batchMax = Math.Max(firstBlock, lastBlock); + + int min = _minBlock ?? SaveRangeBound(batches.Meta, SpecialKey.MinBlockNum, batchMin); + int max = _maxBlock ?? SaveRangeBound(batches.Meta, SpecialKey.MaxBlockNum, batchMax); + + if (isBackwardSync) + { + if (isReorg) + throw new ArgumentException("Backwards sync does not support reorgs."); + if (batchMin < _minBlock) + min = SaveRangeBound(batches.Meta, SpecialKey.MinBlockNum, batchMin); + } + else + { + if ((isReorg && batchMax < _maxBlock) || (!isReorg && batchMax > _maxBlock)) + max = SaveRangeBound(batches.Meta, SpecialKey.MaxBlockNum, batchMax); + } + + return (min, max); + } + + private int? GetLastReorgableBlockNumber() => _maxBlock - _maxReorgDepth; + + private static bool IsBlockNewer(int next, int? lastMin, int? lastMax, bool isBackwardSync) => isBackwardSync + ? lastMin is null || next < lastMin + : lastMax is null || next > lastMax; + + private bool IsBlockNewer(int next, bool isBackwardSync) => + IsBlockNewer(next, _minBlock, _maxBlock, isBackwardSync); + + public string GetDbSize() => _rootDb.GatherMetric().Size.SizeToString(useSi: true, addSpace: true); + + public IEnumerator GetEnumerator(Address address, int from, int to) => + GetEnumerator(null, address.Bytes, from, to); + + public IEnumerator GetEnumerator(int topicIndex, Hash256 topic, int from, int to) => + GetEnumerator(topicIndex, topic.BytesToArray(), from, to); + + public IEnumerator GetEnumerator(int? topicIndex, byte[] key, int from, int to) + { + IDb db = GetDb(topicIndex); + ISortedKeyValueStore? sortedDb = db as ISortedKeyValueStore + ?? throw new NotSupportedException($"{db.GetType().Name} DB does not support sorted lookups."); + + return new LogIndexEnumerator(sortedDb, _compressionAlgorithm, key, from, to); + } + + // TODO: discuss potential optimizations + public LogIndexAggregate Aggregate(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats) + { + ThrowIfStopped(); + ThrowIfHasError(); + + if ((!isBackwardSync && !IsSeqAsc(batch)) || (isBackwardSync && !IsSeqDesc(batch))) + throw new ArgumentException($"Unexpected blocks batch order: ({batch[0]} to {batch[^1]})."); + + if (!IsBlockNewer(batch[^1].BlockNumber, isBackwardSync)) + return new(batch); + + long timestamp = Stopwatch.GetTimestamp(); + + LogIndexAggregate aggregate = new(batch); + foreach ((int blockNumber, TxReceipt[] receipts) in batch) + { + if (!IsBlockNewer(blockNumber, isBackwardSync)) + continue; + + stats?.IncrementBlocks(); + stats?.IncrementTx(receipts.Length); + + foreach (TxReceipt receipt in receipts) + { + if (receipt.Logs == null) + continue; + + foreach (LogEntry log in receipt.Logs) + { + stats?.IncrementLogs(); + + List addressBlocks = aggregate.Address.GetOrAdd(log.Address, static _ => new(1)); + + if (addressBlocks.Count == 0 || addressBlocks[^1] != blockNumber) + addressBlocks.Add(blockNumber); + + int topicsLength = Math.Min(log.Topics.Length, MaxTopics); + for (byte topicIndex = 0; topicIndex < topicsLength; topicIndex++) + { + stats?.IncrementTopics(); + + List topicBlocks = aggregate.Topic[topicIndex].GetOrAdd(log.Topics[topicIndex], static _ => new(1)); + + if (topicBlocks.Count == 0 || topicBlocks[^1] != blockNumber) + topicBlocks.Add(blockNumber); + } + } + } + } + + stats?.KeysCount.Include(aggregate.Address.Count + aggregate.TopicCount); + stats?.Aggregating.Include(Stopwatch.GetElapsedTime(timestamp)); + + return aggregate; + } + + private async ValueTask LockRunAsync(SemaphoreSlim semaphore) + { + if (!await semaphore.WaitAsync(TimeSpan.Zero, CancellationToken.None)) + { + ThrowIfStopped(); + throw new InvalidOperationException($"{nameof(LogIndexStorage)} does not support concurrent invocations in the same direction."); + } + } + + public async Task RemoveReorgedAsync(BlockReceipts block) + { + ThrowIfStopped(); + ThrowIfHasError(); + + if (!FirstBlockAdded) + return; + + await LockRunAsync(_forwardWriteSemaphore); + + try + { + RemoveReorgedCore(block); + } + finally + { + _forwardWriteSemaphore.Release(); + } + } + + private void RemoveReorgedCore(BlockReceipts block) + { + const bool isBackwardSync = false; + + using DbBatches batches = new(_rootDb); + + Span keyBuffer = stackalloc byte[MaxDbKeyLength]; + Span dbValue = MergeOps.Create(MergeOp.Reorg, block.BlockNumber, stackalloc byte[MergeOps.Size]); + + foreach (TxReceipt receipt in block.Receipts) + { + foreach (LogEntry log in receipt.Logs ?? []) + { + ReadOnlySpan addressKey = CreateMergeDbKey(log.Address.Bytes, keyBuffer, isBackwardSync: false); + batches.Address.Merge(addressKey, dbValue); + + var topicsLength = Math.Min(log.Topics.Length, MaxTopics); + for (var topicIndex = 0; topicIndex < topicsLength; topicIndex++) + { + Hash256 topic = log.Topics[topicIndex]; + ReadOnlySpan topicKey = CreateMergeDbKey(topic.Bytes, keyBuffer, isBackwardSync: false); + batches.Topics[topicIndex].Merge(topicKey, dbValue); + } + } + } + + // Need to update the last block number so that new-receipts comparison won't fail when rewriting it + int blockNum = block.BlockNumber - 1; + + (int minBlock, int maxBlock) = SaveRange(batches, blockNum, blockNum, isBackwardSync, isReorg: true); + + batches.Commit(); + + // Postpone in-memory values update until batch is committed + UpdateRange(minBlock, maxBlock, isBackwardSync); + } + + // TODO: refactor compaction to explicitly compress full range for each involved key + public async Task CompactAsync(bool flush = false, int mergeIterations = 0, LogIndexUpdateStats? stats = null) + { + ThrowIfStopped(); + ThrowIfHasError(); + + if (_logger.IsInfo) + _logger.Info($"Log index forced compaction started, DB size: {GetDbSize()}"); + + var timestamp = Stopwatch.GetTimestamp(); + + if (flush) + DBColumns.ForEach(static db => db.Flush()); + + for (var i = 0; i < mergeIterations; i++) + { + Task[] tasks = DBColumns + .Select(static db => Task.Run(() => ForceMerge(db))) + .ToArray(); + + await Task.WhenAll(tasks); + await _compressor.WaitUntilEmptyAsync(TimeSpan.FromSeconds(30)); + } + + CompactingStats compactStats = await _compactor.ForceAsync(); + stats?.Compacting.Combine(compactStats); + + foreach (IMergeOperator mergeOperator in _mergeOperators) + stats?.Combine(mergeOperator.Stats); + + if (_logger.IsInfo) + _logger.Info($"Log index forced compaction finished in {Stopwatch.GetElapsedTime(timestamp)}, DB size: {GetDbSize()} {stats:d}"); + } + + public async Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null) + { + ThrowIfStopped(); + ThrowIfHasError(); + + long totalTimestamp = Stopwatch.GetTimestamp(); + + bool isBackwardSync = aggregate.LastBlockNum < aggregate.FirstBlockNum; + SemaphoreSlim semaphore = isBackwardSync ? _backwardWriteSemaphore : _forwardWriteSemaphore; + await LockRunAsync(semaphore); + + bool wasInitialized = FirstBlockAdded; + if (!wasInitialized) + await _initSemaphore.WaitAsync(); + + try + { + using DbBatches batches = new(_rootDb); + + // Add values to batches + long timestamp; + if (!aggregate.IsEmpty) + { + timestamp = Stopwatch.GetTimestamp(); + + // Add addresses + foreach ((Address address, List blocks) in aggregate.Address) + { + MergeBlockNumbers(batches.Address, address.Bytes, blocks, isBackwardSync, stats); + } + + // Add topics + for (var topicIndex = 0; topicIndex < aggregate.Topic.Length; topicIndex++) + { + Dictionary> topics = aggregate.Topic[topicIndex]; + + foreach ((Hash256 topic, List blocks) in topics) + { + MergeBlockNumbers(batches.Topics[topicIndex], topic.Bytes, blocks, isBackwardSync, stats); + } + } + + stats?.Merging.Include(Stopwatch.GetElapsedTime(timestamp)); + } + + timestamp = Stopwatch.GetTimestamp(); + (int addressRange, int topicRanges) = SaveRange(batches, aggregate.FirstBlockNum, aggregate.LastBlockNum, isBackwardSync); + stats?.UpdatingMeta.Include(Stopwatch.GetElapsedTime(timestamp)); + + // Submit batches + timestamp = Stopwatch.GetTimestamp(); + batches.Commit(); + stats?.CommittingBatch.Include(Stopwatch.GetElapsedTime(timestamp)); + + UpdateRange(addressRange, topicRanges, isBackwardSync); + + // Enqueue compaction if needed + _compactor.TryEnqueue(); + } + finally + { + if (!wasInitialized) + _initSemaphore.Release(); + + semaphore.Release(); + } + + foreach (IMergeOperator mergeOperator in _mergeOperators) + stats?.Combine(mergeOperator.GetAndResetStats()); + stats?.Compressing.Combine(_compressor.GetAndResetStats()); + stats?.Compacting.Combine(_compactor.GetAndResetStats()); + stats?.Adding.Include(Stopwatch.GetElapsedTime(totalTimestamp)); + } + + protected virtual void MergeBlockNumbers( + IWriteBatch dbBatch, ReadOnlySpan key, List numbers, + bool isBackwardSync, LogIndexUpdateStats? stats + ) + { + Span dbKeyBuffer = stackalloc byte[MaxDbKeyLength]; + ReadOnlySpan dbKey = CreateMergeDbKey(key, dbKeyBuffer, isBackwardSync); + + byte[] newValue = CreateDbValue(numbers); + + long timestamp = Stopwatch.GetTimestamp(); + + if (newValue is null or []) + throw new LogIndexStateException("No block numbers to save.", key); + + // TODO: consider disabling WAL, but check: + // - FlushOnTooManyWrites + // - atomic flushing + dbBatch.Merge(dbKey, newValue); + stats?.DBMerging.Include(Stopwatch.GetElapsedTime(timestamp)); + } + + private static ReadOnlySpan WriteKey(ReadOnlySpan key, Span buffer) + { + key.CopyTo(buffer); + return buffer[..key.Length]; + } + + private static ReadOnlySpan ExtractKey(ReadOnlySpan dbKey) => dbKey[..^BlockNumberSize]; + + /// + /// Generates a key consisting of the key || block-number byte array. + /// / + private static ReadOnlySpan CreateDbKey(ReadOnlySpan key, int blockNumber, Span buffer) + { + key = WriteKey(key, buffer); + WriteKeyBlockNumber(buffer[key.Length..], blockNumber); + + int length = key.Length + BlockNumberSize; + return buffer[..length]; + } + + /// + /// Generates a key consisting of the key || block-number byte array. + /// / + private static ReadOnlySpan CreateDbKey(ReadOnlySpan key, ReadOnlySpan blockNumber, Span buffer) + { + key = WriteKey(key, buffer); + blockNumber.CopyTo(buffer[key.Length..]); + + int length = key.Length + blockNumber.Length; + return buffer[..length]; + } + + private static ReadOnlySpan CreateMergeDbKey(ReadOnlySpan key, Span buffer, bool isBackwardSync) => + CreateDbKey(key, isBackwardSync ? Postfix.BackwardMerge : Postfix.ForwardMerge, buffer); + + // RocksDB uses big-endian (lexicographic) ordering + // +1 is needed as 0 is used for the backward-merge key + private static void WriteKeyBlockNumber(Span dbKeyEnd, int number) => BinaryPrimitives.WriteInt32BigEndian(dbKeyEnd, number + 1); + + private static bool UseBackwardSyncFor(ReadOnlySpan dbKey) => dbKey.EndsWith(Postfix.BackwardMerge); + + private static int BinarySearch(ReadOnlySpan blocks, int from) + { + int index = blocks.BinarySearch(from); + return index < 0 ? ~index : index; + } + + private ReadOnlySpan Compress(Span data, Span buffer) + { + ReadOnlySpan blockNumbers = MemoryMarshal.Cast(data); + int length = (int)_compressionAlgorithm.Compress(blockNumbers, (nuint)blockNumbers.Length, buffer); + return buffer[..length]; + } + + private static int ReadCompressionMarker(ReadOnlySpan source) => -BinaryPrimitives.ReadInt32LittleEndian(source); + private static void WriteCompressionMarker(Span source, int len) => BinaryPrimitives.WriteInt32LittleEndian(source, -len); + + private static bool IsCompressed(ReadOnlySpan source, out int len) + { + if (source.Length == 0) + { + len = 0; + return false; + } + + len = ReadCompressionMarker(source); + return len > 0; + } + + private static void WriteBlockNumber(Span destination, int number) => BinaryPrimitives.WriteInt32LittleEndian(destination, number); + private static int ReadBlockNumber(ReadOnlySpan source) => BinaryPrimitives.ReadInt32LittleEndian(source); + private static int ReadLastBlockNumber(ReadOnlySpan source) => ReadBlockNumber(source[^BlockNumberSize..]); + + private static void ReadBlockNumbers(ReadOnlySpan source, Span buffer) + { + if (source.Length % BlockNumberSize != 0) + throw new LogIndexStateException("Invalid length for array of block numbers."); + + if (buffer.Length < source.Length / BlockNumberSize) + throw new ArgumentException($"Buffer is too small to hold {source.Length / BlockNumberSize} block numbers.", nameof(buffer)); + + if (BitConverter.IsLittleEndian) + { + ReadOnlySpan sourceInt = MemoryMarshal.Cast(source); + sourceInt.CopyTo(buffer); + } + else + { + for (var i = 0; i < source.Length; i += BlockNumberSize) + buffer[i / BlockNumberSize] = ReadBlockNumber(source[i..]); + } + } + + private static byte[] CreateDbValue(List numbers) + { + byte[] value = new byte[numbers.Count * BlockNumberSize]; + numbers.CopyTo(MemoryMarshal.Cast(value.AsSpan())); + return value; + } + + private static LogIndexColumns GetColumn(int? topicIndex) => topicIndex.HasValue + ? (LogIndexColumns)(topicIndex + LogIndexColumns.Topics0) + : LogIndexColumns.Addresses; + + private IDb GetDb(int? topicIndex) => topicIndex.HasValue ? _topicDbs[topicIndex.Value] : _addressDb; + + private static IDb GetMetaDb(IColumnsDb rootDb) => rootDb.GetColumnDb(LogIndexColumns.Meta); + + private byte[] CompressDbValue(ReadOnlySpan key, Span data) + { + if (IsCompressed(data, out _)) + throw new LogIndexStateException("Attempt to compress already compressed data.", key); + if (data.Length % BlockNumberSize != 0) + throw new LogIndexStateException($"Invalid length of data to compress: {data.Length}.", key); + + byte[] buffer = Pool.Rent(data.Length + BlockNumberSize); + + try + { + WriteCompressionMarker(buffer, data.Length / BlockNumberSize); + int compressedLen = Compress(data, buffer.AsSpan(BlockNumberSize..)).Length; + return buffer[..(BlockNumberSize + compressedLen)]; + } + finally + { + Pool.Return(buffer); + } + } + + private static void DecompressDbValue(CompressionAlgorithm algorithm, ReadOnlySpan data, Span buffer) + { + if (!IsCompressed(data, out int len)) + throw new ValidationException("Data is not compressed"); + + if (buffer.Length < len) + throw new ArgumentException($"Buffer is too small to decompress {len} block numbers.", nameof(buffer)); + + _ = algorithm.Decompress(data[BlockNumberSize..], (nuint)len, buffer); + } + + private void DecompressDbValue(ReadOnlySpan data, Span buffer) => DecompressDbValue(_compressionAlgorithm, data, buffer); + + private Span RemoveReorgableBlocks(Span data) + { + if (GetLastReorgableBlockNumber() is not { } lastCompressBlock) + return Span.Empty; + + int lastCompressIndex = LastBlockSearch(data, lastCompressBlock, false); + + if (lastCompressIndex < 0) lastCompressIndex = 0; + if (lastCompressIndex > data.Length) lastCompressIndex = data.Length; + + return data[..lastCompressIndex]; + } + + private static void ReverseBlocksIfNeeded(Span data) + { + if (data.Length != 0 && ReadBlockNumber(data) > ReadLastBlockNumber(data)) + MemoryMarshal.Cast(data).Reverse(); + } + + private static void ReverseBlocksIfNeeded(Span blocks) + { + if (blocks.Length != 0 && blocks[0] > blocks[^1]) + blocks.Reverse(); + } + + private static int LastBlockSearch(ReadOnlySpan operand, int block, bool isBackward) + { + if (operand.IsEmpty) + return 0; + + int i = operand.Length - BlockNumberSize; + for (; i >= 0; i -= BlockNumberSize) + { + int currentBlock = ReadBlockNumber(operand[i..]); + if (currentBlock == block) + return i; + + if (isBackward) + { + if (currentBlock > block) + return i + BlockNumberSize; + } + else + { + if (currentBlock < block) + return i + BlockNumberSize; + } + } + + return i; + } + + private static bool IsSeqAsc(IReadOnlyList blocks) + { + int j = blocks.Count - 1; + int i = 1, d = blocks[0].BlockNumber; + while (i <= j && blocks[i].BlockNumber - i == d) i++; + return i > j; + } + + private static bool IsSeqDesc(IReadOnlyList blocks) + { + int j = blocks.Count - 1; + int i = 1, d = blocks[0].BlockNumber; + while (i <= j && blocks[i].BlockNumber + i == d) i++; + return i > j; + } + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexUpdateStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexUpdateStats.cs new file mode 100644 index 000000000000..22651564ed8b --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexUpdateStats.cs @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading; + +namespace Nethermind.Db.LogIndex; + +/// +/// Log index building statistics across some time range. +/// +public class LogIndexUpdateStats(ILogIndexStorage storage) : IFormattable +{ + private long _blocksAdded; + private long _txAdded; + private long _logsAdded; + private long _topicsAdded; + + public long BlocksAdded => _blocksAdded; + public long TxAdded => _txAdded; + public long LogsAdded => _logsAdded; + public long TopicsAdded => _topicsAdded; + + public long? MaxBlockNumber => storage.MaxBlockNumber; + public long? MinBlockNumber => storage.MinBlockNumber; + + public ExecTimeStats Adding { get; } = new(); + public ExecTimeStats Aggregating { get; } = new(); + public ExecTimeStats Merging { get; } = new(); + + public ExecTimeStats DBMerging { get; } = new(); + public ExecTimeStats UpdatingMeta { get; } = new(); + public ExecTimeStats CommittingBatch { get; } = new(); + public ExecTimeStats BackgroundMerging { get; } = new(); + + public AverageStats KeysCount { get; } = new(); + + public ExecTimeStats QueueingAddressCompression { get; } = new(); + public ExecTimeStats QueueingTopicCompression { get; } = new(); + + public PostMergeProcessingStats Compressing { get; } = new(); + public CompactingStats Compacting { get; } = new(); + + public ExecTimeStats LoadingReceipts { get; } = new(); + + public void Combine(LogIndexUpdateStats other) + { + _blocksAdded += other._blocksAdded; + _txAdded += other._txAdded; + _logsAdded += other._logsAdded; + _topicsAdded += other._topicsAdded; + + Adding.Combine(other.Adding); + Aggregating.Combine(other.Aggregating); + Merging.Combine(other.Merging); + UpdatingMeta.Combine(other.UpdatingMeta); + DBMerging.Combine(other.DBMerging); + CommittingBatch.Combine(other.CommittingBatch); + BackgroundMerging.Combine(other.BackgroundMerging); + KeysCount.Combine(other.KeysCount); + + QueueingAddressCompression.Combine(other.QueueingAddressCompression); + QueueingTopicCompression.Combine(other.QueueingTopicCompression); + + Compressing.Combine(other.Compressing); + Compacting.Combine(other.Compacting); + + LoadingReceipts.Combine(other.LoadingReceipts); + } + + public void IncrementBlocks() => Interlocked.Increment(ref _blocksAdded); + public void IncrementTx(int count = 1) => Interlocked.Add(ref _txAdded, count); + public void IncrementLogs() => Interlocked.Increment(ref _logsAdded); + public void IncrementTopics() => Interlocked.Increment(ref _topicsAdded); + + public string ToString(string? format, IFormatProvider? formatProvider) + { + const string tab = "\t"; + + return !string.Equals(format, "D", StringComparison.OrdinalIgnoreCase) + ? $"{MinBlockNumber:N0} - {MaxBlockNumber:N0} (blocks: +{BlocksAdded:N0} | txs: +{TxAdded:N0} | logs: +{LogsAdded:N0} | topics: +{TopicsAdded:N0})" + : $""" + + {tab}Blocks: {MinBlockNumber:N0} - {MaxBlockNumber:N0} (+{_blocksAdded:N0}) + + {tab}Txs: +{TxAdded:N0} + {tab}Logs: +{LogsAdded:N0} + {tab}Topics: +{TopicsAdded:N0} + + {tab}Keys per batch: {KeysCount:N0} + + {tab}Loading receipts: {LoadingReceipts} + {tab}Aggregating: {Aggregating} + + {tab}Adding receipts: {Adding} + {tab}{tab}Merging: {Merging} (DB: {DBMerging}) + {tab}{tab}Updating metadata: {UpdatingMeta} + {tab}{tab}Committing batch: {CommittingBatch} + + {tab}Background merging: {BackgroundMerging} + + {tab}Post-merge compression: {Compressing.Total} + {tab}{tab}DB reading: {Compressing.DBReading} + {tab}{tab}Compressing: {Compressing.CompressingValue} + {tab}{tab}DB saving: {Compressing.DBSaving} + {tab}{tab}Keys compressed: {Compressing.CompressedAddressKeys:N0} address, {Compressing.CompressedTopicKeys:N0} topic + {tab}{tab}Keys in queue: {Compressing.QueueLength:N0} + + {tab}Compacting: {Compacting.Total} + """; + } + + public override string ToString() => ToString(null, null); +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/MergeOperator.cs b/src/Nethermind/Nethermind.Db/LogIndex/MergeOperator.cs new file mode 100644 index 000000000000..a9f30b6fe4e7 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/MergeOperator.cs @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Diagnostics; +using System.Threading; +using Nethermind.Core; +using Nethermind.Core.Collections; + +namespace Nethermind.Db.LogIndex; + +partial class LogIndexStorage +{ + // TODO: check if success=false + paranoid_checks=true is better than throwing an exception + /// + /// Merges log index incoming block number sequences into a single DB value. + /// + /// + /// Handles calls from . + /// Operator works only with uncompressed data under "transient" keys.
+ /// Each log index column family has its own merge operator instance, + /// parameter is used to find corresponding DB.
+ /// + /// + /// Supports 2 different use cases depending on the incoming operand: + /// + /// + /// In case if operand is a sequence of block numbers - + /// directly concatenates with the existing DB value, validating order remains correct (see ).
+ /// If value size grows to or over block numbers - + /// queues it for compression (via ). + ///
+ /// + /// If operand from - performs specified operation on the previously obtained sequence. + /// + ///
+ /// Check MergeOperator tests for the expected behavior. + ///
+ ///
+ public class MergeOperator(ILogIndexStorage storage, ICompressor compressor, int? topicIndex) : IMergeOperator + { + private LogIndexUpdateStats _stats = new(storage); + public LogIndexUpdateStats Stats => _stats; + public LogIndexUpdateStats GetAndResetStats() => Interlocked.Exchange(ref _stats, new(storage)); + + public string Name => $"{nameof(LogIndexStorage)}.{nameof(MergeOperator)}"; + + public ArrayPoolList? FullMerge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator) => + Merge(key, enumerator, isPartial: false); + + public ArrayPoolList? PartialMerge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator) => + Merge(key, enumerator, isPartial: true); + + private static bool IsBlockNewer(int next, int? last, bool isBackwardSync) => + LogIndexStorage.IsBlockNewer(next, last, last, isBackwardSync); + + // Validate we are merging non-intersecting segments - to prevent data corruption + private static void AddEnsureSorted(ReadOnlySpan key, ArrayPoolList result, ReadOnlySpan value, bool isBackward) + { + if (value.Length == 0) + return; + + int nextBlock = ReadBlockNumber(value); + int? lastBlock = result.Count > 0 ? ReadLastBlockNumber(result.AsSpan()) : (int?)null; + + if (!IsBlockNewer(next: nextBlock, last: lastBlock, isBackward)) + throw new LogIndexStateException($"Invalid order during merge: {lastBlock} -> {nextBlock} (backward: {isBackward}).", key); + + result.AddRange(value); + } + + // TODO: avoid array copying in case of a single value? + private ArrayPoolList? Merge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator, bool isPartial) + { + var success = false; + ArrayPoolList? result = null; + var timestamp = Stopwatch.GetTimestamp(); + + try + { + // Fast return in case of a single operand + if (!enumerator.HasExistingValue && enumerator.OperandsCount == 1 && !MergeOps.IsAny(enumerator.GetOperand(0))) + return new(enumerator.GetOperand(0)); + + bool isBackwards = UseBackwardSyncFor(key); + + // Calculate total length + var resultLength = enumerator.GetExistingValue().Length; + for (var i = 0; i < enumerator.OperandsCount; i++) + { + ReadOnlySpan operand = enumerator.GetOperand(i); + + if (MergeOps.IsAny(operand)) + { + if (isPartial) + return null; // Notify RocksDB that we can't partially merge custom ops + + continue; + } + + resultLength += operand.Length; + } + + result = new(resultLength); + + // For truncate - just use max/min for all operands + int? truncateAggregate = Aggregate(MergeOp.Truncate, enumerator, isBackwards); + + int iReorg = 0; + for (int i = 0; i < enumerator.TotalCount; i++) + { + Span operand = enumerator.Get(i); + + if (MergeOps.IsAny(operand)) + continue; + + // For reorg - order matters, so we need to always traverse from the current position + iReorg = Math.Max(iReorg, i + 1); + if (FindNext(MergeOp.Reorg, enumerator, ref iReorg) is { } reorgBlock) + operand = MergeOps.ApplyTo(operand, MergeOp.Reorg, reorgBlock, isBackwards); + + if (truncateAggregate is { } truncateBlock) + operand = MergeOps.ApplyTo(operand, MergeOp.Truncate, truncateBlock, isBackwards); + + AddEnsureSorted(key, result, operand, isBackwards); + } + + if (result.Count % BlockNumberSize != 0) + throw new LogIndexStateException($"Invalid data length post-merge: {result.Count}.", key); + + compressor.TryEnqueue(topicIndex, key, result.AsSpan()); + + success = true; + return result; + } + catch (Exception exception) + { + (storage as LogIndexStorage)?.OnBackgroundError(exception); + return null; + } + finally + { + if (!success) result?.Dispose(); + + _stats.BackgroundMerging.Include(Stopwatch.GetElapsedTime(timestamp)); + } + } + + private static int? FindNext(MergeOp op, RocksDbMergeEnumerator enumerator, ref int i) + { + while (i < enumerator.TotalCount && !MergeOps.Is(op, enumerator.Get(i))) + i++; + + return i < enumerator.TotalCount && MergeOps.Is(op, enumerator.Get(i), out int block) + ? block + : null; + } + + private static int? Aggregate(MergeOp op, RocksDbMergeEnumerator enumerator, bool isBackwardSync) + { + int? result = null; + for (var i = 0; i < enumerator.OperandsCount; i++) + { + if (!MergeOps.Is(op, enumerator.GetOperand(i), out var next)) + continue; + + if (result is null || (isBackwardSync && next < result) || (!isBackwardSync && next > result)) + result = next; + } + + return result; + } + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/MergeOps.cs b/src/Nethermind/Nethermind.Db/LogIndex/MergeOps.cs new file mode 100644 index 000000000000..74d494cd5d0f --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/MergeOps.cs @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Db.LogIndex; + +partial class LogIndexStorage +{ + /// + /// Custom operations for . + /// + public enum MergeOp : byte + { + /// + /// Reorgs from the provided block number, + /// removing any numbers starting from it. + /// + /// + /// Added to support "fast" reorgs - without explicitly fetching value from the database. + /// + Reorg = 1, + + /// + /// Truncates data up to the provided block number, + /// removing it and anything coming before. + /// + /// + /// Added to remove numbers already saved in "finalized" keys (via ) + /// from "transient" keys, without the need for a lock (in case of concurrent merges). + /// + Truncate = 2 + } + + /// + /// Helper class to create and parse operations. + /// + public static class MergeOps + { + public const int Size = BlockNumberSize + 1; + + public static bool Is(MergeOp op, ReadOnlySpan operand) => + operand.Length == Size && operand[0] == (byte)op; + + public static bool Is(MergeOp op, ReadOnlySpan operand, out int fromBlock) + { + if (operand.Length == Size && operand[0] == (byte)op) + { + fromBlock = ReadLastBlockNumber(operand); + return true; + } + + fromBlock = 0; + return false; + } + + public static bool IsAny(ReadOnlySpan operand) => + Is(MergeOp.Reorg, operand, out _) || + Is(MergeOp.Truncate, operand, out _); + + public static Span Create(MergeOp op, int fromBlock, Span buffer) + { + Span dbValue = buffer[..Size]; + dbValue[0] = (byte)op; + WriteBlockNumber(dbValue[1..], fromBlock); + return dbValue; + } + + public static Span ApplyTo(Span operand, MergeOp op, int block, bool isBackward) + { + // In most cases the searched block will be near or at the end of the operand, if present there + int i = LastBlockSearch(operand, block, isBackward); + + return op switch + { + MergeOp.Reorg => i switch + { + < 0 => Span.Empty, + _ when i >= operand.Length => operand, + _ => operand[..i] + }, + + MergeOp.Truncate => i switch + { + < 0 => operand, + _ when i >= operand.Length => Span.Empty, + _ => operand[(i + BlockNumberSize)..] + }, + + _ => throw new ArgumentOutOfRangeException(nameof(op)) + }; + } + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/PostMergeProcessingStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/PostMergeProcessingStats.cs new file mode 100644 index 000000000000..312056e88054 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/PostMergeProcessingStats.cs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Db.LogIndex; + +public class PostMergeProcessingStats +{ + public ExecTimeStats DBReading { get; set; } = new(); + public ExecTimeStats CompressingValue { get; set; } = new(); + public ExecTimeStats DBSaving { get; set; } = new(); + public int QueueLength { get; set; } + + public long CompressedAddressKeys; + public long CompressedTopicKeys; + + public ExecTimeStats Total { get; } = new(); + + public void Combine(PostMergeProcessingStats other) + { + DBReading.Combine(other.DBReading); + CompressingValue.Combine(other.CompressingValue); + DBSaving.Combine(other.DBSaving); + + CompressedAddressKeys += other.CompressedAddressKeys; + CompressedTopicKeys += other.CompressedTopicKeys; + + Total.Combine(other.Total); + } +} diff --git a/src/Nethermind/Nethermind.Db/MemColumnsDb.cs b/src/Nethermind/Nethermind.Db/MemColumnsDb.cs index ea37042e51b2..02fd19178ca1 100644 --- a/src/Nethermind/Nethermind.Db/MemColumnsDb.cs +++ b/src/Nethermind/Nethermind.Db/MemColumnsDb.cs @@ -38,6 +38,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 af6f4313e070..e68055560f3c 100644 --- a/src/Nethermind/Nethermind.Db/MemDb.cs +++ b/src/Nethermind/Nethermind.Db/MemDb.cs @@ -5,6 +5,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using Nethermind.Core; using Nethermind.Core.Extensions; @@ -18,8 +20,13 @@ public class MemDb : IFullDb public long ReadsCount { get; private set; } public long WritesCount { get; private set; } - private readonly ConcurrentDictionary _db; +#if ZK + private readonly Dictionary _db = new(Bytes.EqualityComparer); + private readonly Dictionary.AlternateLookup> _spanDb; +#else + private readonly ConcurrentDictionary _db = new(Bytes.EqualityComparer); private readonly ConcurrentDictionary.AlternateLookup> _spanDb; +#endif public MemDb(string name) : this(0, 0) @@ -29,7 +36,7 @@ public MemDb(string name) public static MemDb CopyFrom(IDb anotherDb) { - MemDb newDb = new MemDb(); + MemDb newDb = new(); foreach (KeyValuePair kv in anotherDb.GetAll()) { newDb[kv.Key] = kv.Value; @@ -46,22 +53,15 @@ public MemDb(int writeDelay, int readDelay) { _writeDelay = writeDelay; _readDelay = readDelay; - _db = new ConcurrentDictionary(Bytes.EqualityComparer); _spanDb = _db.GetAlternateLookup>(); } - public string Name { get; } + public string Name { get; } = nameof(MemDb); public virtual byte[]? this[ReadOnlySpan key] { - get - { - return Get(key); - } - set - { - Set(key, value); - } + get => Get(key); + set => Set(key, value); } public KeyValuePair[] this[byte[][] keys] @@ -74,28 +74,17 @@ public virtual byte[]? this[ReadOnlySpan key] } ReadsCount += keys.Length; - return keys.Select(k => new KeyValuePair(k, _db.TryGetValue(k, out var value) ? value : null)).ToArray(); + return keys.Select(k => new KeyValuePair(k, _db.GetValueOrDefault(k))).ToArray(); } } - public virtual void Remove(ReadOnlySpan key) - { - _spanDb.TryRemove(key, out _); - } + public virtual void Remove(ReadOnlySpan key) => _spanDb.TryRemove(key, out _); - 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 +92,18 @@ 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 void DangerousReleaseMemory(in ReadOnlySpan span) - { - } + public unsafe void DangerousReleaseMemory(in ReadOnlySpan span) { } public virtual byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) { @@ -144,6 +116,9 @@ public void DangerousReleaseMemory(in ReadOnlySpan span) return _spanDb.TryGetValue(key, out byte[] value) ? value : null; } + public unsafe Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + => Get(key).AsSpan(); + public virtual void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { if (_writeDelay > 0) @@ -154,19 +129,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 b4889866ab92..78fbab96ae58 100644 --- a/src/Nethermind/Nethermind.Db/Metrics.cs +++ b/src/Nethermind/Nethermind.Db/Metrics.cs @@ -5,6 +5,7 @@ using System.Runtime.CompilerServices; using System.Threading; using Nethermind.Core.Attributes; +using Nethermind.Core.Metric; using Nethermind.Core.Threading; [assembly: InternalsVisibleTo("Nethermind.Consensus")] @@ -16,6 +17,9 @@ namespace Nethermind.Db { public static class Metrics { + [DetailedMetricOnFlag] + public static bool DetailedMetricsEnabled { get; set; } + [CounterMetric] [Description("Number of State Trie cache hits.")] public static long StateTreeCache => _stateTreeCacheHits.GetTotalValue(); @@ -85,7 +89,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] @@ -125,5 +129,15 @@ public static class Metrics [Description("Metrics extracted from RocksDB Compaction Stats")] [KeyIsLabel("db", "level", "metric")] public static NonBlocking.ConcurrentDictionary<(string, int, string), double> DbCompactionStats { get; } = new(); + + [DetailedMetric] + [Description("Prewarmer get operation times")] + [ExponentialPowerHistogramMetric(Start = 10, Factor = 1.5, Count = 30, LabelNames = ["part", "is_prewarmer"])] + public static IMetricObserver PrewarmerGetTime { get; set; } = NoopMetricObserver.Instance; + } + + public readonly struct PrewarmerGetTimeLabel(string part, bool isPrewarmer) : IMetricLabels + { + public string[] Labels { get; } = [part, isPrewarmer ? "true" : "false"]; } } diff --git a/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj b/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj index 04ad5b8b916b..f8a963c6bf31 100644 --- a/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj +++ b/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Nethermind/Nethermind.Db/PruningConfig.cs b/src/Nethermind/Nethermind.Db/PruningConfig.cs index ffe5b5b4ba80..d02de3caa1c2 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; @@ -63,5 +63,7 @@ public int DirtyNodeShardBit public long MaxUnpersistedBlockCount { get; set; } = 300; // About 1 hour on mainnet 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 3b7bafd750d1..0c8483d09e77 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 ff73f260a3a5..98a7d045220f 100644 --- a/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs +++ b/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using Nethermind.Core; +using Nethermind.Core.Buffers; namespace Nethermind.Db { @@ -12,17 +14,12 @@ public class ReadOnlyDb(IDb wrappedDb, bool createInMemWriteStore) : IReadOnlyDb { private readonly MemDb _memDb = new(); - public void Dispose() - { - _memDb.Dispose(); - } + public void Dispose() => _memDb.Dispose(); public string Name { get => wrappedDb.Name; } - public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - { - return _memDb.Get(key, flags) ?? wrappedDb.Get(key, flags); - } + public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => + _memDb.Get(key, flags) ?? wrappedDb.Get(key, flags); public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { @@ -38,11 +35,11 @@ public KeyValuePair[] this[byte[][] keys] { get { - var result = wrappedDb[keys]; - var memResult = _memDb[keys]; + KeyValuePair[]? result = wrappedDb[keys]; + KeyValuePair[]? memResult = _memDb[keys]; for (int i = 0; i < memResult.Length; i++) { - var memValue = memResult[i]; + KeyValuePair memValue = memResult[i]; if (memValue.Value is not null) { result[i] = memValue; @@ -61,7 +58,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) { } @@ -73,7 +70,6 @@ public void Flush(bool onlyWal) { } public virtual void ClearTempChanges() => _memDb.Clear(); - public Span GetSpan(ReadOnlySpan key) => Get(key).AsSpan(); public void PutSpan(ReadOnlySpan keyBytes, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) { if (!createInMemWriteStore) diff --git a/src/Nethermind/Nethermind.Db/RocksDbMergeEnumerator.cs b/src/Nethermind/Nethermind.Db/RocksDbMergeEnumerator.cs new file mode 100644 index 000000000000..fa2da2060b30 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/RocksDbMergeEnumerator.cs @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Db; + +/// +/// RocksDB enumerator for values of merge operation. +/// +// Interface was not used because of ref struct limitations. +public readonly ref struct RocksDbMergeEnumerator(ReadOnlySpan operandsList, ReadOnlySpan operandsListLength) +{ + private readonly ReadOnlySpan _operandsList = operandsList; + private readonly ReadOnlySpan _operandsListLength = operandsListLength; + + public Span ExistingValue { get; } + public bool HasExistingValue { get; } + public int OperandsCount => _operandsList.Length; + public int TotalCount => OperandsCount + (HasExistingValue ? 1 : 0); + + public RocksDbMergeEnumerator( + Span existingValue, bool hasExistingValue, + ReadOnlySpan operandsList, ReadOnlySpan operandsListLength + ) : this(operandsList, operandsListLength) + { + ExistingValue = existingValue; + HasExistingValue = hasExistingValue; + } + + public Span GetExistingValue() + { + return HasExistingValue ? ExistingValue : default; + } + + public unsafe Span GetOperand(int index) + { + return new((void*)_operandsList[index], (int)_operandsListLength[index]); + } + + public Span Get(int index) + { + if (index == 0 && HasExistingValue) + return ExistingValue; + + if (HasExistingValue) + index -= 1; + + return GetOperand(index); + } +} diff --git a/src/Nethermind/Nethermind.Db/RocksDbSettings.cs b/src/Nethermind/Nethermind.Db/RocksDbSettings.cs index f3d9eebd4e67..2c7feceed718 100644 --- a/src/Nethermind/Nethermind.Db/RocksDbSettings.cs +++ b/src/Nethermind/Nethermind.Db/RocksDbSettings.cs @@ -1,22 +1,21 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Collections.Generic; + namespace Nethermind.Db { - public class DbSettings + public class DbSettings(string name, string path) { - public DbSettings(string name, string path) - { - DbName = name; - DbPath = path; - } - - public string DbName { get; private set; } - public string DbPath { get; private set; } + public string DbName { get; private set; } = name; + public string DbPath { get; private set; } = path; public bool DeleteOnStart { get; set; } public bool CanDeleteFolder { get; set; } = true; + public IMergeOperator? MergeOperator { get; set; } + public Dictionary? ColumnsMergeOperators { get; set; } + public DbSettings Clone(string name, string path) { DbSettings settings = (DbSettings)MemberwiseClone(); diff --git a/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs b/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs index 979ff10526d7..2c9a273eb2b7 100644 --- a/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs +++ b/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -23,12 +24,11 @@ public class SimpleFilePublicKeyDb : IFullDb private readonly ILogger _logger; private bool _hasPendingChanges; - private ConcurrentDictionary _cache; - private ConcurrentDictionary.AlternateLookup> _cacheSpan; + private readonly ConcurrentDictionary _cache = new(Bytes.EqualityComparer); + private readonly ConcurrentDictionary.AlternateLookup> _cacheSpan; - public string DbPath { get; } + private string DbPath { get; } public string Name { get; } - public string Description { get; } public ICollection Keys => _cache.Keys.ToArray(); public ICollection Values => _cache.Values; @@ -40,26 +40,27 @@ public SimpleFilePublicKeyDb(string name, string dbDirectoryPath, ILogManager lo ArgumentNullException.ThrowIfNull(dbDirectoryPath); Name = name ?? throw new ArgumentNullException(nameof(name)); DbPath = Path.Combine(dbDirectoryPath, DbFileName); - Description = $"{Name}|{DbPath}"; if (!Directory.Exists(dbDirectoryPath)) { Directory.CreateDirectory(dbDirectoryPath); } - LoadData(); + _cacheSpan = _cache.GetAlternateLookup>(); + + if (File.Exists(DbPath)) + { + LoadData(); + } } public byte[]? this[ReadOnlySpan key] { - get => Get(key, ReadFlags.None); - set => Set(key, value, WriteFlags.None); + get => Get(key); + set => Set(key, value); } - public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - { - return _cacheSpan[key]; - } + public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => _cacheSpan[key]; public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { @@ -72,16 +73,7 @@ public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteF return; } - bool setValue = true; - if (_cacheSpan.TryGetValue(key, out var existingValue)) - { - if (!Bytes.AreEqual(existingValue, value)) - { - setValue = false; - } - } - - if (setValue) + if (!_cacheSpan.TryGetValue(key, out var existingValue) || !Bytes.AreEqual(existingValue, value)) { _cacheSpan[key] = value; _hasPendingChanges = true; @@ -98,10 +90,7 @@ public void Remove(ReadOnlySpan key) } } - public bool KeyExists(ReadOnlySpan key) - { - return _cacheSpan.ContainsKey(key); - } + public bool KeyExists(ReadOnlySpan key) => _cacheSpan.ContainsKey(key); public void Flush(bool onlyWal = false) { } @@ -117,10 +106,7 @@ public void Clear() public IEnumerable GetAllValues(bool ordered = false) => _cache.Values; - public IWriteBatch StartWriteBatch() - { - return this.LikeABatch(CommitBatch); - } + public IWriteBatch StartWriteBatch() => this.LikeABatch(CommitBatch); private void CommitBatch() { @@ -219,14 +205,6 @@ private void LoadData() { const int maxLineLength = 2048; - _cache = new ConcurrentDictionary(Bytes.EqualityComparer); - _cacheSpan = _cache.GetAlternateLookup>(); - - if (!File.Exists(DbPath)) - { - return; - } - using SafeFileHandle fileHandle = File.OpenHandle(DbPath, FileMode.OpenOrCreate); using var handle = ArrayPoolDisposableReturn.Rent(maxLineLength, out byte[] rentedBuffer); @@ -240,7 +218,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; @@ -306,24 +284,6 @@ void RecordError(Span data) } } - private byte[] Update(byte[] oldValue, byte[] newValue) - { - if (!Bytes.AreEqual(oldValue, newValue)) - { - _hasPendingChanges = true; - } - - return newValue; - } - - private byte[] Add(byte[] value) - { - _hasPendingChanges = true; - return value; - } - - public void Dispose() - { - } + public void Dispose() { } } } diff --git a/src/Nethermind/Nethermind.Era.Test/testdata/holesky/holesky-00000-a3bfd81f.era1 b/src/Nethermind/Nethermind.Era.Test/testdata/holesky/holesky-00000-a3bfd81f.era1 deleted file mode 100644 index b0bff516105a..000000000000 Binary files a/src/Nethermind/Nethermind.Era.Test/testdata/holesky/holesky-00000-a3bfd81f.era1 and /dev/null differ diff --git a/src/Nethermind/Nethermind.Era.Test/testdata/holesky/holesky-00001-e343db12.era1 b/src/Nethermind/Nethermind.Era.Test/testdata/holesky/holesky-00001-e343db12.era1 deleted file mode 100644 index 45d4919e23b3..000000000000 Binary files a/src/Nethermind/Nethermind.Era.Test/testdata/holesky/holesky-00001-e343db12.era1 and /dev/null differ diff --git a/src/Nethermind/Nethermind.Era.Test/testdata/holesky/holesky-00002-5a3ecb1a.era1 b/src/Nethermind/Nethermind.Era.Test/testdata/holesky/holesky-00002-5a3ecb1a.era1 deleted file mode 100644 index f38c19c7cc74..000000000000 Binary files a/src/Nethermind/Nethermind.Era.Test/testdata/holesky/holesky-00002-5a3ecb1a.era1 and /dev/null differ diff --git a/src/Nethermind/Nethermind.Era.Test/AccumulatorCalculatorTests.cs b/src/Nethermind/Nethermind.Era1.Test/AccumulatorCalculatorTests.cs similarity index 88% rename from src/Nethermind/Nethermind.Era.Test/AccumulatorCalculatorTests.cs rename to src/Nethermind/Nethermind.Era1.Test/AccumulatorCalculatorTests.cs index 48bcc5055a1a..831edfc5ff18 100644 --- a/src/Nethermind/Nethermind.Era.Test/AccumulatorCalculatorTests.cs +++ b/src/Nethermind/Nethermind.Era1.Test/AccumulatorCalculatorTests.cs @@ -1,16 +1,10 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Runtime.Intrinsics.Arm; -using System.Text; -using System.Threading.Tasks; using Nethermind.Core.Crypto; namespace Nethermind.Era1.Test; + public class AccumulatorCalculatorTests { [Test] diff --git a/src/Nethermind/Nethermind.Era.Test/AdminEraServiceTests.cs b/src/Nethermind/Nethermind.Era1.Test/AdminEraServiceTests.cs similarity index 98% rename from src/Nethermind/Nethermind.Era.Test/AdminEraServiceTests.cs rename to src/Nethermind/Nethermind.Era1.Test/AdminEraServiceTests.cs index 7a0f245b705b..461f0ff4a511 100644 --- a/src/Nethermind/Nethermind.Era.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.Era.Test/E2StoreWriterTests.cs b/src/Nethermind/Nethermind.Era1.Test/E2StoreWriterTests.cs similarity index 99% rename from src/Nethermind/Nethermind.Era.Test/E2StoreWriterTests.cs rename to src/Nethermind/Nethermind.Era1.Test/E2StoreWriterTests.cs index 303e4c2ad265..9be200e2fb9c 100644 --- a/src/Nethermind/Nethermind.Era.Test/E2StoreWriterTests.cs +++ b/src/Nethermind/Nethermind.Era1.Test/E2StoreWriterTests.cs @@ -6,6 +6,7 @@ using Snappier; namespace Nethermind.Era1.Test; + internal class E2StoreWriterTests { byte[] TestBytes = new byte[] { 0x0f, 0xf0, 0xff, 0xff }; @@ -42,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.Era.Test/Era1ModuleTests.cs b/src/Nethermind/Nethermind.Era1.Test/Era1ModuleTests.cs similarity index 94% rename from src/Nethermind/Nethermind.Era.Test/Era1ModuleTests.cs rename to src/Nethermind/Nethermind.Era1.Test/Era1ModuleTests.cs index 5af77c2f1465..0dfb24a1d471 100644 --- a/src/Nethermind/Nethermind.Era.Test/Era1ModuleTests.cs +++ b/src/Nethermind/Nethermind.Era1.Test/Era1ModuleTests.cs @@ -23,6 +23,7 @@ using NSubstitute; namespace Nethermind.Era1.Test; + public class Era1ModuleTests { [Test] @@ -73,7 +74,6 @@ public async Task ExportAndImportTwoBlocksAndReceipts() Assert.That(importedBlock1.TotalDifficulty, Is.EqualTo(BlockHeaderBuilder.DefaultDifficulty)); } - [TestCase("holesky")] [TestCase("mainnet")] public async Task ImportAndExportGethFiles(string network) { @@ -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; @@ -221,7 +216,7 @@ public async Task TestBigBlocksExportImportHistory() TestBlockchain testBlockchain = await BasicTestBlockchain.Create(configurer: builder => { builder.AddScoped((worldState, specProvider) => - new FunctionalGenesisPostProcessor(worldState, (block, state) => + new FunctionalGenesisPostProcessor((block) => { worldState.AddToBalance(TestItem.AddressA, 10.Ether(), specProvider.GenesisSpec); worldState.RecalculateStateRoot(); @@ -295,10 +290,11 @@ public async Task TestBigBlocksExportImportHistory() [TestCase(true, 0, 0, 0, null, 0)] [TestCase(false, 0, 0, 0, 1, 9999)] [TestCase(false, 0, 0, 2000, 2001, 9999)] - public async Task EraExportAndImport(bool fastSync, long start, long end, long headBlockNumber, long? expectedMinSuggestedBlock, long expectedMaxSuggestedBlock) + [CancelAfter(10000)] + public async Task EraExportAndImport(bool fastSync, long start, long end, long headBlockNumber, long? expectedMinSuggestedBlock, long expectedMaxSuggestedBlock, CancellationToken cancellationToken) { const int ChainLength = 10000; - await using IContainer outCtx = await EraTestModule.CreateExportedEraEnv(ChainLength); + await using IContainer outCtx = await EraTestModule.CreateExportedEraEnvWithCompleteBlockBuilder(ChainLength, cancellationToken: cancellationToken); string tmpDir = outCtx.ResolveTempDirPath(); IBlockTree outTree = outCtx.Resolve(); @@ -313,7 +309,8 @@ public async Task EraExportAndImport(bool fastSync, long start, long end, long h inTree.UpdateMainChain(new[] { headBlock }, true); } - await using IContainer inCtx = EraTestModule.BuildContainerBuilder() + await using IContainer inCtx = new ContainerBuilder() + .AddModule(new EraTestModule(useRealValidator: true)) .AddSingleton(inTree) .AddSingleton(new SyncConfig() { diff --git a/src/Nethermind/Nethermind.Era.Test/EraCliRunnerTests.cs b/src/Nethermind/Nethermind.Era1.Test/EraCliRunnerTests.cs similarity index 98% rename from src/Nethermind/Nethermind.Era.Test/EraCliRunnerTests.cs rename to src/Nethermind/Nethermind.Era1.Test/EraCliRunnerTests.cs index fa621df9e8c3..9f84c3a38670 100644 --- a/src/Nethermind/Nethermind.Era.Test/EraCliRunnerTests.cs +++ b/src/Nethermind/Nethermind.Era1.Test/EraCliRunnerTests.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Nethermind.Config; using Nethermind.Logging; using NSubstitute; diff --git a/src/Nethermind/Nethermind.Era.Test/EraExporterTests.cs b/src/Nethermind/Nethermind.Era1.Test/EraExporterTests.cs similarity index 99% rename from src/Nethermind/Nethermind.Era.Test/EraExporterTests.cs rename to src/Nethermind/Nethermind.Era1.Test/EraExporterTests.cs index 6b5396f2d705..76adff8bb4ad 100644 --- a/src/Nethermind/Nethermind.Era.Test/EraExporterTests.cs +++ b/src/Nethermind/Nethermind.Era1.Test/EraExporterTests.cs @@ -9,6 +9,7 @@ using Autofac; namespace Nethermind.Era1.Test; + public class EraExporterTests { [TestCase(1, 0, 1 - 1, 1, 1)] diff --git a/src/Nethermind/Nethermind.Era.Test/EraImporterTest.cs b/src/Nethermind/Nethermind.Era1.Test/EraImporterTest.cs similarity index 98% rename from src/Nethermind/Nethermind.Era.Test/EraImporterTest.cs rename to src/Nethermind/Nethermind.Era1.Test/EraImporterTest.cs index a59b2b40aad9..c72456878f8d 100644 --- a/src/Nethermind/Nethermind.Era.Test/EraImporterTest.cs +++ b/src/Nethermind/Nethermind.Era1.Test/EraImporterTest.cs @@ -12,6 +12,7 @@ using Nethermind.Era1.Exceptions; namespace Nethermind.Era1.Test; + public class EraImporterTest { [Test] @@ -135,7 +136,8 @@ public async Task VerifyEraFiles_ModifiedChecksum_ThrowEraVerificationException( Assert.That(importTask, Throws.TypeOf()); } - [CancelAfter(2000)] + [CancelAfter(4000)] + [Retry(3)] [Test] public async Task ImportAsArchiveSync_WillPaceSuggestBlock(CancellationToken token) { @@ -156,7 +158,7 @@ public async Task ImportAsArchiveSync_WillPaceSuggestBlock(CancellationToken tok }) .Build(); - ManualResetEventSlim reachedBlock11 = new ManualResetEventSlim(); + ManualResetEventSlim reachedBlock11 = new(); bool shouldUpdateMainChain = false; long maxSuggestedBlocks = 0; long expectedStopBlock = 10; diff --git a/src/Nethermind/Nethermind.Era.Test/EraPathUtilsTests.cs b/src/Nethermind/Nethermind.Era1.Test/EraPathUtilsTests.cs similarity index 100% rename from src/Nethermind/Nethermind.Era.Test/EraPathUtilsTests.cs rename to src/Nethermind/Nethermind.Era1.Test/EraPathUtilsTests.cs diff --git a/src/Nethermind/Nethermind.Era.Test/EraReaderTests.cs b/src/Nethermind/Nethermind.Era1.Test/EraReaderTests.cs similarity index 95% rename from src/Nethermind/Nethermind.Era.Test/EraReaderTests.cs rename to src/Nethermind/Nethermind.Era1.Test/EraReaderTests.cs index 9369bd48693b..dd0ab4f416f6 100644 --- a/src/Nethermind/Nethermind.Era.Test/EraReaderTests.cs +++ b/src/Nethermind/Nethermind.Era1.Test/EraReaderTests.cs @@ -1,27 +1,20 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using FluentAssertions; -using Microsoft.ClearScript.JavaScript; using Nethermind.Blockchain.Receipts; using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Core.Test.IO; -using Nethermind.Int256; using Nethermind.Serialization.Rlp; using Nethermind.Specs; using NSubstitute; namespace Nethermind.Era1.Test; + internal class EraReaderTests { private class PopulatedTestFile : IDisposable diff --git a/src/Nethermind/Nethermind.Era.Test/EraStoreTests.cs b/src/Nethermind/Nethermind.Era1.Test/EraStoreTests.cs similarity index 96% rename from src/Nethermind/Nethermind.Era.Test/EraStoreTests.cs rename to src/Nethermind/Nethermind.Era1.Test/EraStoreTests.cs index a908e40e5366..d6a0686e157e 100644 --- a/src/Nethermind/Nethermind.Era.Test/EraStoreTests.cs +++ b/src/Nethermind/Nethermind.Era1.Test/EraStoreTests.cs @@ -3,7 +3,6 @@ using Autofac; using FluentAssertions; -using Nethermind.Core.Test.IO; namespace Nethermind.Era1.Test; diff --git a/src/Nethermind/Nethermind.Era.Test/EraTestModule.cs b/src/Nethermind/Nethermind.Era1.Test/EraTestModule.cs similarity index 65% rename from src/Nethermind/Nethermind.Era.Test/EraTestModule.cs rename to src/Nethermind/Nethermind.Era1.Test/EraTestModule.cs index 9352cb35ce6a..cccf69a24af3 100644 --- a/src/Nethermind/Nethermind.Era.Test/EraTestModule.cs +++ b/src/Nethermind/Nethermind.Era1.Test/EraTestModule.cs @@ -5,16 +5,16 @@ using Autofac; using Nethermind.Blockchain; using Nethermind.Blockchain.Receipts; -using Nethermind.Config; using Nethermind.Consensus.Validators; using Nethermind.Core; +using Nethermind.Core.Test.Blockchain; using Nethermind.Core.Test.Builders; using Nethermind.Core.Test.IO; using Nethermind.Core.Test.Modules; namespace Nethermind.Era1.Test; -public class EraTestModule : Module +public class EraTestModule(bool useRealValidator = false) : Module { public const string TestNetwork = "abc"; @@ -38,6 +38,24 @@ public static ContainerBuilder BuildContainerBuilderWithBlockTreeOfLength(int le }); } + public static async Task CreateExportedEraEnvWithCompleteBlockBuilder(int chainLength = 512, int start = 0, int end = 0, CancellationToken cancellationToken = default) + { + IContainer testCtx = new ContainerBuilder() + .AddModule(new EraTestModule(useRealValidator: true)) + .Build(); + + await testCtx.Resolve().StartBlockProcessing(cancellationToken); + + var util = testCtx.Resolve(); + for (int i = 0; i < chainLength - 1; i++) + { + await util.AddBlock(cancellationToken); + } + + await testCtx.Resolve().Export(testCtx.ResolveTempDirPath(), start, end); + return testCtx; + } + public static async Task CreateExportedEraEnv(int chainLength = 512, int start = 0, int end = 0) { IContainer testCtx = BuildContainerBuilderWithBlockTreeOfLength(chainLength).Build(); @@ -50,16 +68,21 @@ protected override void Load(ContainerBuilder builder) base.Load(builder); builder - .AddModule(new TestNethermindModule(new ConfigProvider())) + .AddModule(TestNethermindModule.CreateWithRealChainSpec()) .AddModule(new EraModule()) - .AddSingleton(Always.Valid) .AddSingleton(new FileSystem()) // Run on real filesystem. .AddSingleton(new EraConfig() { MaxEra1Size = 16, NetworkName = TestNetwork, }) + .AddSingleton(ManualTimestamper.PreMerge) .AddKeyedSingleton("file", ctx => TempPath.GetTempFile()) .AddKeyedSingleton("directory", ctx => TempPath.GetTempDirectory()); + + if (!useRealValidator) + { + builder.AddSingleton(Always.Valid); + } } } diff --git a/src/Nethermind/Nethermind.Era.Test/EraWriterTests.cs b/src/Nethermind/Nethermind.Era1.Test/EraWriterTests.cs similarity index 99% rename from src/Nethermind/Nethermind.Era.Test/EraWriterTests.cs rename to src/Nethermind/Nethermind.Era1.Test/EraWriterTests.cs index c6af556ef516..c455f225a423 100644 --- a/src/Nethermind/Nethermind.Era.Test/EraWriterTests.cs +++ b/src/Nethermind/Nethermind.Era1.Test/EraWriterTests.cs @@ -6,11 +6,11 @@ using Nethermind.Core; using Nethermind.Core.Test.Builders; using NSubstitute; -using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Core.Test.IO; namespace Nethermind.Era1.Test; + internal class EraWriterTests { [Test] diff --git a/src/Nethermind/Nethermind.Era.Test/GlobalUsings.cs b/src/Nethermind/Nethermind.Era1.Test/GlobalUsings.cs similarity index 100% rename from src/Nethermind/Nethermind.Era.Test/GlobalUsings.cs rename to src/Nethermind/Nethermind.Era1.Test/GlobalUsings.cs diff --git a/src/Nethermind/Nethermind.Era.Test/Nethermind.Era1.Test.csproj b/src/Nethermind/Nethermind.Era1.Test/Nethermind.Era1.Test.csproj similarity index 72% rename from src/Nethermind/Nethermind.Era.Test/Nethermind.Era1.Test.csproj rename to src/Nethermind/Nethermind.Era1.Test/Nethermind.Era1.Test.csproj index ff61ad75ccab..d8eb03e51131 100644 --- a/src/Nethermind/Nethermind.Era.Test/Nethermind.Era1.Test.csproj +++ b/src/Nethermind/Nethermind.Era1.Test/Nethermind.Era1.Test.csproj @@ -20,15 +20,6 @@ - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - PreserveNewest diff --git a/src/Nethermind/Nethermind.Era.Test/TestContainerExtensions.cs b/src/Nethermind/Nethermind.Era1.Test/TestContainerExtensions.cs similarity index 100% rename from src/Nethermind/Nethermind.Era.Test/TestContainerExtensions.cs rename to src/Nethermind/Nethermind.Era1.Test/TestContainerExtensions.cs diff --git a/src/Nethermind/Nethermind.Era.Test/testdata/mainnet/mainnet-00000-5ec1ffb8.era1 b/src/Nethermind/Nethermind.Era1.Test/testdata/mainnet/mainnet-00000-5ec1ffb8.era1 similarity index 100% rename from src/Nethermind/Nethermind.Era.Test/testdata/mainnet/mainnet-00000-5ec1ffb8.era1 rename to src/Nethermind/Nethermind.Era1.Test/testdata/mainnet/mainnet-00000-5ec1ffb8.era1 diff --git a/src/Nethermind/Nethermind.Era.Test/testdata/mainnet/mainnet-00001-a5364e9a.era1 b/src/Nethermind/Nethermind.Era1.Test/testdata/mainnet/mainnet-00001-a5364e9a.era1 similarity index 100% rename from src/Nethermind/Nethermind.Era.Test/testdata/mainnet/mainnet-00001-a5364e9a.era1 rename to src/Nethermind/Nethermind.Era1.Test/testdata/mainnet/mainnet-00001-a5364e9a.era1 diff --git a/src/Nethermind/Nethermind.Era.Test/testdata/mainnet/mainnet-00002-98cbd8a9.era1 b/src/Nethermind/Nethermind.Era1.Test/testdata/mainnet/mainnet-00002-98cbd8a9.era1 similarity index 100% rename from src/Nethermind/Nethermind.Era.Test/testdata/mainnet/mainnet-00002-98cbd8a9.era1 rename to src/Nethermind/Nethermind.Era1.Test/testdata/mainnet/mainnet-00002-98cbd8a9.era1 diff --git a/src/Nethermind/Nethermind.Era1/AccumulatorCalculator.cs b/src/Nethermind/Nethermind.Era1/AccumulatorCalculator.cs index c97fd19173f3..8be42473da51 100644 --- a/src/Nethermind/Nethermind.Era1/AccumulatorCalculator.cs +++ b/src/Nethermind/Nethermind.Era1/AccumulatorCalculator.cs @@ -12,16 +12,11 @@ namespace Nethermind.Era1; // https://github.com/ethereum/portal-network-specs/blob/master/history/history-network.md#algorithms internal class AccumulatorCalculator : IDisposable { - ArrayPoolList> _roots; - - public AccumulatorCalculator() - { - _roots = new(EraWriter.MaxEra1Size); - } + private readonly ArrayPoolList> _roots = new(EraWriter.MaxEra1Size); public void Add(Hash256 headerHash, UInt256 td) { - Merkleizer merkleizer = new Merkleizer((int)Merkle.NextPowerOfTwoExponent(2)); + Merkleizer merkleizer = new(Merkle.NextPowerOfTwoExponent(2)); merkleizer.Feed(headerHash.Bytes); merkleizer.Feed(td); _roots.Add(merkleizer.CalculateRoot().ToLittleEndian()); @@ -29,7 +24,7 @@ public void Add(Hash256 headerHash, UInt256 td) public ValueHash256 ComputeRoot() { - Merkleizer merkleizer = new Merkleizer(0); + Merkleizer merkleizer = new(0); merkleizer.Feed(_roots, EraWriter.MaxEra1Size); UInt256 root = merkleizer.CalculateRoot(); return new ValueHash256(MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref root, 1))); diff --git a/src/Nethermind/Nethermind.Era1/AdminEraService.cs b/src/Nethermind/Nethermind.Era1/AdminEraService.cs index c1ce1fdd9cf0..d8e89290ae97 100644 --- a/src/Nethermind/Nethermind.Era1/AdminEraService.cs +++ b/src/Nethermind/Nethermind.Era1/AdminEraService.cs @@ -5,6 +5,7 @@ using Nethermind.Logging; namespace Nethermind.Era1; + public class AdminEraService : IAdminEraService { private readonly ILogger _logger; @@ -65,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/E2StoreReader.cs b/src/Nethermind/Nethermind.Era1/E2StoreReader.cs index 4031526717eb..89cfa476922d 100644 --- a/src/Nethermind/Nethermind.Era1/E2StoreReader.cs +++ b/src/Nethermind/Nethermind.Era1/E2StoreReader.cs @@ -1,12 +1,10 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Buffers; using System.Buffers.Binary; using System.IO.Compression; using System.Security.Cryptography; using CommunityToolkit.HighPerformance; -using DotNetty.Buffers; using Microsoft.IO; using Microsoft.Win32.SafeHandles; using Nethermind.Core.Collections; @@ -43,7 +41,7 @@ public long ReadEntryAndDecode(long position, Func, T> decoder, Entry entry = ReadEntry(position, expectedType); int length = (int)entry.Length; - using ArrayPoolList buffer = new ArrayPoolList(length, length); + using ArrayPoolListRef buffer = new(length, length); RandomAccess.Read(_file, buffer.AsSpan(), position + HeaderSize); value = decoder(buffer.AsMemory()); return (long)(entry.Length + HeaderSize); @@ -75,7 +73,7 @@ public Entry ReadEntry(long position, ushort? expectedType, CancellationToken to private async Task ReadEntryValueAsSnappy(long offset, ulong length, Func, T> decoder, CancellationToken cancellation = default) { - using ArrayPoolList inputBuffer = new ArrayPoolList((int)length, (int)length); + using ArrayPoolList inputBuffer = new((int)length, (int)length); RandomAccess.Read(_file, inputBuffer.AsSpan(), offset); Stream inputStream = inputBuffer.AsMemory().AsStream(); diff --git a/src/Nethermind/Nethermind.Era1/E2StoreWriter.cs b/src/Nethermind/Nethermind.Era1/E2StoreWriter.cs index 66f493ca43a7..065afad9a1a5 100644 --- a/src/Nethermind/Nethermind.Era1/E2StoreWriter.cs +++ b/src/Nethermind/Nethermind.Era1/E2StoreWriter.cs @@ -1,19 +1,14 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Buffers; -using System.Buffers.Binary; using System.Diagnostics; using System.IO.Compression; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography; -using DotNetty.Buffers; using Microsoft.IO; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Resettables; -using Nethermind.Serialization.Rlp; using Snappier; namespace Nethermind.Era1; diff --git a/src/Nethermind/Nethermind.Era1/Entry.cs b/src/Nethermind/Nethermind.Era1/Entry.cs index bea19d6f7822..aa671806bc08 100644 --- a/src/Nethermind/Nethermind.Era1/Entry.cs +++ b/src/Nethermind/Nethermind.Era1/Entry.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Snappier; - namespace Nethermind.Era1; public readonly struct Entry diff --git a/src/Nethermind/Nethermind.Era1/EraCliRunner.cs b/src/Nethermind/Nethermind.Era1/EraCliRunner.cs index d0eb93f710bc..6cc3e8f72fb4 100644 --- a/src/Nethermind/Nethermind.Era1/EraCliRunner.cs +++ b/src/Nethermind/Nethermind.Era1/EraCliRunner.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Nethermind.Blockchain; -using Nethermind.Config; using Nethermind.Logging; namespace Nethermind.Era1; diff --git a/src/Nethermind/Nethermind.Era1/EraConfig.cs b/src/Nethermind/Nethermind.Era1/EraConfig.cs index 87243ad854a8..74a2743816e3 100644 --- a/src/Nethermind/Nethermind.Era1/EraConfig.cs +++ b/src/Nethermind/Nethermind.Era1/EraConfig.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Nethermind.Config; - namespace Nethermind.Era1; public class EraConfig : IEraConfig diff --git a/src/Nethermind/Nethermind.Era1/EraExporter.cs b/src/Nethermind/Nethermind.Era1/EraExporter.cs index 1f304770205a..98f07d7860d9 100644 --- a/src/Nethermind/Nethermind.Era1/EraExporter.cs +++ b/src/Nethermind/Nethermind.Era1/EraExporter.cs @@ -1,19 +1,14 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Diagnostics; using System.IO.Abstractions; -using System.Runtime.CompilerServices; -using System.Text; using Nethermind.Blockchain; using Nethermind.Blockchain.Receipts; using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Logging; -using Nethermind.Serialization.Rlp; namespace Nethermind.Era1; @@ -62,7 +57,7 @@ private async Task DoExport( fileSystem.Directory.CreateDirectory(destinationPath); } - ProgressLogger progress = new ProgressLogger("Era export", logManager); + ProgressLogger progress = new("Era export", logManager); progress.Reset(0, to - from + 1); int totalProcessed = 0; diff --git a/src/Nethermind/Nethermind.Era1/EraImportException.cs b/src/Nethermind/Nethermind.Era1/EraImportException.cs index 077c73dca485..cd3017dcf11c 100644 --- a/src/Nethermind/Nethermind.Era1/EraImportException.cs +++ b/src/Nethermind/Nethermind.Era1/EraImportException.cs @@ -2,4 +2,5 @@ // SPDX-License-Identifier: LGPL-3.0-only namespace Nethermind.Era1; + public class EraImportException(string message) : EraException(message); diff --git a/src/Nethermind/Nethermind.Era1/EraImporter.cs b/src/Nethermind/Nethermind.Era1/EraImporter.cs index d7d61adc52bf..28b1ab7b443c 100644 --- a/src/Nethermind/Nethermind.Era1/EraImporter.cs +++ b/src/Nethermind/Nethermind.Era1/EraImporter.cs @@ -4,20 +4,19 @@ using System.Collections.Concurrent; using System.IO.Abstractions; using Autofac.Features.AttributeFilters; -using CommunityToolkit.HighPerformance; using Nethermind.Blockchain; using Nethermind.Blockchain.Receipts; using Nethermind.Blockchain.Synchronization; using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; using Nethermind.Db; using Nethermind.Era1.Exceptions; using Nethermind.Logging; namespace Nethermind.Era1; + public class EraImporter( IFileSystem fileSystem, IBlockTree blockTree, @@ -48,7 +47,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 93f301267724..740d2167c6c5 100644 --- a/src/Nethermind/Nethermind.Era1/EraReader.cs +++ b/src/Nethermind/Nethermind.Era1/EraReader.cs @@ -77,7 +77,7 @@ public async Task VerifyContent(ISpecProvider specProvider, IBlock int blockCount = (int)_fileReader.BlockCount; using ArrayPoolList<(Hash256, UInt256)> blockHashes = new(blockCount, blockCount); - ConcurrentQueue blockNumbers = new ConcurrentQueue(EnumerateBlockNumber()); + ConcurrentQueue blockNumbers = new(EnumerateBlockNumber()); using ArrayPoolList workers = Enumerable.Range(0, verifyConcurrency).Select((_) => Task.Run(async () => { @@ -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)) @@ -110,7 +110,7 @@ public async Task VerifyContent(ISpecProvider specProvider, IBlock await Task.WhenAll(workers.AsSpan()); using AccumulatorCalculator calculator = new(); - foreach (var valueTuple in blockHashes.AsSpan()) + foreach ((Hash256, UInt256) valueTuple in blockHashes.AsSpan()) { calculator.Add(valueTuple.Item1, valueTuple.Item2); } @@ -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 7f7c746f7812..208bb7db7db8 100644 --- a/src/Nethermind/Nethermind.Era1/EraStore.cs +++ b/src/Nethermind/Nethermind.Era1/EraStore.cs @@ -3,16 +3,15 @@ using System.IO.Abstractions; using System.Runtime.CompilerServices; -using CommunityToolkit.HighPerformance; using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Era1.Exceptions; using NonBlocking; namespace Nethermind.Era1; + public class EraStore : IEraStore { private readonly char[] _eraSeparator = ['-']; @@ -80,14 +79,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/EraWriter.cs b/src/Nethermind/Nethermind.Era1/EraWriter.cs index 2574736d5f98..ba2ac845454f 100644 --- a/src/Nethermind/Nethermind.Era1/EraWriter.cs +++ b/src/Nethermind/Nethermind.Era1/EraWriter.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Buffers.Binary; -using System.Security.Cryptography; using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; @@ -10,6 +9,7 @@ using Nethermind.Serialization.Rlp; namespace Nethermind.Era1; + public class EraWriter : IDisposable { public const int MaxEra1Size = 8192; @@ -105,7 +105,7 @@ public async Task Add(Block block, TxReceipt[] receipts, CancellationToken cance //Index is 64 bits segments in the format => start | index | index | ... | count //16 bytes is for the start and count plus every entry int length = 16 + _entryIndexes.Count * 8; - using ArrayPoolList blockIndex = new ArrayPoolList(length, length); + using ArrayPoolList blockIndex = new(length, length); Span blockIndexSpan = blockIndex.AsSpan(); WriteInt64(blockIndexSpan, 0, _startNumber); diff --git a/src/Nethermind/Nethermind.Era1/Exceptions/EraFormatException.cs b/src/Nethermind/Nethermind.Era1/Exceptions/EraFormatException.cs index aeeccbc40a06..d667f59536a8 100644 --- a/src/Nethermind/Nethermind.Era1/Exceptions/EraFormatException.cs +++ b/src/Nethermind/Nethermind.Era1/Exceptions/EraFormatException.cs @@ -2,5 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only namespace Nethermind.Era1; + [Serializable] internal class EraFormatException(string message) : EraException(message); diff --git a/src/Nethermind/Nethermind.Era1/Exceptions/EraVerificationException.cs b/src/Nethermind/Nethermind.Era1/Exceptions/EraVerificationException.cs index 2022b80ca34d..08a713bef4af 100644 --- a/src/Nethermind/Nethermind.Era1/Exceptions/EraVerificationException.cs +++ b/src/Nethermind/Nethermind.Era1/Exceptions/EraVerificationException.cs @@ -2,4 +2,5 @@ // SPDX-License-Identifier: LGPL-3.0-only namespace Nethermind.Era1.Exceptions; + public class EraVerificationException(string message) : EraException(message); diff --git a/src/Nethermind/Nethermind.Era1/JsonRpc/IEraAdminRpcModule.cs b/src/Nethermind/Nethermind.Era1/JsonRpc/IEraAdminRpcModule.cs index c1591be7fe79..ebb021f0ef33 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 e6d47686a701..cf34ee2e6b90 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.EthStats/EthStatsPlugin.cs b/src/Nethermind/Nethermind.EthStats/EthStatsPlugin.cs index 6160c44b93f5..e9756144a3a4 100644 --- a/src/Nethermind/Nethermind.EthStats/EthStatsPlugin.cs +++ b/src/Nethermind/Nethermind.EthStats/EthStatsPlugin.cs @@ -1,10 +1,8 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Threading.Tasks; using Autofac; using Autofac.Core; -using Nethermind.Api; using Nethermind.Api.Extensions; using Nethermind.Api.Steps; diff --git a/src/Nethermind/Nethermind.EthStats/EthStatsStep.cs b/src/Nethermind/Nethermind.EthStats/EthStatsStep.cs index c4ab1948687b..fb9ac50d35ea 100644 --- a/src/Nethermind/Nethermind.EthStats/EthStatsStep.cs +++ b/src/Nethermind/Nethermind.EthStats/EthStatsStep.cs @@ -8,6 +8,7 @@ using Nethermind.Api.Steps; using Nethermind.Blockchain; using Nethermind.Config; +using Nethermind.Consensus; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; @@ -38,6 +39,7 @@ public class EthStatsStep( IEthStatsConfig ethStatsConfig, INetworkConfig networkConfig, IInitConfig initConfig, + IMiningConfig miningConfig, ILogManager logManager ) : IStep, IAsyncDisposable { @@ -70,7 +72,7 @@ public async Task Execute(CancellationToken cancellationToken) string network = specProvider!.NetworkId.ToString(); string protocol = $"{P2PProtocolInfoProvider.DefaultCapabilitiesToString()}"; - IEthStatsClient _ethStatsClient = new EthStatsClient( + IEthStatsClient ethStatsClient = new EthStatsClient( ethStatsConfig.Server, reconnectionInterval, sender, @@ -87,14 +89,14 @@ public async Task Execute(CancellationToken cancellationToken) ethStatsConfig.Contact!, canUpdateHistory, ethStatsConfig.Secret!, - _ethStatsClient, + ethStatsClient, sender, txPool!, blockTree!, peerManager!, gasPriceOracle!, ethSyncingInfo!, - initConfig.IsMining, + miningConfig.Enabled, TimeSpan.FromSeconds(ethStatsConfig.SendInterval), logManager); diff --git a/src/Nethermind/Nethermind.EthStats/Integrations/EthStatsIntegration.cs b/src/Nethermind/Nethermind.EthStats/Integrations/EthStatsIntegration.cs index a8d5e5065a12..0c43179c597b 100644 --- a/src/Nethermind/Nethermind.EthStats/Integrations/EthStatsIntegration.cs +++ b/src/Nethermind/Nethermind.EthStats/Integrations/EthStatsIntegration.cs @@ -226,7 +226,7 @@ private async Task SendStatsAsync() } await _sender.SendAsync(_websocketClient!, new StatsMessage(new Messages.Models.Stats(true, _ethSyncingInfo.IsSyncing(), _isMining, 0, - _peerManager.ActivePeers.Count, (long)gasPrice, 100))); + _peerManager.ActivePeersCount, (long)gasPrice, 100))); } } } diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs index 2c1de53fb552..d7d56e883c10 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs @@ -8,15 +8,13 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Core.Test; -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; using Nethermind.Logging; -using Nethermind.State; -using Nethermind.Trie.Pruning; using Nethermind.Blockchain; namespace Nethermind.Evm.Benchmark @@ -31,8 +29,8 @@ public class EvmBenchmarks private ExecutionEnvironment _environment; 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(MainnetSpecProvider.Instance); - private EvmState _evmState; + private IBlockhashProvider _blockhashProvider = new TestBlockhashProvider(); + private VmState _evmState; private IWorldState _stateProvider; [GlobalSetup] @@ -45,12 +43,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 +58,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 cea7137c308a..0cbed6199ad5 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs @@ -9,16 +9,14 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Specs; 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; using Nethermind.Logging; using Nethermind.Specs; using Nethermind.Specs.Forks; -using Nethermind.State; -using Nethermind.Trie.Pruning; using Nethermind.Blockchain; namespace Nethermind.Evm.Benchmark; @@ -30,8 +28,8 @@ public class MultipleUnsignedOperations private ExecutionEnvironment _environment; 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(MainnetSpecProvider.Instance); - private EvmState _evmState; + private readonly IBlockhashProvider _blockhashProvider = new TestBlockhashProvider(); + private VmState _evmState; private IWorldState _stateProvider; private readonly byte[] _bytecode = Prepare.EvmCode @@ -77,12 +75,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 +90,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 5a777996feef..378f5a8833d0 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs @@ -9,16 +9,14 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Core.Test; -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; using Nethermind.Logging; using Nethermind.Specs.Forks; -using Nethermind.State; -using Nethermind.Trie.Pruning; using Nethermind.Blockchain; namespace Nethermind.Evm.Benchmark @@ -31,8 +29,8 @@ public class StaticCallBenchmarks private ExecutionEnvironment _environment; 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(MainnetSpecProvider.Instance); - private EvmState _evmState; + private IBlockhashProvider _blockhashProvider = new TestBlockhashProvider(); + private VmState _evmState; private IWorldState _stateProvider; public IEnumerable Bytecodes @@ -88,11 +86,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 +100,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.Benchmark/TestBlockhashProvider.cs b/src/Nethermind/Nethermind.Evm.Benchmark/TestBlockhashProvider.cs index 603f42954a9c..05ac63c5f18b 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/TestBlockhashProvider.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/TestBlockhashProvider.cs @@ -1,29 +1,23 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Threading; +using System.Threading.Tasks; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; -using Nethermind.State; namespace Nethermind.Evm.Benchmark { - public class TestBlockhashProvider : IBlockhashProvider + public class TestBlockhashProvider() : IBlockhashProvider { - private readonly ISpecProvider _specProvider; - public TestBlockhashProvider(ISpecProvider specProvider) - { - _specProvider = specProvider; - } - - public Hash256 GetBlockhash(BlockHeader currentBlock, long number) - => GetBlockhash(currentBlock, number, _specProvider.GetSpec(currentBlock)); - public Hash256 GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec spec) { return Keccak.Compute(spec.IsBlockHashInStateAvailable ? (Eip2935Constants.RingBufferSize + number).ToString() : (number).ToString()); } + + public Task Prefetch(BlockHeader currentBlock, CancellationToken token) => Task.CompletedTask; } } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/BN254.cs b/src/Nethermind/Nethermind.Evm.Precompiles/BN254.cs index fb4ced5f60f1..047f9053b059 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/BN254.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/BN254.cs @@ -2,8 +2,12 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers.Binary; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; using Nethermind.MclBindings; namespace Nethermind.Evm.Precompiles; @@ -21,50 +25,59 @@ static BN254() throw new InvalidOperationException("MCL initialization failed"); } - internal static bool Add(Span input, Span output) + [MethodImpl(MethodImplOptions.NoInlining)] + internal static bool Add(byte[] output, ReadOnlySpan input) { - if (input.Length != 128) - return false; + const int chunkSize = 64; - if (!DeserializeG1(input[0..64], out mclBnG1 x)) - return false; + Debug.Assert(input.Length == 128); + Debug.Assert(output.Length == 64); - if (!DeserializeG1(input[64..128], out mclBnG1 y)) - return false; + fixed (byte* data = &MemoryMarshal.GetReference(input)) + { + if (!DeserializeG1(data, out mclBnG1 x)) + return false; + + if (!DeserializeG1(data + chunkSize, out mclBnG1 y)) + return false; - mclBnG1_add(ref x, x, y); // x += y - mclBnG1_normalize(ref x, x); + mclBnG1_add(ref x, x, y); // x += y + mclBnG1_normalize(ref x, x); - return SerializeG1(x, output); + return SerializeG1(x, output); + } } - internal static bool Mul(Span input, Span output) + [MethodImpl(MethodImplOptions.NoInlining)] + internal static bool Mul(byte[] output, ReadOnlySpan input) { - if (input.Length != 96) - return false; - - if (!DeserializeG1(input[0..64], out mclBnG1 x)) - return false; + const int chunkSize = 64; - Span yData = input[64..]; - yData.Reverse(); // To little-endian + Debug.Assert(input.Length == 96); + Debug.Assert(output.Length == 64); - mclBnFr y = default; - - fixed (byte* ptr = &MemoryMarshal.GetReference(yData)) + fixed (byte* data = &MemoryMarshal.GetReference(input)) { - if (mclBnFr_setLittleEndianMod(ref y, (nint)ptr, 32) == -1 || mclBnFr_isValid(y) == 0) + if (!DeserializeG1(data, out mclBnG1 x)) return false; - } - mclBnG1_mul(ref x, x, y); // x *= y - mclBnG1_normalize(ref x, x); + Unsafe.SkipInit(out mclBnFr y); + if (mclBnFr_setBigEndianMod(ref y, (nint)data + chunkSize, 32) == -1 || mclBnFr_isValid(y) == 0) + return false; - return SerializeG1(x, output); + mclBnG1_mul(ref x, x, y); // x *= y + mclBnG1_normalize(ref x, x); + return SerializeG1(x, output); + } } - internal static bool CheckPairing(Span input, Span output) + [MethodImpl(MethodImplOptions.NoInlining)] + internal static bool CheckPairing(byte[] output, ReadOnlySpan input) { + if (output.Length < 32) + return false; + + // Empty input means "true" by convention if (input.Length == 0) { output[31] = 1; @@ -74,138 +87,307 @@ internal static bool CheckPairing(Span input, Span output) if (input.Length % PairSize != 0) return false; - mclBnGT gt = default; - Unsafe.SkipInit(out mclBnGT previous); - var hasPrevious = false; - - for (int i = 0, count = input.Length; i < count; i += PairSize) + fixed (byte* data = &MemoryMarshal.GetReference(input)) { - var i64 = i + 64; - - if (!DeserializeG1(input[i..i64], out mclBnG1 g1)) - return false; - - if (!DeserializeG2(input[i64..(i64 + 128)], out mclBnG2 g2)) - return false; - - if (mclBnG1_isZero(g1) == 1 || mclBnG2_isZero(g2) == 1) - continue; + Unsafe.SkipInit(out mclBnGT ml); + Unsafe.SkipInit(out mclBnGT acc); + bool hasMl = false; + + for (int i = 0; i < input.Length; i += PairSize) + { + if (!DeserializeG1(data + i, out mclBnG1 g1)) + return false; + + if (!DeserializeG2(data + i + 64, out mclBnG2 g2)) + return false; + + // Skip if g1 or g2 are zero + if (IsZero(g1) || IsZero(g2)) + continue; + + mclBn_millerLoop(ref hasMl ? ref ml : ref acc, g1, g2); // Miller loop only + + if (hasMl) + { + mclBnGT_mul(ref acc, acc, ml); + } + else + { + hasMl = true; + } + } + + // All pairs had zero element -> valid + if (!hasMl) + { + output[31] = 1; + return true; + } + + // Single final exponentiation for the product + mclBn_finalExp(ref acc, acc); + + // True if the product of pairings equals 1 in GT + output[31] = Convert.ToByte(mclBnGT_isOne(acc) == 1); + } + return true; + } - mclBn_pairing(ref gt, g1, g2); + private static bool IsZero(in T data) + where T : unmanaged, allows ref struct + { + ref byte start = ref Unsafe.As(ref Unsafe.AsRef(in data)); + ReadOnlySpan span = MemoryMarshal.CreateReadOnlySpan(in start, sizeof(T)); + return span.IndexOfAnyExcept((byte)0) < 0; + } - // Skip multiplication for the first pairing as there's no previous result - if (hasPrevious) - mclBnGT_mul(ref gt, gt, previous); // gt *= previous + private static bool DeserializeG1(byte* data, out mclBnG1 point) + { + const int chunkSize = 32; - previous = gt; - hasPrevious = true; - } + point = default; - // If gt is zero, then no pairing was computed, and it's considered valid - if (mclBnGT_isOne(gt) == 1 || mclBnGT_isZero(gt) == 1) + // Treat all-zero as point at infinity for your calling convention + if (IsZero64(data)) { - output[31] = 1; return true; } - return mclBnGT_isValid(gt) == 1; + // Input is big-endian; MCL call below expects little-endian byte order for Fp + byte* tmp = stackalloc byte[chunkSize]; + + // x + CopyReverse32(data, tmp); + if (mclBnFp_deserialize(ref point.x, (nint)tmp, chunkSize) == nuint.Zero) + return false; + // y + CopyReverse32(data + chunkSize, tmp); + if (mclBnFp_deserialize(ref point.y, (nint)tmp, chunkSize) == nuint.Zero) + return false; + + mclBnFp_setInt32(ref point.z, 1); + return mclBnG1_isValid(point) == 1; } - private static bool DeserializeG1(Span data, out mclBnG1 point) + private static bool DeserializeG2(byte* data, out mclBnG2 point) { + const int chunkSize = 32; + point = default; - // Check for all-zero data - if (data.IndexOfAnyExcept((byte)0) == -1) + // Treat all-zero as point at infinity + if (IsZero128(data)) + { return true; + } - Span x = data[0..32]; - x.Reverse(); // To little-endian + // Input layout: x_im, x_re, y_im, y_re (each 32 bytes, big-endian) + // MCL Fp2 layout: d0 = re, d1 = im + byte* tmp = stackalloc byte[chunkSize]; - fixed (byte* ptr = &MemoryMarshal.GetReference(x)) - { - if (mclBnFp_deserialize(ref point.x, (nint)ptr, 32) == nuint.Zero) - return false; - } + // x.im + CopyReverse32(data, tmp); + if (mclBnFp_deserialize(ref point.x.d1, (nint)tmp, chunkSize) == nuint.Zero) + return false; - Span y = data[32..64]; - y.Reverse(); // To little-endian + // x.re + CopyReverse32(data + chunkSize, tmp); + if (mclBnFp_deserialize(ref point.x.d0, (nint)tmp, chunkSize) == nuint.Zero) + return false; - fixed (byte* ptr = &MemoryMarshal.GetReference(y)) - { - if (mclBnFp_deserialize(ref point.y, (nint)ptr, 32) == nuint.Zero) - return false; - } + // y.im + CopyReverse32(data + chunkSize * 2, tmp); + if (mclBnFp_deserialize(ref point.y.d1, (nint)tmp, chunkSize) == nuint.Zero) + return false; - mclBnFp_setInt32(ref point.z, 1); + // y.re + CopyReverse32(data + chunkSize * 3, tmp); + if (mclBnFp_deserialize(ref point.y.d0, (nint)tmp, chunkSize) == nuint.Zero) + return false; - return mclBnG1_isValid(point) == 1; + mclBnFp_setInt32(ref point.z.d0, 1); + + return mclBnG2_isValid(point) == 1 && mclBnG2_isValidOrder(point) == 1; } - private static bool DeserializeG2(Span data, out mclBnG2 point) + private static bool SerializeG1(in mclBnG1 point, byte[] output) { - point = default; - - // Check for all-zero data - if (data.IndexOfAnyExcept((byte)0) == -1) - return true; + const int chunkSize = 32; - Span x0 = data[32..64]; - Span x1 = data[0..32]; - x0.Reverse(); // To little-endian - x1.Reverse(); // To little-endian - - fixed (byte* ptr0 = &MemoryMarshal.GetReference(x0)) - fixed (byte* ptr1 = &MemoryMarshal.GetReference(x1)) + fixed (byte* ptr = &MemoryMarshal.GetArrayDataReference(output)) { - if (mclBnFp_deserialize(ref point.x.d0, (nint)ptr0, 32) == nuint.Zero) + if (mclBnFp_getLittleEndian((nint)ptr, chunkSize, point.x) == nuint.Zero) return false; - if (mclBnFp_deserialize(ref point.x.d1, (nint)ptr1, 32) == nuint.Zero) + if (mclBnFp_getLittleEndian((nint)ptr + chunkSize, chunkSize, point.y) == nuint.Zero) return false; + + CopyReverse32(ptr, ptr); // To big-endian + CopyReverse32(ptr + chunkSize, ptr + chunkSize); // To big-endian } - Span y0 = data[96..128]; - Span y1 = data[64..96]; - y0.Reverse(); // To little-endian - y1.Reverse(); // To little-endian + return true; + } + + private static unsafe bool IsZero64(byte* ptr) + { + const int Length = 64; - fixed (byte* ptr0 = &MemoryMarshal.GetReference(y0)) - fixed (byte* ptr1 = &MemoryMarshal.GetReference(y1)) + if (Vector512.IsHardwareAccelerated) { - if (mclBnFp_deserialize(ref point.y.d0, (nint)ptr0, 32) == nuint.Zero) - return false; - - if (mclBnFp_deserialize(ref point.y.d1, (nint)ptr1, 32) == nuint.Zero) - return false; + Vector512 a = Unsafe.ReadUnaligned>(ptr); + return a == default; + } + else if (Vector256.IsHardwareAccelerated) + { + Vector256 a = Unsafe.ReadUnaligned>(ptr); + Vector256 b = Unsafe.ReadUnaligned>(ptr + Vector256.Count); + Vector256 o = Vector256.BitwiseOr(a, b); + return o == default; + } + else if (Vector128.IsHardwareAccelerated) + { + // 4x16-byte blocks, coalesced in pairs + for (nuint offset = 0; offset < Length; offset += (nuint)Vector128.Count * 2) + { + Vector128 a = Unsafe.ReadUnaligned>(ptr + offset); + Vector128 b = Unsafe.ReadUnaligned>(ptr + offset + Vector128.Count); + Vector128 o = Vector128.BitwiseOr(a, b); + if (o != default) return false; + } + return true; + } + else + { + // scalar fallback + ulong* x = (ulong*)ptr; + for (int i = 0; i < 8; i++) + { + if (x[i] != 0) + return false; + } + return true; } + } - mclBnFp_setInt32(ref point.z.d0, 1); + private static unsafe bool IsZero128(byte* ptr) + { + const int Length = 128; - return mclBnG2_isValid(point) == 1 && mclBnG2_isValidOrder(point) == 1; + if (Vector512.IsHardwareAccelerated) + { + // 2x512 -> OR‑reduce -> EqualsAll + Vector512 a = Unsafe.ReadUnaligned>(ptr + 0); + Vector512 b = Unsafe.ReadUnaligned>(ptr + Vector512.Count); + Vector512 o = Vector512.BitwiseOr(a, b); + return o == default; + } + else if (Vector256.IsHardwareAccelerated) + { + // 4x32-byte blocks, coalesced in pairs (2 loads per iteration) + for (nuint offset = 0; offset < Length; offset += (nuint)Vector256.Count * 2) + { + Vector256 a = Unsafe.ReadUnaligned>(ptr + offset); + Vector256 b = Unsafe.ReadUnaligned>(ptr + offset + Vector256.Count); + Vector256 o = Vector256.BitwiseOr(a, b); + if (o != default) return false; + } + return true; + } + else if (Vector128.IsHardwareAccelerated) + { + // 8x16-byte blocks, coalesced in pairs + for (nuint offset = 0; offset < Length; offset += (nuint)Vector128.Count * 2) + { + Vector128 a = Unsafe.ReadUnaligned>(ptr + offset); + Vector128 b = Unsafe.ReadUnaligned>(ptr + offset + Vector128.Count); + Vector128 o = Vector128.BitwiseOr(a, b); + if (o != default) return false; + } + return true; + } + else + { + // scalar fallback + ulong* x = (ulong*)ptr; + for (int i = 0; i < 16; i++) + { + if (x[i] != 0) + return false; + } + return true; + } } - private static bool SerializeG1(in mclBnG1 point, Span output) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void CopyReverse32(byte* srcRef, byte* dstRef) { - Span x = output[0..32]; - - fixed (byte* ptr = &MemoryMarshal.GetReference(x)) + if (Avx2.IsSupported) { - if (mclBnFp_getLittleEndian((nint)ptr, 32, point.x) == nuint.Zero) - return false; + Reverse32BytesAvx2(srcRef, dstRef); + } + else if (Vector128.IsHardwareAccelerated) + { + Reverse32Bytes128(srcRef, dstRef); + } + else + { + // Fallback scalar path + Reverse32BytesScalar(srcRef, dstRef); } + } - Span y = output[32..64]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Reverse32BytesAvx2(byte* srcRef, byte* dstRef) + { + // Load 32 bytes as one 256-bit vector + Vector256 vec = Unsafe.ReadUnaligned>(srcRef); + Vector256 fullRev; - fixed (byte* ptr = &MemoryMarshal.GetReference(y)) + Vector256 mask = Vector256.Create((byte)31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); + if (Avx512Vbmi.VL.IsSupported) { - if (mclBnFp_getLittleEndian((nint)ptr, 32, point.y) == nuint.Zero) - return false; + fullRev = Avx512Vbmi.VL.PermuteVar32x8(vec, mask); + } + else + { + Vector256 revInLane = Avx2.Shuffle(vec, mask); + fullRev = Avx2.Permute2x128(revInLane, revInLane, 0x01); } - x.Reverse(); // To big-endian - y.Reverse(); // To big-endian + Unsafe.WriteUnaligned(dstRef, fullRev); + } - return true; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Reverse32Bytes128(byte* srcRef, byte* dstRef) + { + // Two 16-byte halves: reverse each then swap them + Vector128 lo = Unsafe.ReadUnaligned>(srcRef); + Vector128 hi = Unsafe.ReadUnaligned>(srcRef + Vector128.Count); + + Vector128 indices = Vector128.Create((byte)15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); + lo = Vector128.Shuffle(lo, indices); + hi = Vector128.Shuffle(hi, indices); + + // Store swapped halves reversed + Unsafe.WriteUnaligned(dstRef, hi); + Unsafe.WriteUnaligned(dstRef + Vector128.Count, lo); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Reverse32BytesScalar(byte* srcRef, byte* dstRef) + { + ulong* src = (ulong*)srcRef; + ulong* dst = (ulong*)dstRef; + + ulong a = BinaryPrimitives.ReverseEndianness(src[0]); + ulong b = BinaryPrimitives.ReverseEndianness(src[1]); + ulong c = BinaryPrimitives.ReverseEndianness(src[2]); + ulong d = BinaryPrimitives.ReverseEndianness(src[3]); + + dst[0] = d; + dst[1] = c; + dst[2] = b; + dst[3] = a; } } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/BN254AddPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/BN254AddPrecompile.cs index bc46669b04a8..518fce0d2428 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/BN254AddPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/BN254AddPrecompile.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Specs; @@ -10,6 +11,9 @@ namespace Nethermind.Evm.Precompiles; /// public class BN254AddPrecompile : IPrecompile { + private const int InputLength = 128; + private const int OutputLength = 64; + public static readonly BN254AddPrecompile Instance = new(); public static Address Address { get; } = Address.FromNumber(6); @@ -22,15 +26,33 @@ public class BN254AddPrecompile : IPrecompile public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 0L; - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + [SkipLocalsInit] + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { Metrics.Bn254AddPrecompile++; - Span input = stackalloc byte[128]; - Span output = stackalloc byte[64]; + ReadOnlySpan input = inputData.Span; + if (InputLength < input.Length) + { + // Input is too long - trim to the expected length. + input = input[..InputLength]; + } + + byte[] output = new byte[OutputLength]; + bool result = input.Length == InputLength ? + BN254.Add(output, input) : + RunPaddedInput(output, input); - inputData.Span[0..Math.Min(inputData.Length, input.Length)].CopyTo(input); + return result ? output : Errors.Failed; + } - return BN254.Add(input, output) ? (output.ToArray(), true) : IPrecompile.Failure; + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool RunPaddedInput(byte[] output, ReadOnlySpan input) + { + // Input is too short - pad with zeros up to the expected length. + Span padded = stackalloc byte[InputLength]; + // Copies input bytes; rest of the span is already zero-initialized. + input.CopyTo(padded); + return BN254.Add(output, padded); } } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/BN254MulPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/BN254MulPrecompile.cs index e5b2796bc83a..ad265278628f 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/BN254MulPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/BN254MulPrecompile.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Specs; @@ -10,6 +11,9 @@ namespace Nethermind.Evm.Precompiles; /// public class BN254MulPrecompile : IPrecompile { + private const int InputLength = 96; + private const int OutputLength = 64; + public static readonly BN254MulPrecompile Instance = new(); public static Address Address { get; } = Address.FromNumber(7); @@ -22,15 +26,34 @@ public class BN254MulPrecompile : IPrecompile public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 0L; - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + [SkipLocalsInit] + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { Metrics.Bn254MulPrecompile++; - Span input = stackalloc byte[96]; - Span output = stackalloc byte[64]; + ReadOnlySpan input = inputData.Span; + if (InputLength < input.Length) + { + // Input is too long - trim to the expected length. + input = input[..InputLength]; + } + + byte[] output = new byte[OutputLength]; + bool result = input.Length == InputLength + ? BN254.Mul(output, input) + : RunPaddedInput(output, input); - inputData.Span[0..Math.Min(inputData.Length, input.Length)].CopyTo(input); + return result ? output : Errors.Failed; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool RunPaddedInput(byte[] output, ReadOnlySpan input) + { + // Input is too short - pad with zeros up to the expected length. + Span padded = stackalloc byte[InputLength]; + // Copies input bytes; rest of the span is already zero-initialized. + input.CopyTo(padded); - return BN254.Mul(input, output) ? (output.ToArray(), true) : IPrecompile.Failure; + return BN254.Mul(output, padded); } } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/BN254PairingPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/BN254PairingPrecompile.cs index ff0b8292062d..e0c190b41645 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/BN254PairingPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/BN254PairingPrecompile.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; using Nethermind.Core; using Nethermind.Core.Specs; @@ -26,25 +25,19 @@ public class BN254PairingPrecompile : IPrecompile public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => (releaseSpec.IsEip1108Enabled ? 34_000L : 80_000L) * (inputData.Length / BN254.PairSize); - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { Metrics.Bn254PairingPrecompile++; if (releaseSpec.IsOpGraniteEnabled && inputData.Length > PairingMaxInputSizeGranite || inputData.Length % BN254.PairSize > 0) { - return IPrecompile.Failure; + return Errors.InvalidInputLength; } - var input = ArrayPool.Shared.Rent(inputData.Length); - Span output = stackalloc byte[32]; + byte[] output = new byte[32]; + bool result = BN254.CheckPairing(output, inputData.Span); - inputData.CopyTo(input); - - var result = BN254.CheckPairing(input.AsSpan(0, inputData.Length), output); - - ArrayPool.Shared.Return(input); - - return result ? (output.ToArray(), true) : IPrecompile.Failure; + return result ? output : Errors.Failed; } } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/Blake2FPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/Blake2FPrecompile.cs index 74762e7ebd51..9cf26c912828 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/Blake2FPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/Blake2FPrecompile.cs @@ -41,22 +41,16 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec return rounds; } - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { - if (inputData.Length != RequiredInputLength) - { - return IPrecompile.Failure; - } + if (inputData.Length != RequiredInputLength) return Errors.InvalidInputLength; byte finalByte = inputData.Span[212]; - if (finalByte != 0 && finalByte != 1) - { - return IPrecompile.Failure; - } + if (finalByte != 0 && finalByte != 1) return Errors.InvalidFinalFlag; byte[] result = new byte[64]; _blake.Compress(inputData.Span, result); - return (result, true); + return result; } } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/BlsExtensions.cs b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/BlsExtensions.cs index afcd1842babe..dee0d049da93 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/BlsExtensions.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/BlsExtensions.cs @@ -2,88 +2,84 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using Nethermind.Core; using G1 = Nethermind.Crypto.Bls.P1; using G2 = Nethermind.Crypto.Bls.P2; namespace Nethermind.Evm.Precompiles.Bls; -public static class BlsExtensions +internal static class BlsExtensions { // decodes and checks point is on curve - public static bool TryDecodeRaw(this G1 p, ReadOnlySpan raw) + public static Result TryDecodeRaw(this G1 p, ReadOnlySpan raw) { if (raw.Length != BlsConst.LenG1) { - return false; + return Errors.InvalidFieldLength; } - if (!ValidRawFp(raw[..BlsConst.LenFp]) || !ValidRawFp(raw[BlsConst.LenFp..])) - { - return false; - } + Result result = ValidRawFp(raw[..BlsConst.LenFp]) && + ValidRawFp(raw[BlsConst.LenFp..]); - // set to infinity point by default - p.Zero(); + if (result) + { + // set to infinity point by default + p.Zero(); - ReadOnlySpan fp0 = raw[BlsConst.LenFpPad..BlsConst.LenFp]; - ReadOnlySpan fp1 = raw[(BlsConst.LenFp + BlsConst.LenFpPad)..]; + ReadOnlySpan fp0 = raw[BlsConst.LenFpPad..BlsConst.LenFp]; + ReadOnlySpan fp1 = raw[(BlsConst.LenFp + BlsConst.LenFpPad)..]; - bool isInfinity = !fp0.ContainsAnyExcept((byte)0) && !fp1.ContainsAnyExcept((byte)0); - if (isInfinity) - { - return true; - } + bool isInfinity = !fp0.ContainsAnyExcept((byte)0) && !fp1.ContainsAnyExcept((byte)0); + if (isInfinity) + { + return Result.Success; + } - p.Decode(fp0, fp1); - if (!p.OnCurve()) - { - return false; + p.Decode(fp0, fp1); + return p.OnCurve() ? Result.Success : Errors.G1PointSubgroup; } - return true; + return result; } + // decodes and checks point is on curve - public static bool TryDecodeRaw(this G2 p, ReadOnlySpan raw) + public static Result TryDecodeRaw(this G2 p, ReadOnlySpan raw) { if (raw.Length != BlsConst.LenG2) { - return false; + return Errors.InvalidFieldLength; } - if (!ValidRawFp(raw[..BlsConst.LenFp]) || - !ValidRawFp(raw[BlsConst.LenFp..(2 * BlsConst.LenFp)]) || - !ValidRawFp(raw[(2 * BlsConst.LenFp)..(3 * BlsConst.LenFp)]) || - !ValidRawFp(raw[(3 * BlsConst.LenFp)..])) + Result result = ValidRawFp(raw[..BlsConst.LenFp]) && + ValidRawFp(raw[BlsConst.LenFp..(2 * BlsConst.LenFp)]) && + ValidRawFp(raw[(2 * BlsConst.LenFp)..(3 * BlsConst.LenFp)]) && + ValidRawFp(raw[(3 * BlsConst.LenFp)..]); + if (result) { - return false; + // set to infinity point by default + p.Zero(); + + ReadOnlySpan fp0 = raw[BlsConst.LenFpPad..BlsConst.LenFp]; + ReadOnlySpan fp1 = raw[(BlsConst.LenFp + BlsConst.LenFpPad)..(2 * BlsConst.LenFp)]; + ReadOnlySpan fp2 = raw[(2 * BlsConst.LenFp + BlsConst.LenFpPad)..(3 * BlsConst.LenFp)]; + ReadOnlySpan fp3 = raw[(3 * BlsConst.LenFp + BlsConst.LenFpPad)..]; + + bool isInfinity = !fp0.ContainsAnyExcept((byte)0) + && !fp1.ContainsAnyExcept((byte)0) + && !fp2.ContainsAnyExcept((byte)0) + && !fp3.ContainsAnyExcept((byte)0); + + if (isInfinity) + { + return Result.Success; + } + + p.Decode(fp0, fp1, fp2, fp3); + return p.OnCurve() ? Result.Success : Errors.G1PointSubgroup; } - // set to infinity point by default - p.Zero(); - - ReadOnlySpan fp0 = raw[BlsConst.LenFpPad..BlsConst.LenFp]; - ReadOnlySpan fp1 = raw[(BlsConst.LenFp + BlsConst.LenFpPad)..(2 * BlsConst.LenFp)]; - ReadOnlySpan fp2 = raw[(2 * BlsConst.LenFp + BlsConst.LenFpPad)..(3 * BlsConst.LenFp)]; - ReadOnlySpan fp3 = raw[(3 * BlsConst.LenFp + BlsConst.LenFpPad)..]; - - bool isInfinity = !fp0.ContainsAnyExcept((byte)0) - && !fp1.ContainsAnyExcept((byte)0) - && !fp2.ContainsAnyExcept((byte)0) - && !fp3.ContainsAnyExcept((byte)0); - - if (isInfinity) - { - return true; - } - - p.Decode(fp0, fp1, fp2, fp3); - if (!p.OnCurve()) - { - return false; - } - - return true; + return result; } public static byte[] EncodeRaw(this G1 p) @@ -118,42 +114,50 @@ public static byte[] EncodeRaw(this G2 p) return raw; } - public static bool ValidRawFp(ReadOnlySpan fp) + public static Result ValidRawFp(ReadOnlySpan fp) { if (fp.Length != BlsConst.LenFp) { - return false; + return Errors.InvalidFieldLength; } // check that padding bytes are zeroes if (fp[..BlsConst.LenFpPad].ContainsAnyExcept((byte)0)) { - return false; + return Errors.InvalidFieldElementTopBytes; } // check that fp < base field order - return fp[BlsConst.LenFpPad..].SequenceCompareTo(BlsConst.BaseFieldOrder.AsSpan()) < 0; + return fp[BlsConst.LenFpPad..].SequenceCompareTo(BlsConst.BaseFieldOrder.AsSpan()) < 0 + ? Result.Success + : Errors.G1PointSubgroup; } - public static bool TryDecodeG1ToBuffer(ReadOnlyMemory inputData, Memory pointBuffer, Memory scalarBuffer, int dest, int index) + public static Result TryDecodeG1ToBuffer(ReadOnlyMemory inputData, Memory pointBuffer, Memory scalarBuffer, int dest, int index) => TryDecodePointToBuffer(inputData, pointBuffer, scalarBuffer, dest, index, BlsConst.LenG1, G1MSMPrecompile.ItemSize, DecodeAndCheckSubgroupG1); - public static bool TryDecodeG2ToBuffer(ReadOnlyMemory inputData, Memory pointBuffer, Memory scalarBuffer, int dest, int index) + public static Result TryDecodeG2ToBuffer(ReadOnlyMemory inputData, Memory pointBuffer, Memory scalarBuffer, int dest, int index) => TryDecodePointToBuffer(inputData, pointBuffer, scalarBuffer, dest, index, BlsConst.LenG2, G2MSMPrecompile.ItemSize, DecodeAndCheckSubgroupG2); - private static bool DecodeAndCheckSubgroupG1(ReadOnlyMemory rawPoint, Memory pointBuffer, int dest) + private static Result DecodeAndCheckSubgroupG1(ReadOnlyMemory rawPoint, Memory pointBuffer, int dest) { G1 p = new(pointBuffer.Span[(dest * G1.Sz)..]); - return p.TryDecodeRaw(rawPoint.Span) && (BlsConst.DisableSubgroupChecks || p.InGroup()); + Result result = p.TryDecodeRaw(rawPoint.Span); + return result + ? BlsConst.DisableSubgroupChecks || p.InGroup() ? Result.Success : Errors.G1PointSubgroup + : result; } - private static bool DecodeAndCheckSubgroupG2(ReadOnlyMemory rawPoint, Memory pointBuffer, int dest) + private static Result DecodeAndCheckSubgroupG2(ReadOnlyMemory rawPoint, Memory pointBuffer, int dest) { G2 p = new(pointBuffer.Span[(dest * G2.Sz)..]); - return p.TryDecodeRaw(rawPoint.Span) && (BlsConst.DisableSubgroupChecks || p.InGroup()); + Result result = p.TryDecodeRaw(rawPoint.Span); + return result + ? BlsConst.DisableSubgroupChecks || p.InGroup() ? Result.Success : Errors.G1PointSubgroup + : result; } - private static bool TryDecodePointToBuffer( + private static Result TryDecodePointToBuffer( ReadOnlyMemory inputData, Memory pointBuffer, Memory scalarBuffer, @@ -161,26 +165,25 @@ private static bool TryDecodePointToBuffer( int index, int pointLen, int itemSize, - Func, Memory, int, bool> decodeAndCheckPoint) + Func, Memory, int, Result> decodeAndCheckPoint) { // skip points at infinity if (dest == -1) { - return true; + return Result.Success; } int offset = index * itemSize; ReadOnlyMemory rawPoint = inputData[offset..(offset + pointLen)]; ReadOnlyMemory reversedScalar = inputData[(offset + pointLen)..(offset + itemSize)]; - if (!decodeAndCheckPoint(rawPoint, pointBuffer, dest)) - { - return false; - } + Result result = decodeAndCheckPoint(rawPoint, pointBuffer, dest); + if (!result) return result; int destOffset = dest * 32; reversedScalar.CopyTo(scalarBuffer[destOffset..]); scalarBuffer[destOffset..(destOffset + 32)].Span.Reverse(); - return true; + return Result.Success; + } } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G1AddPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G1AddPrecompile.cs index 85ecf7dae0d2..96a619b7d110 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G1AddPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G1AddPrecompile.cs @@ -5,7 +5,6 @@ using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Specs; -using Nethermind.Evm.Precompiles; using G1 = Nethermind.Crypto.Bls.P1; namespace Nethermind.Evm.Precompiles.Bls; @@ -30,35 +29,28 @@ private G1AddPrecompile() public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 0L; [SkipLocalsInit] - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { Metrics.BlsG1AddPrecompile++; const int expectedInputLength = 2 * BlsConst.LenG1; - if (inputData.Length != expectedInputLength) - { - return IPrecompile.Failure; - } + if (inputData.Length != expectedInputLength) return Errors.InvalidInputLength; G1 x = new(stackalloc long[G1.Sz]); G1 y = new(stackalloc long[G1.Sz]); - if (!x.TryDecodeRaw(inputData[..BlsConst.LenG1].Span) || !y.TryDecodeRaw(inputData[BlsConst.LenG1..].Span)) - { - return IPrecompile.Failure; - } + Result result = x.TryDecodeRaw(inputData[..BlsConst.LenG1].Span) && + y.TryDecodeRaw(inputData[BlsConst.LenG1..].Span); - // adding to infinity point has no effect - if (x.IsInf()) + if (result) { - return (inputData[BlsConst.LenG1..].ToArray(), true); - } + // adding to infinity point has no effect + if (x.IsInf()) return inputData[BlsConst.LenG1..].ToArray(); + if (y.IsInf()) return inputData[..BlsConst.LenG1].ToArray(); - if (y.IsInf()) - { - return (inputData[..BlsConst.LenG1].ToArray(), true); + G1 res = x.Add(y); + return res.EncodeRaw(); } - G1 res = x.Add(y); - return (res.EncodeRaw(), true); + return result.Error!; } } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G1MSMPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G1MSMPrecompile.cs index 80e92ab1cbf3..79270203be31 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G1MSMPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G1MSMPrecompile.cs @@ -9,7 +9,6 @@ using G1 = Nethermind.Crypto.Bls.P1; using System.Runtime.CompilerServices; -using Nethermind.Evm.Precompiles; namespace Nethermind.Evm.Precompiles.Bls; @@ -39,34 +38,34 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec public const int ItemSize = 160; [SkipLocalsInit] - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { Metrics.BlsG1MSMPrecompile++; if (inputData.Length % ItemSize > 0 || inputData.Length == 0) { - return IPrecompile.Failure; + 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); } - private (byte[], bool) Mul(ReadOnlyMemory inputData) + private Result Mul(ReadOnlyMemory inputData) { G1 x = new(stackalloc long[G1.Sz]); - if (!x.TryDecodeRaw(inputData[..BlsConst.LenG1].Span) || !(BlsConst.DisableSubgroupChecks || x.InGroup())) - { - return IPrecompile.Failure; - } + Result result = x.TryDecodeRaw(inputData[..BlsConst.LenG1].Span); + if (!result) return result.Error!; + + if (!(BlsConst.DisableSubgroupChecks || x.InGroup())) return Errors.G1PointSubgroup; // multiplying by zero gives infinity point // any scalar multiplied by infinity point is infinity point bool scalarIsZero = !inputData.Span[BlsConst.LenG1..].ContainsAnyExcept((byte)0); if (scalarIsZero || x.IsInf()) { - return (BlsConst.G1Inf, true); + return BlsConst.G1Inf; } Span scalar = stackalloc byte[32]; @@ -74,10 +73,10 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec scalar.Reverse(); G1 res = x.Mult(scalar); - return (res.EncodeRaw(), true); + return res.EncodeRaw(); } - private (byte[], bool) MSM(ReadOnlyMemory inputData, int nItems) + private Result MSM(ReadOnlyMemory inputData, int nItems) { using ArrayPoolList rawPoints = new(nItems * G1.Sz, nItems * G1.Sz); using ArrayPoolList rawScalars = new(nItems * 32, nItems * 32); @@ -98,23 +97,19 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec // only infinity points so return infinity if (npoints == 0) { - return (BlsConst.G1Inf, true); + return BlsConst.G1Inf; } - bool fail = false; + Result result = Result.Success; // decode points to rawPoints buffer // n.b. subgroup checks carried out as part of decoding #pragma warning disable CS0162 // Unreachable code detected if (BlsConst.DisableConcurrency) { - for (int i = 0; i < pointDestinations.Count; i++) + for (int i = 0; i < pointDestinations.Count && result; i++) { - if (!BlsExtensions.TryDecodeG1ToBuffer(inputData, rawPoints.AsMemory(), rawScalars.AsMemory(), pointDestinations[i], i)) - { - fail = true; - break; - } + result = BlsExtensions.TryDecodeG1ToBuffer(inputData, rawPoints.AsMemory(), rawScalars.AsMemory(), pointDestinations[i], i); } } else @@ -122,22 +117,20 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec Parallel.ForEach(pointDestinations, (dest, state, i) => { int index = (int)i; - if (!BlsExtensions.TryDecodeG1ToBuffer(inputData, rawPoints.AsMemory(), rawScalars.AsMemory(), dest, index)) + Result local = BlsExtensions.TryDecodeG1ToBuffer(inputData, rawPoints.AsMemory(), rawScalars.AsMemory(), dest, index); + if (!local) { - fail = true; + result = local; state.Break(); } }); } #pragma warning restore CS0162 // Unreachable code detected - if (fail) - { - return IPrecompile.Failure; - } + if (!result) return result.Error!; // compute res = rawPoints_0 * rawScalars_0 + rawPoints_1 * rawScalars_1 + ... G1 res = new G1(stackalloc long[G1.Sz]).MultiMult(rawPoints.AsSpan(), rawScalars.AsSpan(), npoints); - return (res.EncodeRaw(), true); + return res.EncodeRaw(); } } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G2AddPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G2AddPrecompile.cs index 734d86024490..23bf32ea6f85 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G2AddPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G2AddPrecompile.cs @@ -5,7 +5,6 @@ using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Specs; -using Nethermind.Evm.Precompiles; using G2 = Nethermind.Crypto.Bls.P2; namespace Nethermind.Evm.Precompiles.Bls; @@ -30,35 +29,29 @@ private G2AddPrecompile() public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 0L; [SkipLocalsInit] - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { Metrics.BlsG2AddPrecompile++; const int expectedInputLength = 2 * BlsConst.LenG2; - if (inputData.Length != expectedInputLength) - { - return IPrecompile.Failure; - } + if (inputData.Length != expectedInputLength) return Errors.InvalidInputLength; G2 x = new(stackalloc long[G2.Sz]); G2 y = new(stackalloc long[G2.Sz]); - if (!x.TryDecodeRaw(inputData[..BlsConst.LenG2].Span) || !y.TryDecodeRaw(inputData[BlsConst.LenG2..].Span)) - { - return IPrecompile.Failure; - } + Result result = x.TryDecodeRaw(inputData[..BlsConst.LenG2].Span) && + y.TryDecodeRaw(inputData[BlsConst.LenG2..].Span); - // adding to infinity point has no effect - if (x.IsInf()) + if (result) { - return (inputData[BlsConst.LenG2..].ToArray(), true); - } + // adding to infinity point has no effect + if (x.IsInf()) return inputData[BlsConst.LenG2..].ToArray(); - if (y.IsInf()) - { - return (inputData[..BlsConst.LenG2].ToArray(), true); + if (y.IsInf()) return inputData[..BlsConst.LenG2].ToArray(); + + G2 res = x.Add(y); + return res.EncodeRaw(); } - G2 res = x.Add(y); - return (res.EncodeRaw(), true); + return result.Error!; } } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G2MSMPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G2MSMPrecompile.cs index 8151e8c5f6ab..6c0d611449c3 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G2MSMPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G2MSMPrecompile.cs @@ -9,7 +9,6 @@ using G2 = Nethermind.Crypto.Bls.P2; using System.Runtime.CompilerServices; -using Nethermind.Evm.Precompiles; namespace Nethermind.Evm.Precompiles.Bls; @@ -39,46 +38,38 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec public const int ItemSize = 288; [SkipLocalsInit] - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { Metrics.BlsG2MSMPrecompile++; - if (inputData.Length % ItemSize > 0 || inputData.Length == 0) - { - return IPrecompile.Failure; - } + 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); } - - private (byte[], bool) Mul(ReadOnlyMemory inputData) + private Result Mul(ReadOnlyMemory inputData) { G2 x = new(stackalloc long[G2.Sz]); - if (!x.TryDecodeRaw(inputData[..BlsConst.LenG2].Span) || !(BlsConst.DisableSubgroupChecks || x.InGroup())) - { - return IPrecompile.Failure; - } + Result result = x.TryDecodeRaw(inputData[..BlsConst.LenG2].Span); + if (!result) return result.Error!; + if (!(BlsConst.DisableSubgroupChecks || x.InGroup())) return Errors.G2PointSubgroup; // multiplying by zero gives infinity point // any scalar multiplied by infinity point is infinity point bool scalarIsZero = !inputData[BlsConst.LenG2..].Span.ContainsAnyExcept((byte)0); - if (scalarIsZero || x.IsInf()) - { - return (BlsConst.G2Inf, true); - } + if (scalarIsZero || x.IsInf()) return BlsConst.G2Inf; Span scalar = stackalloc byte[32]; inputData.Span[BlsConst.LenG2..].CopyTo(scalar); scalar.Reverse(); G2 res = x.Mult(scalar); - return (res.EncodeRaw(), true); + return res.EncodeRaw(); } - private (byte[], bool) MSM(ReadOnlyMemory inputData, int nItems) + private Result MSM(ReadOnlyMemory inputData, int nItems) { using ArrayPoolList pointBuffer = new(nItems * G2.Sz, nItems * G2.Sz); using ArrayPoolList scalarBuffer = new(nItems * 32, nItems * 32); @@ -97,25 +88,18 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec } // only infinity points so return infinity - if (npoints == 0) - { - return (BlsConst.G2Inf, true); - } + if (npoints == 0) return BlsConst.G2Inf; - bool fail = false; + Result result = Result.Success; // decode points to rawPoints buffer // n.b. subgroup checks carried out as part of decoding #pragma warning disable CS0162 // Unreachable code detected if (BlsConst.DisableConcurrency) { - for (int i = 0; i < pointDestinations.Count; i++) + for (int i = 0; i < pointDestinations.Count && result; i++) { - if (!BlsExtensions.TryDecodeG2ToBuffer(inputData, pointBuffer.AsMemory(), scalarBuffer.AsMemory(), pointDestinations[i], i)) - { - fail = true; - break; - } + result = BlsExtensions.TryDecodeG2ToBuffer(inputData, pointBuffer.AsMemory(), scalarBuffer.AsMemory(), pointDestinations[i], i); } } else @@ -123,22 +107,20 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec Parallel.ForEach(pointDestinations, (dest, state, i) => { int index = (int)i; - if (!BlsExtensions.TryDecodeG2ToBuffer(inputData, pointBuffer.AsMemory(), scalarBuffer.AsMemory(), dest, index)) + Result local = BlsExtensions.TryDecodeG2ToBuffer(inputData, pointBuffer.AsMemory(), scalarBuffer.AsMemory(), dest, index); + if (!local) { - fail = true; + result = local; state.Break(); } }); } #pragma warning restore CS0162 // Unreachable code detected - if (fail) - { - return IPrecompile.Failure; - } + if (!result) return result.Error!; // compute res = rawPoints_0 * rawScalars_0 + rawPoints_1 * rawScalars_1 + ... G2 res = new G2(stackalloc long[G2.Sz]).MultiMult(pointBuffer.AsSpan(), scalarBuffer.AsSpan(), npoints); - return (res.EncodeRaw(), true); + return res.EncodeRaw(); } } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/MapFp2ToG2Precompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/MapFp2ToG2Precompile.cs index cac3f490ff51..0fe678a06349 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/MapFp2ToG2Precompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/MapFp2ToG2Precompile.cs @@ -5,7 +5,6 @@ using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Specs; -using Nethermind.Evm.Precompiles; using G2 = Nethermind.Crypto.Bls.P2; namespace Nethermind.Evm.Precompiles.Bls; @@ -30,28 +29,26 @@ private MapFp2ToG2Precompile() public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 0L; [SkipLocalsInit] - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { Metrics.BlsMapFp2ToG2Precompile++; const int expectedInputLength = 2 * BlsConst.LenFp; - if (inputData.Length != expectedInputLength) - { - return IPrecompile.Failure; - } + if (inputData.Length != expectedInputLength) return Errors.InvalidInputLength; G2 res = new(stackalloc long[G2.Sz]); - if (!BlsExtensions.ValidRawFp(inputData.Span[..BlsConst.LenFp]) || - !BlsExtensions.ValidRawFp(inputData.Span[BlsConst.LenFp..])) + Result result = BlsExtensions.ValidRawFp(inputData.Span[..BlsConst.LenFp]) && + BlsExtensions.ValidRawFp(inputData.Span[BlsConst.LenFp..]); + if (result) { - return IPrecompile.Failure; - } + // map field point to G2 + ReadOnlySpan fp0 = inputData[BlsConst.LenFpPad..BlsConst.LenFp].Span; + ReadOnlySpan fp1 = inputData[(BlsConst.LenFp + BlsConst.LenFpPad)..].Span; + res.MapTo(fp0, fp1); - // map field point to G2 - ReadOnlySpan fp0 = inputData[BlsConst.LenFpPad..BlsConst.LenFp].Span; - ReadOnlySpan fp1 = inputData[(BlsConst.LenFp + BlsConst.LenFpPad)..].Span; - res.MapTo(fp0, fp1); + return res.EncodeRaw(); + } - return (res.EncodeRaw(), true); + return result.Error!; } } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/MapFpToG1Precompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/MapFpToG1Precompile.cs index f3689b5b98f5..e56b96c3de17 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/MapFpToG1Precompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/MapFpToG1Precompile.cs @@ -5,7 +5,6 @@ using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Specs; -using Nethermind.Evm.Precompiles; using G1 = Nethermind.Crypto.Bls.P1; namespace Nethermind.Evm.Precompiles.Bls; @@ -30,26 +29,21 @@ private MapFpToG1Precompile() public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 0L; [SkipLocalsInit] - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { Metrics.BlsMapFpToG1Precompile++; const int expectedInputLength = BlsConst.LenFp; - if (inputData.Length != expectedInputLength) - { - return IPrecompile.Failure; - } + if (inputData.Length != expectedInputLength) return Errors.InvalidInputLength; G1 res = new(stackalloc long[G1.Sz]); - if (!BlsExtensions.ValidRawFp(inputData.Span)) - { - return IPrecompile.Failure; - } + Result result = BlsExtensions.ValidRawFp(inputData.Span); + if (!result) return result.Error!; // map field point to G1 ReadOnlySpan fp = inputData[BlsConst.LenFpPad..BlsConst.LenFp].Span; res.MapTo(fp); - return (res.EncodeRaw(), true); + return res.EncodeRaw(); } } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/PairingCheckPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/PairingCheckPrecompile.cs index de45b8838a92..0ae0148c7c25 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/PairingCheckPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/PairingCheckPrecompile.cs @@ -6,7 +6,6 @@ using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Specs; -using Nethermind.Evm.Precompiles; using G1 = Nethermind.Crypto.Bls.P1; using G2 = Nethermind.Crypto.Bls.P2; using GT = Nethermind.Crypto.Bls.PT; @@ -32,19 +31,16 @@ private PairingCheckPrecompile() { } public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 32600L * (inputData.Length / PairSize); [SkipLocalsInit] - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { Metrics.BlsPairingCheckPrecompile++; - if (inputData.Length % PairSize > 0 || inputData.Length == 0) - { - return IPrecompile.Failure; - } + if (inputData.Length % PairSize > 0 || inputData.Length == 0) return Errors.InvalidInputLength; G1 x = new(stackalloc long[G1.Sz]); G2 y = new(stackalloc long[G2.Sz]); - using ArrayPoolList buf = new(GT.Sz * 2, GT.Sz * 2); + using ArrayPoolListRef buf = new(GT.Sz * 2, GT.Sz * 2); var acc = GT.One(buf.AsSpan()); GT p = new(buf.AsSpan()[GT.Sz..]); @@ -52,32 +48,31 @@ private PairingCheckPrecompile() { } { int offset = i * PairSize; - if (!x.TryDecodeRaw(inputData[offset..(offset + BlsConst.LenG1)].Span) || - !(BlsConst.DisableSubgroupChecks || x.InGroup()) || - !y.TryDecodeRaw(inputData[(offset + BlsConst.LenG1)..(offset + PairSize)].Span) || - !(BlsConst.DisableSubgroupChecks || y.InGroup())) + Result result = x.TryDecodeRaw(inputData[offset..(offset + BlsConst.LenG1)].Span) && + y.TryDecodeRaw(inputData[(offset + BlsConst.LenG1)..(offset + PairSize)].Span); + + if (result) { - return IPrecompile.Failure; - } + if (!(BlsConst.DisableSubgroupChecks || x.InGroup())) return Errors.G1PointSubgroup; + if (!(BlsConst.DisableSubgroupChecks || y.InGroup())) return Errors.G2PointSubgroup; + + // x == inf || y == inf -> e(x, y) = 1 + if (x.IsInf() || y.IsInf()) continue; - // x == inf || y == inf -> e(x, y) = 1 - if (x.IsInf() || y.IsInf()) + // acc *= e(x, y) + p.MillerLoop(y, x); + acc.Mul(p); + } + else { - continue; + return result.Error!; } - - // acc *= e(x, y) - p.MillerLoop(y, x); - acc.Mul(p); } // e(x_0, y_0) * e(x_1, y_1) * ... == 1 byte[] res = new byte[32]; - if (acc.FinalExp().IsOne()) - { - res[31] = 1; - } + if (acc.FinalExp().IsOne()) res[31] = 1; - return (res, true); + return res; } } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/EcRecoverPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/EcRecoverPrecompile.cs index a828db494045..04eb1fe73cc8 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/EcRecoverPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/EcRecoverPrecompile.cs @@ -3,18 +3,19 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Crypto; -using Nethermind.Evm.Precompiles; namespace Nethermind.Evm.Precompiles; public class EcRecoverPrecompile : IPrecompile { public static readonly EcRecoverPrecompile Instance = new(); + private static readonly Result Empty = Array.Empty(); private EcRecoverPrecompile() { @@ -30,13 +31,13 @@ private EcRecoverPrecompile() private readonly byte[] _zero31 = new byte[31]; - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { Metrics.EcRecoverPrecompile++; return inputData.Length >= 128 ? RunInternal(inputData.Span) : RunInternal(inputData); } - private (byte[], bool) RunInternal(ReadOnlyMemory inputData) + private Result RunInternal(ReadOnlyMemory inputData) { Span inputDataSpan = stackalloc byte[128]; inputData.Span[..Math.Min(128, inputData.Length)] @@ -45,7 +46,7 @@ private EcRecoverPrecompile() return RunInternal(inputDataSpan); } - private (byte[], bool) RunInternal(ReadOnlySpan inputDataSpan) + private Result RunInternal(ReadOnlySpan inputDataSpan) { ReadOnlySpan vBytes = inputDataSpan.Slice(32, 32); @@ -53,24 +54,29 @@ private EcRecoverPrecompile() // TEST: CALLCODEEcrecoverV_prefixedf0_d1g0v0 if (!Bytes.AreEqual(_zero31, vBytes[..31])) { - return ([], true); + return Empty; } byte v = vBytes[31]; if (v != 27 && v != 28) { - return ([], true); + return Empty; } Span publicKey = stackalloc byte[65]; if (!EthereumEcdsa.RecoverAddressRaw(inputDataSpan.Slice(64, 64), Signature.GetRecoveryId(v), inputDataSpan[..32], publicKey)) { - return ([], true); + return Empty; } - byte[] result = ValueKeccak.Compute(publicKey.Slice(1, 64)).ToByteArray(); - result.AsSpan(0, 12).Clear(); - return (result, true); + 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/Errors.cs b/src/Nethermind/Nethermind.Evm.Precompiles/Errors.cs new file mode 100644 index 000000000000..6052ba650741 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Precompiles/Errors.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Evm.Precompiles; + +public static class Errors +{ + public const string InvalidInputLength = "invalid input length"; + public const string InvalidFieldLength = "invalid field element length"; + public const string InvalidFieldElementTopBytes = "invalid field element top bytes"; + public const string G1PointSubgroup = "g1 point is not on correct subgroup"; + public const string G2PointSubgroup = "g2 point is not on correct subgroup"; + + public const string InvalidFinalFlag = "invalid final flag"; + public const string L1StorageAccessFailed = "l1 storage access failed"; + + public const string Failed = "failed"; +} diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/Extensions.cs b/src/Nethermind/Nethermind.Evm.Precompiles/Extensions.cs index ebe22dcda94e..d5d6b96e70f4 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/IdentityPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/IdentityPrecompile.cs index f435e8886dbc..616e4be31b16 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/IdentityPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/IdentityPrecompile.cs @@ -4,8 +4,6 @@ using System; using Nethermind.Core; using Nethermind.Core.Specs; -using Nethermind.Evm; -using Nethermind.Evm.Precompiles; namespace Nethermind.Evm.Precompiles; @@ -21,13 +19,14 @@ private IdentityPrecompile() public static string Name => "ID"; + // Caching disabled: the copy operation is O(n) and the cache key hash is also O(n), + // making caching strictly worse than direct execution for this precompile. + public bool SupportsCaching => false; + public long BaseGasCost(IReleaseSpec releaseSpec) => 15L; public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 3L * EvmCalculations.Div32Ceiling((ulong)inputData.Length); - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) - { - return (inputData.ToArray(), true); - } + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => inputData.ToArray(); } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/L1SloadPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/L1SloadPrecompile.cs index 2775d3173213..e0f0e7f16f2d 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/L1SloadPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/L1SloadPrecompile.cs @@ -2,13 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Threading.Tasks; using Nethermind.Core; -using Nethermind.Core.Extensions; using Nethermind.Core.Specs; -using Nethermind.Evm.Precompiles; using Nethermind.Int256; -using Nethermind.Logging; namespace Nethermind.Evm.Precompiles; @@ -40,38 +36,25 @@ private L1SloadPrecompile() public long BaseGasCost(IReleaseSpec releaseSpec) => L1PrecompileConstants.FixedGasCost; - public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) - { - if (inputData.Length != L1PrecompileConstants.ExpectedInputLength) - { - return 0L; - } - - return L1PrecompileConstants.PerLoadGasCost; - } + public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => + inputData.Length != L1PrecompileConstants.ExpectedInputLength ? 0L : L1PrecompileConstants.PerLoadGasCost; - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { - if (inputData.Length != L1PrecompileConstants.ExpectedInputLength) - { - return IPrecompile.Failure; - } + if (inputData.Length != L1PrecompileConstants.ExpectedInputLength) return Errors.InvalidInputLength; - Address contractAddress = new Address(inputData.Span[..L1PrecompileConstants.AddressBytes]); - UInt256 storageKey = new UInt256(inputData.Span[L1PrecompileConstants.AddressBytes..(L1PrecompileConstants.AddressBytes + L1PrecompileConstants.StorageKeyBytes)], isBigEndian: true); - UInt256 blockNumber = new UInt256(inputData.Span[(L1PrecompileConstants.AddressBytes + L1PrecompileConstants.StorageKeyBytes)..], isBigEndian: true); + Address contractAddress = new(inputData.Span[..L1PrecompileConstants.AddressBytes]); + UInt256 storageKey = new(inputData.Span[L1PrecompileConstants.AddressBytes..(L1PrecompileConstants.AddressBytes + L1PrecompileConstants.StorageKeyBytes)], isBigEndian: true); + UInt256 blockNumber = new(inputData.Span[(L1PrecompileConstants.AddressBytes + L1PrecompileConstants.StorageKeyBytes)..], isBigEndian: true); UInt256? storageValue = GetL1StorageValue(contractAddress, storageKey, blockNumber); - if (storageValue == null) - { - return IPrecompile.Failure; // L1 storage access failed - } + if (storageValue is null) return Errors.L1StorageAccessFailed; // Convert storage value to output bytes byte[] output = new byte[32]; storageValue.Value.ToBigEndian().CopyTo(output.AsSpan()); - return (output, true); + return output; } /// diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs index 81e384a3db74..613282b79eb5 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs @@ -90,16 +90,19 @@ private static long DataGasCostShortInternal(ReadOnlySpan inputData, IRele private static long DataGasCostInternal(ReadOnlySpan inputData, IReleaseSpec releaseSpec) { (uint baseLength, uint expLength, uint modulusLength) = GetInputLengths(inputData); - if (ExceedsMaxInputSize(releaseSpec, baseLength, expLength, modulusLength)) - { - return long.MaxValue; - } - 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); @@ -117,8 +120,8 @@ private static long DataGasCostInternal(ReadOnlySpan inputData, IReleaseSp private static bool ExceedsMaxInputSize(IReleaseSpec releaseSpec, uint baseLength, uint expLength, uint modulusLength) { return releaseSpec.IsEip7823Enabled - ? (baseLength > ModExpMaxInputSizeEip7823 | expLength > ModExpMaxInputSizeEip7823 | modulusLength > ModExpMaxInputSizeEip7823) - : (baseLength | modulusLength) > int.MaxValue; + ? (baseLength > ModExpMaxInputSizeEip7823 || expLength > ModExpMaxInputSizeEip7823 || modulusLength > ModExpMaxInputSizeEip7823) + : baseLength >= uint.MaxValue || modulusLength >= uint.MaxValue; } [MethodImpl(MethodImplOptions.NoInlining)] @@ -135,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(); @@ -150,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(); @@ -189,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( @@ -201,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( @@ -227,7 +230,7 @@ private static (uint baseLength, uint expLength, uint modulusLength) GetInputLen return (0, uint.MaxValue, 0); } - public unsafe (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + public unsafe Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { Metrics.ModExpPrecompile++; @@ -237,11 +240,11 @@ public unsafe (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec re : GetInputLengthsShort(inputSpan); if (ExceedsMaxInputSize(releaseSpec, baseLength, expLength, modulusLength)) - return IPrecompile.Failure; + return "one or more of base/exponent/modulus length exceeded 1024 bytes"; // if both are 0, then expLength can be huge, which leads to a potential buffer too big exception if (baseLength == 0 && modulusLength == 0) - return (Bytes.Empty, true); + return Bytes.Empty; using var modulusInt = mpz_t.Create(); @@ -253,7 +256,7 @@ public unsafe (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec re } if (Gmp.mpz_sgn(modulusInt) == 0) - return (new byte[modulusLength], true); + return new byte[modulusLength]; using var baseInt = mpz_t.Create(); using var expInt = mpz_t.Create(); @@ -282,7 +285,7 @@ public unsafe (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec re fixed (byte* ptr = &MemoryMarshal.GetArrayDataReference(result)) Gmp.mpz_export((nint)(ptr + offset), out _, 1, 1, 1, nuint.Zero, powmResult); - return (result, true); + return result; } /// @@ -294,7 +297,7 @@ public unsafe (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec re /// private static ulong MultComplexity(uint baseLength, uint modulusLength, bool isEip7883Enabled) { - // Pick the larger of the two + // Pick the larger of the two uint max = baseLength > modulusLength ? baseLength : modulusLength; // Compute ceil(max/8) via a single add + shift diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompilePreEip2565.cs b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompilePreEip2565.cs index c646b8b40a60..1fe420566482 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompilePreEip2565.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompilePreEip2565.cs @@ -34,7 +34,7 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec try { return inputData.Length >= 96 - ? DataGasCostInternal(inputData.Span.Slice(0, 96), inputData, releaseSpec) + ? DataGasCostInternal(inputData.Span.Slice(0, 96), inputData) : DataGasCostInternal(inputData, releaseSpec); } catch (OverflowException) @@ -49,11 +49,10 @@ private static long DataGasCostInternal(ReadOnlyMemory inputData, IRelease inputData[..Math.Min(96, inputData.Length)].Span .CopyTo(extendedInput[..Math.Min(96, inputData.Length)]); - return DataGasCostInternal(extendedInput, inputData, releaseSpec); + return DataGasCostInternal(extendedInput, inputData); } - private static long DataGasCostInternal(ReadOnlySpan extendedInput, ReadOnlyMemory inputData, - IReleaseSpec releaseSpec) + private static long DataGasCostInternal(ReadOnlySpan extendedInput, ReadOnlyMemory inputData) { UInt256 baseLength = new(extendedInput[..32], true); UInt256 expLength = new(extendedInput.Slice(32, 32), true); @@ -70,7 +69,7 @@ private static long DataGasCostInternal(ReadOnlySpan extendedInput, ReadOn return gas > long.MaxValue ? long.MaxValue : (long)gas; } - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { Metrics.ModExpPrecompile++; @@ -84,13 +83,13 @@ private static long DataGasCostInternal(ReadOnlySpan extendedInput, ReadOn if (modulusInt.IsZero) { - return (new byte[modulusLength], true); + return new byte[modulusLength]; } BigInteger baseInt = inputData.Span.SliceWithZeroPaddingEmptyOnError(96, baseLength).ToUnsignedBigInteger(); BigInteger expInt = inputData.Span.SliceWithZeroPaddingEmptyOnError(96 + baseLength, expLength) .ToUnsignedBigInteger(); - return (BigInteger.ModPow(baseInt, expInt, modulusInt).ToBigEndianByteArray(modulusLength), true); + return BigInteger.ModPow(baseInt, expInt, modulusInt).ToBigEndianByteArray(modulusLength); } private static UInt256 MultComplexity(in UInt256 adjustedExponentLength) diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/PointEvaluationPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/PointEvaluationPrecompile.cs index 534113b911fe..bf2305bdc33b 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/PointEvaluationPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/PointEvaluationPrecompile.cs @@ -8,7 +8,6 @@ using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Crypto; -using Nethermind.Evm.Precompiles; using Nethermind.Int256; namespace Nethermind.Evm.Precompiles; @@ -30,7 +29,7 @@ public class PointEvaluationPrecompile : IPrecompile public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 0; - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -56,7 +55,7 @@ static bool IsValid(ReadOnlyMemory inputData) Metrics.PointEvaluationPrecompile++; return IsValid(inputData) - ? (PointEvaluationSuccessfulResponse, true) - : IPrecompile.Failure; + ? PointEvaluationSuccessfulResponse + : Errors.Failed; } } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/Ripemd160Precompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/Ripemd160Precompile.cs index a4977a63c8c4..d58e6e3cf6f8 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/Ripemd160Precompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/Ripemd160Precompile.cs @@ -3,11 +3,8 @@ using System; using Nethermind.Core; -using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Crypto; -using Nethermind.Evm; -using Nethermind.Evm.Precompiles; namespace Nethermind.Evm.Precompiles; @@ -34,10 +31,9 @@ private Ripemd160Precompile() public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 120L * EvmCalculations.Div32Ceiling((ulong)inputData.Length); - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { Metrics.Ripemd160Precompile++; - - return (Ripemd.Compute(inputData.ToArray()).PadLeft(32), true); + return Ripemd.Compute(inputData.Span); } } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/Secp256r1Precompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/Secp256r1Precompile.cs index ee1983024620..4b7cca1ef47c 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/Secp256r1Precompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/Secp256r1Precompile.cs @@ -6,7 +6,6 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Crypto; -using Nethermind.Evm.Precompiles; namespace Nethermind.Evm.Precompiles; @@ -22,13 +21,9 @@ public class Secp256r1Precompile : IPrecompile public long BaseGasCost(IReleaseSpec releaseSpec) => releaseSpec.IsEip7951Enabled ? 6900L : 3450L; public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 0L; - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { Metrics.Secp256r1Precompile++; - - return ( - Secp256r1.VerifySignature(inputData) ? ValidResult : [], - true - ); + return Secp256r1.VerifySignature(inputData) ? ValidResult : []; } } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/Sha256Precompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/Sha256Precompile.cs index 4d05ebdec32f..338c9963c6de 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/Sha256Precompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/Sha256Precompile.cs @@ -5,8 +5,6 @@ using System.Security.Cryptography; using Nethermind.Core; using Nethermind.Core.Specs; -using Nethermind.Evm; -using Nethermind.Evm.Precompiles; namespace Nethermind.Evm.Precompiles; @@ -27,13 +25,12 @@ private Sha256Precompile() public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 12L * EvmCalculations.Div32Ceiling((ulong)inputData.Length); - public (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { Metrics.Sha256Precompile++; byte[] output = new byte[SHA256.HashSizeInBytes]; bool success = SHA256.TryHashData(inputData.Span, output, out int bytesWritten); - - return (output, success && bytesWritten == SHA256.HashSizeInBytes); + return success && bytesWritten == SHA256.HashSizeInBytes ? output : Errors.Failed; } } diff --git a/src/Nethermind/Nethermind.Evm.Test/BytecodeBuilderExtensionsTests.cs b/src/Nethermind/Nethermind.Evm.Test/BytecodeBuilderExtensionsTests.cs index 0af189691f47..993f303edf11 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/CallTests.cs b/src/Nethermind/Nethermind.Evm.Test/CallTests.cs new file mode 100644 index 000000000000..086752b11ad9 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Test/CallTests.cs @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Specs; +using NUnit.Framework; + +namespace Nethermind.Evm.Test +{ + public class CallTests : VirtualMachineTestsBase + { + protected override long BlockNumber => MainnetSpecProvider.ParisBlockNumber; + protected override ulong Timestamp => MainnetSpecProvider.OsakaBlockTimestamp; + + [Test] + [TestCase(Instruction.CALL)] + [TestCase(Instruction.CALLCODE)] + [TestCase(Instruction.DELEGATECALL)] + [TestCase(Instruction.STATICCALL)] + public void Stack_underflow_on_call(Instruction instruction) + { + byte[] code = Prepare.EvmCode + .PushData(0) + .PushData(0) + .PushData("0x805e0d3cde3764a4d0a02f33cf624c8b7cfd911a") + .PushData("0x793d1e") + .Op(instruction) + .Done; + + TestAllTracerWithOutput result = Execute(Activation, 21020, code); + Assert.That(result.Error, Is.EqualTo("StackUnderflow")); + } + + [Test] + [TestCase(Instruction.CALL)] + [TestCase(Instruction.CALLCODE)] + [TestCase(Instruction.DELEGATECALL)] + [TestCase(Instruction.STATICCALL)] + public void Out_of_gas_on_call(Instruction instruction) + { + byte[] code = Prepare.EvmCode + .PushData(0) + .PushData(0) + .PushData("0x805e0d3cde3764a4d0a02f33cf624c8b7cfd911a") + .PushData("0x793d1e") + .PushData("0x793d1e") + .PushData("0x793d1e") + .PushData("0x793d1e") + .Op(instruction) + .Done; + + TestAllTracerWithOutput result = Execute(Activation, 21020, code); + Assert.That(result.Error, Is.EqualTo("OutOfGas")); + } + } +} diff --git a/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs b/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs index a297fe3945b6..b8da26cbc008 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()); + CodeInfo 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 621fa480743d..1f84f9e6fba0 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/Eip1014Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip1014Tests.cs index 88a1ca8fa941..aca03a9393f7 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip1014Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip1014Tests.cs @@ -9,7 +9,6 @@ using Nethermind.Specs; using Nethermind.Evm.State; using Nethermind.Core.Test.Builders; -using Nethermind.Trie; using NUnit.Framework; namespace Nethermind.Evm.Test diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip1153Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip1153Tests.cs index 792f53e56ba4..158ec5da7634 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 @@ -535,7 +535,7 @@ public void tstore_from_static_reentrant_call(Instruction callType, int expected .Op(Instruction.CALLER) .PushData(TestItem.AddressD) .Op(Instruction.EQ) - .PushData(113) + .PushData(callType == Instruction.CALL ? 113 : 111) .Op(Instruction.JUMPI) // Non-reentrant, call self after TSTORE 8 @@ -589,7 +589,7 @@ public void tstore_from_nonstatic_reentrant_call_with_static_intermediary(Instru // See if we're at call depth 1 .PushData(1) .Op(Instruction.EQ) - .PushData(84) + .PushData(callType == Instruction.CALL ? 84 : 82) .Op(Instruction.JUMPI) // See if we're at call depth 2 @@ -597,7 +597,7 @@ public void tstore_from_nonstatic_reentrant_call_with_static_intermediary(Instru .Op(Instruction.MLOAD) .PushData(2) .Op(Instruction.EQ) - .PushData(140) + .PushData(callType == Instruction.CALL ? 140 : 138) .Op(Instruction.JUMPI) // Call depth = 0, call self after TSTORE 8 @@ -787,7 +787,7 @@ public void tload_from_static_reentrant_call(Instruction callType, int expectedR .Op(Instruction.CALLER) .PushData(TestItem.AddressD) .Op(Instruction.EQ) - .PushData(114) + .PushData(callType == Instruction.CALL ? 114 : 112) .Op(Instruction.JUMPI) // Non-reentrant, call self after TSTORE 8 diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip1283Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip1283Tests.cs index 995cdf9d0431..0f75f89071d1 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 2431e6ae1672..dae7f1845b30 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 e0286a9a48d7..cb82a017a796 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 f7e749380a7e..df8b99a330d6 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 b91632f29ab5..6a506066113c 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip2537Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip2537Tests.cs @@ -1,9 +1,9 @@ // 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; using Nethermind.Specs; namespace Nethermind.Evm.Test; diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip2565Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip2565Tests.cs index 14e715eb990b..37816776daee 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 02869cbc12e3..56d691719345 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip2935Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip2935Tests.cs @@ -6,7 +6,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; -using Nethermind.Evm.Tracing; +using Nethermind.Evm.State; using Nethermind.Evm.TransactionProcessing; using Nethermind.Specs; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip3198BaseFeeTests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip3198BaseFeeTests.cs index 6f65342dcc83..22bc16baa34d 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 d170bf21d2cf..09d1ce61a4a4 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 d9585be6ec86..d28235fa0aca 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/Eip3588Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip3588Tests.cs index c68e8fababe2..b26bb461fd17 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip3588Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip3588Tests.cs @@ -52,12 +52,12 @@ public void Test_Eip3855_should_fail(int repeat, bool isShanghai) if (isShanghai && repeat > 1024) // should fail because of stackoverflow (exceeds stack limit of 1024) { - receipt.Error.Should().Be(EvmExceptionType.StackOverflow.ToString()); + receipt.Error.Should().Be(nameof(EvmExceptionType.StackOverflow)); } if (!isShanghai) // should fail because of bad instruction (push zero is an EIP-3540 new instruction) { - receipt.Error.Should().Be(EvmExceptionType.BadInstruction.ToString()); + receipt.Error.Should().Be(nameof(EvmExceptionType.BadInstruction)); } } } diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip3860Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip3860Tests.cs index 4e3b3223c7b2..b02611c91072 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 2722a62827f5..12d4704a5d28 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 74d5b538a982..5344bdae0d48 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 b67e8283c53a..674159d89f2f 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/Eip7823Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip7823Tests.cs index 578e214fbbfe..8955d5d61cba 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip7823Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip7823Tests.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Buffers.Binary; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -44,10 +43,6 @@ public void TestEip7823(bool isEip7823Enabled, bool success, uint inputBaseLengt { Assert.That(gas, Is.LessThan(long.MaxValue)); } - else - { - Assert.That(gas, Is.EqualTo(long.MaxValue)); - } } [TestCase("0x9e5faafc")] diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip7883Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip7883Tests.cs index b132abae8da1..70680e4f47e7 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip7883Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip7883Tests.cs @@ -58,11 +58,11 @@ private static IEnumerable Eip7883TestCases() yield return new Eip7883TestCase { Lp = 5, FusakaEnabled = true, BaseLength = 32, ExpLength = 1024, ModulusLength = 1024, Result = 528449536L }; yield return new Eip7883TestCase - { Lp = 6, FusakaEnabled = true, BaseLength = 10000, ExpLength = 1024, ModulusLength = 32, Result = long.MaxValue }; + { Lp = 6, FusakaEnabled = true, BaseLength = 10000, ExpLength = 1024, ModulusLength = 32, Result = 50396875000L }; yield return new Eip7883TestCase - { Lp = 7, FusakaEnabled = true, BaseLength = 1024, ExpLength = 10000, ModulusLength = 1024, Result = long.MaxValue }; + { Lp = 7, FusakaEnabled = true, BaseLength = 1024, ExpLength = 10000, ModulusLength = 1024, Result = 5234458624L }; yield return new Eip7883TestCase - { Lp = 8, FusakaEnabled = true, BaseLength = 1024, ExpLength = 1024, ModulusLength = 10000, Result = long.MaxValue }; + { Lp = 8, FusakaEnabled = true, BaseLength = 1024, ExpLength = 1024, ModulusLength = 10000, Result = 50396875000L }; yield return new Eip7883TestCase // testing exponent >32bytes { Lp = 9, FusakaEnabled = true, BaseLength = 8, ExpLength = 81, ModulusLength = 8, Result = 16624L }; yield return new Eip7883TestCase // testing base/modulo below 32 bytes diff --git a/src/Nethermind/Nethermind.Evm.Test/EvmMemoryTestsBase.cs b/src/Nethermind/Nethermind.Evm.Test/EvmMemoryTestsBase.cs index c4b6c63bfc16..7838ed213efc 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 47b649cff512..6989afb2b289 100644 --- a/src/Nethermind/Nethermind.Evm.Test/EvmPooledMemoryTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/EvmPooledMemoryTests.cs @@ -19,7 +19,6 @@ using FluentAssertions; using Nethermind.Blockchain; using Nethermind.Core.Test; -using Nethermind.State; using NUnit.Framework; namespace Nethermind.Evm.Test; @@ -54,20 +53,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 +244,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 +259,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 +272,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 +318,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 f19ce925c27d..000000000000 --- 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 2705534a68ff..67ba2f888528 100644 --- a/src/Nethermind/Nethermind.Evm.Test/GasPriceExtractorTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/GasPriceExtractorTests.cs @@ -1,14 +1,12 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Linq; using FluentAssertions; using Nethermind.Blockchain.Tracing; using Nethermind.Core; using Nethermind.Specs; using Nethermind.Core.Test.Builders; -using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; using Nethermind.Serialization.Rlp; using NUnit.Framework; @@ -35,7 +33,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 cac1206d6301..23d53fa55401 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/L1SloadPrecompileTests.cs b/src/Nethermind/Nethermind.Evm.Test/L1SloadPrecompileTests.cs index eb986f3493af..29ebb4d1334f 100644 --- a/src/Nethermind/Nethermind.Evm.Test/L1SloadPrecompileTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/L1SloadPrecompileTests.cs @@ -2,13 +2,10 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; -using System.Linq; using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Evm.Precompiles; using Nethermind.Int256; -using Nethermind.Logging; using Nethermind.Specs; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Evm.Test/OverridableEnv/DisposableScopeOverridableEnvTests.cs b/src/Nethermind/Nethermind.Evm.Test/OverridableEnv/DisposableScopeOverridableEnvTests.cs index 196605517be7..c06f9b8cb89c 100644 --- a/src/Nethermind/Nethermind.Evm.Test/OverridableEnv/DisposableScopeOverridableEnvTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/OverridableEnv/DisposableScopeOverridableEnvTests.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; using Autofac; using FluentAssertions; @@ -9,71 +10,101 @@ using Nethermind.Core.Test.Builders; using Nethermind.Core.Test.Modules; using Nethermind.Evm.State; -using Nethermind.State.OverridableEnv; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; using Nethermind.State; +using Nethermind.State.OverridableEnv; using NUnit.Framework; namespace Nethermind.Evm.Test.OverridableEnv; +[Parallelizable(ParallelScope.All)] public class DisposableScopeOverridableEnvTests { [Test] - public void TestCreate() + public void Create_ReturnsEnvWithOverriddenComponents() + { + using TestContext ctx = new(); + + ctx.ChildComponents.WorldState.Should().NotBe(ctx.WorldStateManager.GlobalWorldState); + ctx.ChildComponents.StateReader.Should().NotBe(ctx.WorldStateManager.GlobalStateReader); + ctx.ChildComponents.CodeInfoRepository.Should().BeAssignableTo(); + ctx.ChildComponents.TransactionProcessor.Should().BeAssignableTo(); + ((TestTransactionProcessor)ctx.ChildComponents.TransactionProcessor).WorldState.Should().Be(ctx.ChildComponents.WorldState); + } + + [Test] + public void BuildAndOverride_WithBalanceOverride_AppliesStateCorrectly() { - using IContainer container = new ContainerBuilder() - .AddModule(new TestNethermindModule()) - .AddScoped() - .Add() - .Build(); - - IOverridableEnvFactory envFactory = container.Resolve(); - IWorldStateManager worldStateManager = container.Resolve(); - ILifetimeScope rootLifetime = container.Resolve(); - IOverridableEnv env = envFactory.Create(); - - using ILifetimeScope childLifetime = rootLifetime.BeginLifetimeScope(builder => builder.AddModule(env)); - - Components childComponents = childLifetime.Resolve(); - childComponents.WorldState.Should().NotBe(worldStateManager.GlobalWorldState); - childComponents.StateReader.Should().NotBe(worldStateManager.GlobalStateReader); - childComponents.CodeInfoRepository.Should().BeAssignableTo(); - childComponents.TransactionProcessor.Should().BeAssignableTo(); - ((TestTransactionProcessor)childComponents.TransactionProcessor).WorldState.Should().Be(childComponents.WorldState); + using TestContext ctx = new(); + + using Scope scope = ctx.Env.BuildAndOverride( + Build.A.BlockHeader.TestObject, + new Dictionary + { + { TestItem.AddressA, new AccountOverride { Balance = 123 } } + }); + + ctx.ChildComponents.WorldState.StateRoot.Should().NotBe(Keccak.EmptyTreeHash); + scope.Component.WorldState.GetBalance(TestItem.AddressA).Should().Be(123); } [Test] - public void TestOverriddenState() + public void BuildAndOverride_AfterExceptionFromInvalidStateOverride_CanBeCalledAgain() { - using IContainer container = new ContainerBuilder() - .AddModule(new TestNethermindModule()) - .AddScoped() - .Add() - .Build(); + using TestContext ctx = new(); + + Action invalidOverride = () => ctx.Env.BuildAndOverride( + Build.A.BlockHeader.TestObject, + new Dictionary + { + { TestItem.AddressA, new AccountOverride { MovePrecompileToAddress = TestItem.AddressB } } + }); + + invalidOverride.Should().Throw() + .WithMessage($"Account {TestItem.AddressA} is not a precompile"); + + using Scope scope = ctx.Env.BuildAndOverride( + Build.A.BlockHeader.TestObject, + new Dictionary + { + { TestItem.AddressA, new AccountOverride { Balance = 456 } } + }); + + scope.Component.WorldState.GetBalance(TestItem.AddressA).Should().Be(456); + } - IOverridableEnvFactory envFactory = container.Resolve(); - ILifetimeScope rootLifetime = container.Resolve(); - IOverridableEnv envModule = envFactory.Create(); - using ILifetimeScope childLifetime = rootLifetime.BeginLifetimeScope(builder => builder.AddModule(envModule)); + private sealed class TestContext : IDisposable + { + private readonly IContainer _container; + private readonly ILifetimeScope _childLifetime; - Components childComponents = childLifetime.Resolve(); - IOverridableEnv env = childLifetime.Resolve>(); + public IWorldStateManager WorldStateManager { get; } + public Components ChildComponents { get; } + public IOverridableEnv Env { get; } + + public TestContext() + { + _container = new ContainerBuilder() + .AddModule(new TestNethermindModule()) + .AddScoped() + .Add() + .Build(); + + WorldStateManager = _container.Resolve(); + IOverridableEnvFactory envFactory = _container.Resolve(); + ILifetimeScope rootLifetime = _container.Resolve(); + IOverridableEnv envModule = envFactory.Create(); + + _childLifetime = rootLifetime.BeginLifetimeScope(builder => builder.AddModule(envModule)); + ChildComponents = _childLifetime.Resolve(); + Env = _childLifetime.Resolve>(); + } + public void Dispose() { - using var scope = env.BuildAndOverride(Build.A.BlockHeader.TestObject, - new Dictionary() - { - { - TestItem.AddressA, new AccountOverride() - { - Balance = 123 - } - } - }); - - childComponents.WorldState.StateRoot.Should().NotBe(Keccak.EmptyTreeHash); - scope.Component.WorldState.GetBalance(TestItem.AddressA).Should().Be(123); + _childLifetime.Dispose(); + _container.Dispose(); } } @@ -88,39 +119,25 @@ private class TestTransactionProcessor(IWorldState worldState) : ITransactionPro { public IWorldState WorldState => worldState; - public TransactionResult Execute(Transaction transaction, ITxTracer txTracer) - { - throw new System.NotImplementedException(); - } + public TransactionResult Execute(Transaction transaction, ITxTracer txTracer) => + throw new NotImplementedException(); - public TransactionResult CallAndRestore(Transaction transaction, ITxTracer txTracer) - { - throw new System.NotImplementedException(); - } + public TransactionResult CallAndRestore(Transaction transaction, ITxTracer txTracer) => + throw new NotImplementedException(); - public TransactionResult BuildUp(Transaction transaction, ITxTracer txTracer) - { - throw new System.NotImplementedException(); - } + public TransactionResult BuildUp(Transaction transaction, ITxTracer txTracer) => + throw new NotImplementedException(); - public TransactionResult Trace(Transaction transaction, ITxTracer txTracer) - { - throw new System.NotImplementedException(); - } + public TransactionResult Trace(Transaction transaction, ITxTracer txTracer) => + throw new NotImplementedException(); - public TransactionResult Warmup(Transaction transaction, ITxTracer txTracer) - { - throw new System.NotImplementedException(); - } + public TransactionResult Warmup(Transaction transaction, ITxTracer txTracer) => + throw new NotImplementedException(); - public void SetBlockExecutionContext(BlockHeader blockHeader) - { - throw new System.NotImplementedException(); - } + public void SetBlockExecutionContext(BlockHeader blockHeader) => + throw new NotImplementedException(); - public void SetBlockExecutionContext(in BlockExecutionContext blockExecutionContext) - { - throw new System.NotImplementedException(); - } + public void SetBlockExecutionContext(in BlockExecutionContext blockExecutionContext) => + throw new NotImplementedException(); } } diff --git a/src/Nethermind/Nethermind.Evm.Test/PrecompileTests.cs b/src/Nethermind/Nethermind.Evm.Test/PrecompileTests.cs index a21707992139..0e08b9985bfa 100644 --- a/src/Nethermind/Nethermind.Evm.Test/PrecompileTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/PrecompileTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using Nethermind.Core; using Nethermind.Evm.Precompiles; using Nethermind.Serialization.Json; using Nethermind.Specs.Forks; @@ -18,7 +19,7 @@ public record TestCase(byte[] Input, byte[]? Expected, string Name, long? Gas, s private static IEnumerable TestSource() { - EthereumJsonSerializer serializer = new EthereumJsonSerializer(); + EthereumJsonSerializer serializer = new(); foreach (string file in T.TestFiles()) { string path = Path.Combine(TestFilesDirectory, file); @@ -37,7 +38,8 @@ public void TestVectors(TestCase testCase) IPrecompile precompile = T.Precompile(); long gas = precompile.BaseGasCost(Prague.Instance) + precompile.DataGasCost(testCase.Input, Prague.Instance); - (byte[] output, bool success) = precompile.Run(testCase.Input, Prague.Instance); + Result result = precompile.Run(testCase.Input, Prague.Instance); + (byte[]? output, bool success) = result; using (Assert.EnterMultipleScope()) { diff --git a/src/Nethermind/Nethermind.Evm.Test/SupportsCachingTests.cs b/src/Nethermind/Nethermind.Evm.Test/SupportsCachingTests.cs new file mode 100644 index 000000000000..09e969933480 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Test/SupportsCachingTests.cs @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Evm.Precompiles; +using Nethermind.Evm.Precompiles.Bls; +using NUnit.Framework; + +namespace Nethermind.Evm.Test; + +[TestFixture] +public class SupportsCachingTests +{ + public static IEnumerable PrecompilesWithCachingEnabled() + { + yield return new TestCaseData(EcRecoverPrecompile.Instance).SetName(nameof(EcRecoverPrecompile)); + yield return new TestCaseData(Sha256Precompile.Instance).SetName(nameof(Sha256Precompile)); + yield return new TestCaseData(Ripemd160Precompile.Instance).SetName(nameof(Ripemd160Precompile)); + yield return new TestCaseData(BN254AddPrecompile.Instance).SetName(nameof(BN254AddPrecompile)); + yield return new TestCaseData(BN254MulPrecompile.Instance).SetName(nameof(BN254MulPrecompile)); + yield return new TestCaseData(BN254PairingPrecompile.Instance).SetName(nameof(BN254PairingPrecompile)); + yield return new TestCaseData(ModExpPrecompile.Instance).SetName(nameof(ModExpPrecompile)); + yield return new TestCaseData(Blake2FPrecompile.Instance).SetName(nameof(Blake2FPrecompile)); + yield return new TestCaseData(G1AddPrecompile.Instance).SetName(nameof(G1AddPrecompile)); + yield return new TestCaseData(G1MSMPrecompile.Instance).SetName(nameof(G1MSMPrecompile)); + yield return new TestCaseData(G2AddPrecompile.Instance).SetName(nameof(G2AddPrecompile)); + yield return new TestCaseData(G2MSMPrecompile.Instance).SetName(nameof(G2MSMPrecompile)); + yield return new TestCaseData(PairingCheckPrecompile.Instance).SetName(nameof(PairingCheckPrecompile)); + yield return new TestCaseData(MapFpToG1Precompile.Instance).SetName(nameof(MapFpToG1Precompile)); + yield return new TestCaseData(MapFp2ToG2Precompile.Instance).SetName(nameof(MapFp2ToG2Precompile)); + yield return new TestCaseData(PointEvaluationPrecompile.Instance).SetName(nameof(PointEvaluationPrecompile)); + yield return new TestCaseData(Secp256r1Precompile.Instance).SetName(nameof(Secp256r1Precompile)); + } + + [TestCaseSource(nameof(PrecompilesWithCachingEnabled))] + public void Precompile_SupportsCaching_ReturnsTrue_ByDefault(IPrecompile precompile) + { + Assert.That(precompile.SupportsCaching, Is.True); + } + + [Test] + public void IdentityPrecompile_SupportsCaching_ReturnsFalse() + { + Assert.That(IdentityPrecompile.Instance.SupportsCaching, Is.False); + } +} diff --git a/src/Nethermind/Nethermind.Evm.Test/Tracing/AccessTxTracerTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/AccessTxTracerTests.cs index 7be709f27d22..59fc7149053c 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/BlockReceiptsTracerTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/BlockReceiptsTracerTests.cs index 785b7a85fcc2..c1c4824a145e 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Tracing/BlockReceiptsTracerTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Tracing/BlockReceiptsTracerTests.cs @@ -6,7 +6,6 @@ using Nethermind.Core; using Nethermind.Core.Test.Builders; using Nethermind.Evm.Tracing; -using Nethermind.Evm.TransactionProcessing; using NSubstitute; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Evm.Test/Tracing/CancellationTracerTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/CancellationTracerTests.cs index 0cb2d8461fca..03cc9cd3dc92 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Tracing/CancellationTracerTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Tracing/CancellationTracerTests.cs @@ -18,17 +18,14 @@ public class CancellationTracerTests { [Test] [Retry(3)] - public async Task Throw_operation_canceled_after_given_timeout() + public void Throw_operation_canceled_after_given_timeout() { TimeSpan timeout = TimeSpan.FromMilliseconds(10); using CancellationTokenSource cancellationTokenSource = new(timeout); CancellationToken cancellationToken = cancellationTokenSource.Token; CancellationTxTracer tracer = new(Substitute.For(), cancellationToken) { IsTracingActions = true }; - // ReSharper disable once MethodSupportsCancellation - await Task.Delay(100); - - Assert.Throws(() => tracer.ReportActionError(EvmExceptionType.None)); + Assert.That(() => tracer.ReportActionError(EvmExceptionType.None), Throws.TypeOf().After(100, 10)); } [Test] diff --git a/src/Nethermind/Nethermind.Evm.Test/Tracing/CompositeBlockTracerTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/CompositeBlockTracerTests.cs index 5b7e1946ad02..8dbab0f5a2dc 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Tracing/CompositeBlockTracerTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Tracing/CompositeBlockTracerTests.cs @@ -7,7 +7,6 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Test.Builders; -using Nethermind.Evm.Tracing; using Nethermind.Blockchain.Tracing.GethStyle; using Nethermind.Blockchain.Tracing.ParityStyle; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Evm.Test/Tracing/DebugTracerTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/DebugTracerTests.cs index e53fd52c78a3..c4bfadd34fa1 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 5552059366dc..16572c02c27b 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs @@ -18,7 +18,6 @@ using Nethermind.Logging; using Nethermind.Specs; using Nethermind.Evm.State; -using Nethermind.State; using NSubstitute; using NUnit.Framework; @@ -506,6 +505,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 +604,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 +614,658 @@ 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(); + } + + [Test] + public void Should_estimate_gas_when_inner_call_reverts_but_transaction_succeeds() + { + // Reproduces https://github.com/NethermindEth/nethermind/issues/10552 + // GnosisSafe createProxyWithNonce has inner calls that revert (try/catch pattern). + // The bug: ReportOperationError sets OutOfGas=true for ANY revert, even inner ones, + // causing the binary search in gas estimation to always think the tx failed. + using TestEnvironment testEnvironment = new(); + + Address reverterAddress = TestItem.AddressB; + Address callerAddress = TestItem.AddressC; + + // Reverter contract: always reverts with empty data + byte[] reverterCode = Prepare.EvmCode + .PushData(0x00) + .PushData(0x00) + .Op(Instruction.REVERT) + .Done; + testEnvironment.InsertContract(reverterAddress, reverterCode); + + // Caller contract: CALLs reverter (which reverts), catches the revert, then succeeds. + // This simulates GnosisSafe's try/catch pattern. + byte[] callerCode = Prepare.EvmCode + .Call(reverterAddress, 100_000) // inner call that reverts - return value 0 on stack + .Op(Instruction.POP) // discard call result + .PushData(0x01) // value = 1 + .PushData(0x00) // key = 0 + .Op(Instruction.SSTORE) // store 1 at slot 0 (proves execution continued) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(callerAddress, callerCode); + + long gasLimit = 300_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(callerAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ByzantiumBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().BeGreaterThan(0, "Gas estimation should succeed when inner call reverts but transaction succeeds overall"); + err.Should().BeNull("No error should occur - inner reverts should not be treated as top-level failures"); + } + + [Test] + public void Should_estimate_gas_for_create2_with_setup_call_pattern() + { + // Simulates GnosisSafe createProxyWithNonce: CREATE2 deploys a proxy, then + // the caller does a CALL to the newly deployed proxy for setup. + // The setup call may revert internally but the overall tx succeeds. + using TestEnvironment testEnvironment = new(); + + // The "proxy" runtime code: just stores a value (simulating successful setup) + byte[] proxyRuntimeCode = Prepare.EvmCode + .PushData(0x42) // value + .PushData(0x00) // key + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + + // Init code that returns the runtime code + byte[] initCode = Prepare.EvmCode + .ForInitOf(proxyRuntimeCode) + .Done; + + // Factory contract: CREATE2 the proxy, then CALL setup on it + // CREATE2(value=0, offset=0, size=initCode.length, salt=0) + // CALL(gas, addr_from_create2, value=0, ...) + byte[] factoryCode = Prepare.EvmCode + .Create2(initCode, new byte[] { 0x01 }, 0) // CREATE2 with salt=1 + .Op(Instruction.DUP1) // duplicate address for CALL + .PushData(0x00) // retSize + .PushData(0x00) // retOffset + .PushData(0x00) // argSize + .PushData(0x00) // argOffset + .PushData(0x00) // value + .Op(Instruction.SWAP5) // bring address to top (after value) + .PushData(50_000) // gas for setup call + .Op(Instruction.CALL) + .Op(Instruction.POP) // discard call result + .Op(Instruction.POP) // discard remaining address copy + .Op(Instruction.STOP) + .Done; + + Address factoryAddress = TestItem.AddressB; + testEnvironment.InsertContract(factoryAddress, factoryCode); + + long gasLimit = 500_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(factoryAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ConstantinopleFixBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().BeGreaterThan(0, "Gas estimation should succeed for CREATE2 + setup call pattern"); + err.Should().BeNull("No error for CREATE2 + setup call"); + } + + [Test] + public void Should_estimate_gas_with_multiple_inner_calls_mixed_reverts() + { + // Contract makes 3 inner calls: first reverts, second succeeds, third reverts. + // Transaction should still succeed and gas estimation should work. + using TestEnvironment testEnvironment = new(); + + Address reverterAddress = TestItem.AddressB; + Address succeederAddress = TestItem.AddressC; + + // Contract that always reverts + byte[] reverterCode = Prepare.EvmCode + .Revert(0, 0) + .Done; + testEnvironment.InsertContract(reverterAddress, reverterCode); + + // Contract that succeeds (stores value) + byte[] succeederCode = Prepare.EvmCode + .PushData(0x01) + .PushData(0x00) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(succeederAddress, succeederCode); + + // Caller: calls reverter, succeeder, reverter - catches all failures + Address callerAddress = TestItem.AddressD; + byte[] callerCode = Prepare.EvmCode + .Call(reverterAddress, 30_000) // call 1: reverts + .Op(Instruction.POP) + .Call(succeederAddress, 50_000) // call 2: succeeds + .Op(Instruction.POP) + .Call(reverterAddress, 30_000) // call 3: reverts + .Op(Instruction.POP) + .PushData(0xFF) + .PushData(0x01) + .Op(Instruction.SSTORE) // store to prove we got here + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(callerAddress, callerCode); + + long gasLimit = 500_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(callerAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ByzantiumBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().BeGreaterThan(0, "Gas estimation should succeed with mixed inner reverts"); + err.Should().BeNull("No error when inner calls revert but overall tx succeeds"); + } + + [Test] + public void Should_estimate_gas_when_inner_call_runs_out_of_gas_but_caller_handles_it() + { + // Verifies that inner OOG (caught by the caller) does not fail gas estimation. + // OutOfGas must be nesting-aware (only set at top level), matching Geth behavior. + // Geth's binary search checks result.Failed() which only reflects the top-level outcome. + // See: https://github.com/ethereum/go-ethereum/blob/master/eth/gasestimator/gasestimator.go + using TestEnvironment testEnvironment = new(); + + // Contract that consumes all gas via infinite loop (will always OOG) + Address gasGuzzlerAddress = TestItem.AddressB; + byte[] gasGuzzlerCode = Prepare.EvmCode + .Op(Instruction.JUMPDEST) // offset 0 + .PushData((byte)0x00) + .Op(Instruction.JUMP) // jump back to 0 + .Done; + testEnvironment.InsertContract(gasGuzzlerAddress, gasGuzzlerCode); + + // Middle contract: calls gas guzzler with limited gas, catches OOG + Address middleAddress = TestItem.AddressC; + byte[] middleCode = Prepare.EvmCode + .Call(gasGuzzlerAddress, 1_000) // only 1000 gas - will OOG + .Op(Instruction.POP) // discard result (0 = failure) + .PushData(0x01) + .PushData((byte)0x00) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(middleAddress, middleCode); + + // Outer caller + Address callerAddress = TestItem.AddressD; + byte[] callerCode = Prepare.EvmCode + .Call(middleAddress, 100_000) + .Op(Instruction.POP) + .PushData(0x02) + .PushData(0x01) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(callerAddress, callerCode); + + long gasLimit = 500_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(callerAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ByzantiumBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().BeGreaterThan(0, "Gas estimation should succeed when inner call OOGs but caller handles it"); + err.Should().BeNull("No error - inner OOG should not affect top-level estimation"); + } + + [TestCase(50_000, true)] + [TestCase(500_000, true)] + [TestCase(1_000, false)] + public void Should_estimate_gas_with_gas_sensitive_branching(long gasThreshold, bool shouldSucceed) + { + // Contract that checks gasLeft() and branches: if gasLeft >= threshold, SSTORE; else REVERT. + // Tests that the binary search correctly handles gas-dependent execution paths. + using TestEnvironment testEnvironment = new(); + + Address contractAddress = TestItem.AddressB; + + // Use the existing pattern from Should_estimate_gas_for_explicit_gas_check_and_revert + // Bytecode: PUSH3 , GAS, LT, PUSH1 , JUMPI, PUSH1 1, PUSH1 0, SSTORE, STOP, JUMPDEST, PUSH1 0, PUSH1 0, REVERT + var check = gasThreshold; + byte[] contractCode = Bytes.FromHexString($"0x62{check:x6}5a10600f576001600055005b6000806000fd"); + testEnvironment.InsertContract(contractAddress, contractCode); + + long gasLimit = 1_100_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(contractAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ByzantiumBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + if (shouldSucceed) + { + result.Should().BeGreaterThan(0, "Gas estimation should find enough gas for the success path"); + err.Should().BeNull("No error - binary search should find gas level above threshold"); + } + else + { + result.Should().BeGreaterThan(0, "Low threshold should always succeed"); + err.Should().BeNull(); + } + } + + [Test] + public void Should_estimate_gas_for_create_with_constructor_making_calls() + { + // CREATE deploys a contract whose constructor makes an external CALL. + // The constructor call might revert but CREATE still succeeds. + using TestEnvironment testEnvironment = new(); + + // External contract that reverts + Address externalAddress = TestItem.AddressB; + byte[] externalCode = Prepare.EvmCode + .Revert(0, 0) + .Done; + testEnvironment.InsertContract(externalAddress, externalCode); + + // Runtime code (deployed contract's code) + byte[] runtimeCode = Prepare.EvmCode + .PushData(0x01) + .PushData(0x00) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + + // Init code: calls external (which reverts, but init code catches it), then returns runtime code + byte[] initCode = Prepare.EvmCode + .Call(externalAddress, 10_000) + .Op(Instruction.POP) // discard call result + .ForInitOf(runtimeCode) + .Done; + + // Factory: CREATE with init code, then STOP + Address factoryAddress = TestItem.AddressC; + byte[] factoryCode = Prepare.EvmCode + .Create(initCode, 0) + .Op(Instruction.POP) // discard created address + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(factoryAddress, factoryCode); + + long gasLimit = 500_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(factoryAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ByzantiumBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().BeGreaterThan(0, "Gas estimation should succeed for CREATE with constructor that makes calls"); + err.Should().BeNull("No error for constructor-call pattern"); + } + + [Test] + public void Should_estimate_gas_consistently_across_repeated_calls() + { + // Tests that repeated gas estimation on the same contract yields consistent results. + // Each call creates a fresh EstimateGasTracer; this guards against non-deterministic estimation behavior across runs. + using TestEnvironment testEnvironment = new(); + + Address reverterAddress = TestItem.AddressB; + byte[] reverterCode = Prepare.EvmCode + .Revert(0, 0) + .Done; + testEnvironment.InsertContract(reverterAddress, reverterCode); + + Address callerAddress = TestItem.AddressC; + byte[] callerCode = Prepare.EvmCode + .Call(reverterAddress, 30_000) + .Op(Instruction.POP) + .PushData(0x01) + .PushData(0x00) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(callerAddress, callerCode); + + long gasLimit = 300_000; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ByzantiumBlockNumber + 1) + .WithGasLimit(gasLimit) + .TestObject; + + long? firstResult = null; + for (int i = 0; i < 10; i++) + { + // Each estimation uses a fresh tracer (as BlockchainBridge.EstimateGas does) + TestEnvironment freshEnv = new(); + freshEnv.InsertContract(reverterAddress, reverterCode); + freshEnv.InsertContract(callerAddress, callerCode); + + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(callerAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + + long result = freshEnv.estimator.Estimate(tx, block.Header, freshEnv.tracer, out string? err); + + result.Should().BeGreaterThan(0, $"Iteration {i}: gas estimation should succeed"); + err.Should().BeNull($"Iteration {i}: no error expected"); + + firstResult ??= result; + result.Should().Be(firstResult.Value, $"Iteration {i}: result should be consistent"); + + freshEnv.Dispose(); + } + } + + [Test] + public void Should_estimate_gas_for_deeply_nested_calls() + { + // Chain of 4 nested CALLs to test nesting level tracking in EstimateGasTracer. + // A -> B -> C -> D (all succeed) + using TestEnvironment testEnvironment = new(); + + // Contract D: leaf, just stores and stops + Address addrD = TestItem.AddressD; + byte[] codeD = Prepare.EvmCode + .PushData(0x04) + .PushData(0x04) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(addrD, codeD); + + // Contract C: calls D + Address addrC = TestItem.AddressC; + byte[] codeC = Prepare.EvmCode + .Call(addrD, 50_000) + .Op(Instruction.POP) + .PushData(0x03) + .PushData(0x03) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(addrC, codeC); + + // Contract B: calls C + Address addrB = TestItem.AddressB; + byte[] codeB = Prepare.EvmCode + .Call(addrC, 100_000) + .Op(Instruction.POP) + .PushData(0x02) + .PushData(0x02) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(addrB, codeB); + + // Contract A: calls B (this is what the tx calls) + Address addrA = new("0x0000000000000000000000000000000000000042"); + byte[] codeA = Prepare.EvmCode + .Call(addrB, 200_000) + .Op(Instruction.POP) + .PushData(0x01) + .PushData(0x01) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(addrA, codeA); + + long gasLimit = 500_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(addrA) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ByzantiumBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().BeGreaterThan(0, "Gas estimation should succeed for deeply nested call chain"); + err.Should().BeNull("No error for deeply nested calls"); + } + + [Test] + public void Should_estimate_gas_for_nested_create2_with_inner_revert_in_constructor() + { + // CREATE2 deploys a contract whose constructor calls an external contract that reverts. + // Constructor catches the revert and continues. This is the GnosisSafe pattern: + // createProxyWithNonce -> CREATE2 -> proxy constructor -> setup() call -> possible revert + using TestEnvironment testEnvironment = new(); + + // External contract that always reverts with data + Address externalAddress = TestItem.AddressB; + byte[] externalCode = Prepare.EvmCode + .StoreDataInMemory(0, new byte[] { 0xDE, 0xAD }) + .Revert(2, 0) + .Done; + testEnvironment.InsertContract(externalAddress, externalCode); + + // Runtime code (what the proxy becomes after deployment) + byte[] runtimeCode = Prepare.EvmCode + .Op(Instruction.STOP) + .Done; + + // Init code: calls external (reverts, caught), then returns runtime code + byte[] initCode = Prepare.EvmCode + .Call(externalAddress, 20_000) // will revert, returns 0 + .Op(Instruction.POP) // discard failure result + .ForInitOf(runtimeCode) + .Done; + + // Factory: CREATE2 with salt, verify address is non-zero, STOP + Address factoryAddress = TestItem.AddressC; + byte[] factoryCode = Prepare.EvmCode + .Create2(initCode, new byte[] { 0xAB, 0xCD }, 0) // CREATE2 with salt + .Op(Instruction.POP) + .PushData(0x01) + .PushData(0x00) + .Op(Instruction.SSTORE) // record success + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(factoryAddress, factoryCode); + + long gasLimit = 500_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(factoryAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ConstantinopleFixBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().BeGreaterThan(0, "Gas estimation should succeed for CREATE2 with inner revert in constructor"); + err.Should().BeNull("No error for GnosisSafe-like CREATE2 pattern"); + } + + [Test] + public void Should_return_revert_error_when_top_level_call_reverts_with_data() + { + // Ensures gas estimation properly reports revert data when the top-level call reverts. + using TestEnvironment testEnvironment = new(); + + Address contractAddress = TestItem.AddressB; + // Store revert reason in memory, then REVERT with it + byte[] contractCode = Prepare.EvmCode + .StoreDataInMemory(0, new byte[] { 0x08, 0xC3, 0x79, 0xA0 }) // Error(string) selector + .Revert(4, 0) + .Done; + testEnvironment.InsertContract(contractAddress, contractCode); + + long gasLimit = 300_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(contractAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ByzantiumBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().Be(0, "Gas estimation should fail when top-level call reverts"); + err.Should().NotBeNull("Should report an error when top-level reverts"); + // The error contains the revert data (hex-encoded output from the REVERT opcode) + testEnvironment.tracer.TopLevelRevert.Should().BeTrue("TopLevelRevert should be set for top-level REVERT"); + } + + [Test] + public void Should_estimate_gas_with_delegatecall_that_reverts_internally() + { + // DELEGATECALL that reverts internally - the revert happens in the caller's context + // but at a nested level. Gas estimation should still succeed. + using TestEnvironment testEnvironment = new(); + + // Implementation that reverts + Address implAddress = TestItem.AddressB; + byte[] implCode = Prepare.EvmCode + .Revert(0, 0) + .Done; + testEnvironment.InsertContract(implAddress, implCode); + + // Proxy: DELEGATECALL to impl (reverts), catches it, then succeeds + Address proxyAddress = TestItem.AddressC; + byte[] proxyCode = Prepare.EvmCode + .DelegateCall(implAddress, 30_000) + .Op(Instruction.POP) // discard result + .PushData(0x01) + .PushData(0x00) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(proxyAddress, proxyCode); + + long gasLimit = 300_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(proxyAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ByzantiumBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().BeGreaterThan(0, "Gas estimation should succeed when DELEGATECALL reverts but caller handles it"); + err.Should().BeNull("No error for caught DELEGATECALL revert"); + } + 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 +1281,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/GethLikeTxMemoryTracerTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/GethLikeTxMemoryTracerTests.cs index 971dce785c83..503c3b636f45 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Tracing/GethLikeTxMemoryTracerTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Tracing/GethLikeTxMemoryTracerTests.cs @@ -213,7 +213,7 @@ public void Stack_is_cleared_and_restored_when_moving_between_call_levels() Assert.That(trace.Entries[9].Stack.Count, Is.EqualTo(0), "BEGIN 2"); Assert.That(trace.Entries[19].Stack.Count, Is.EqualTo(4), "CREATE FROM 2"); Assert.That(trace.Entries[20].Stack.Count, Is.EqualTo(0), "BEGIN 3"); - Assert.That(trace.Entries[2].Stack.Count, Is.EqualTo(2), "END 3"); + Assert.That(trace.Entries[25].Stack.Count, Is.EqualTo(2), "END 3"); Assert.That(trace.Entries[26].Stack.Count, Is.EqualTo(2), "END 2"); Assert.That(trace.Entries[27].Stack.Count, Is.EqualTo(2), "END 1"); } diff --git a/src/Nethermind/Nethermind.Evm.Test/Tracing/GethLikeTxTraceCollectionConverterTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/GethLikeTxTraceCollectionConverterTests.cs index b7d0959b0cfb..289ca3592ccf 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 93f0e39762fe..60ba34a5f031 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 16d1dba46bd5..21ce948d65f4 100644 --- a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip4844Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip4844Tests.cs @@ -18,7 +18,6 @@ using System.Collections.Generic; using Nethermind.Blockchain; using Nethermind.Core.Test; -using Nethermind.State; namespace Nethermind.Evm.Test; @@ -38,8 +37,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 69ed6870c84f..d4e7f5dfce81 100644 --- a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip7623Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip7623Tests.cs @@ -16,7 +16,6 @@ using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.Evm.State; -using Nethermind.State; using NUnit.Framework; namespace Nethermind.Evm.Test; @@ -36,8 +35,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 50f593d378b3..c80d6c5ebf25 100644 --- a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorFeeTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorFeeTests.cs @@ -11,18 +11,15 @@ using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; -using Nethermind.Db; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; using Nethermind.Logging; using Nethermind.Specs; using Nethermind.Specs.Test; using Nethermind.Evm.State; -using Nethermind.Trie.Pruning; using Nethermind.Int256; using NUnit.Framework; using Nethermind.Specs.GnosisForks; -using Nethermind.State; namespace Nethermind.Evm.Test; @@ -48,8 +45,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 d139a885c33b..81f14bde8676 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 9287d74ec8bd..7eadf15bc0b8 100644 --- a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs +++ b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs @@ -20,7 +20,6 @@ using Nethermind.Evm.TransactionProcessing; using Nethermind.Logging; using Nethermind.Evm.State; -using Nethermind.State; using NUnit.Framework; namespace Nethermind.Evm.Test; @@ -37,7 +36,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 +71,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] @@ -118,14 +117,13 @@ protected GethLikeTxTrace ExecuteAndTraceToFile(Action dum /// /// deprecated. Please use activation instead of blockNumber. /// - protected TestAllTracerWithOutput Execute(long blockNumber, params byte[] code) - { - return Execute((blockNumber, Timestamp), code); - } + protected TestAllTracerWithOutput Execute(long blockNumber, params byte[] code) => Execute((blockNumber, Timestamp), code); + + protected TestAllTracerWithOutput Execute(ForkActivation activation, params byte[] code) => Execute(activation, 100000, code); - protected TestAllTracerWithOutput Execute(ForkActivation activation, params byte[] code) + protected TestAllTracerWithOutput Execute(ForkActivation activation, long gasLimit, params byte[] code) { - (Block block, Transaction transaction) = PrepareTx(activation, 100000, code); + (Block block, Transaction transaction) = PrepareTx(activation, gasLimit, code); TestAllTracerWithOutput tracer = CreateTracer(); _processor.Execute(transaction, new BlockExecutionContext(block.Header, SpecProvider.GetSpec(block.Header)), tracer); return tracer; @@ -139,14 +137,9 @@ protected TestAllTracerWithOutput Execute(ForkActivation activation, Transaction return tracer; } - protected TestAllTracerWithOutput Execute(params byte[] code) - { - return Execute(Activation, code); - } - protected TestAllTracerWithOutput Execute(Transaction tx) - { - return Execute(Activation, tx); - } + protected TestAllTracerWithOutput Execute(params byte[] code) => Execute(Activation, code); + + protected TestAllTracerWithOutput Execute(Transaction tx) => Execute(Activation, tx); protected virtual TestAllTracerWithOutput CreateTracer() => new(); @@ -203,9 +196,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( @@ -217,7 +211,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; @@ -249,7 +244,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)) @@ -267,7 +262,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 000000000000..8fc3e9a8b584 --- /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/AddressExtensions.cs b/src/Nethermind/Nethermind.Evm/AddressExtensions.cs index 4ec56c2e3593..0f49af91c001 100644 --- a/src/Nethermind/Nethermind.Evm/AddressExtensions.cs +++ b/src/Nethermind/Nethermind.Evm/AddressExtensions.cs @@ -8,6 +8,7 @@ using Nethermind.Int256; using Nethermind.Serialization.Rlp; using Nethermind.Evm.State; +using System.Runtime.CompilerServices; namespace Nethermind.Evm { @@ -16,26 +17,28 @@ public static class ContractAddress public static Address From(Address? deployingAddress, in UInt256 nonce) { int contentLength = Rlp.LengthOf(deployingAddress) + Rlp.LengthOf(nonce); - RlpStream stream = new RlpStream(Rlp.LengthOfSequence(contentLength)); + RlpStream stream = new(Rlp.LengthOfSequence(contentLength)); stream.StartSequence(contentLength); stream.Encode(deployingAddress); stream.Encode(nonce); ValueHash256 contractAddressKeccak = ValueKeccak.Compute(stream.Data.AsSpan()); - return new Address(in contractAddressKeccak); + return new(in contractAddressKeccak); } + + [SkipLocalsInit] public static Address From(Address deployingAddress, ReadOnlySpan salt, ReadOnlySpan initCode) { // sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code) ++ sha3(aux_data)) - Span bytes = new byte[1 + Address.Size + Keccak.Size + salt.Length]; + Span bytes = stackalloc byte[1 + Address.Size + Keccak.Size + salt.Length]; bytes[0] = 0xff; deployingAddress.Bytes.CopyTo(bytes.Slice(1, Address.Size)); salt.CopyTo(bytes.Slice(1 + Address.Size, salt.Length)); ValueKeccak.Compute(initCode).BytesAsSpan.CopyTo(bytes.Slice(1 + Address.Size + salt.Length, Keccak.Size)); ValueHash256 contractAddressKeccak = ValueKeccak.Compute(bytes); - return new Address(in contractAddressKeccak); + return new(in contractAddressKeccak); } // See https://eips.ethereum.org/EIPS/eip-7610 diff --git a/src/Nethermind/Nethermind.Evm/BadInstructionException.cs b/src/Nethermind/Nethermind.Evm/BadInstructionException.cs deleted file mode 100644 index e8caed45f5cd..000000000000 --- a/src/Nethermind/Nethermind.Evm/BadInstructionException.cs +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Evm; - -public class BadInstructionException : EvmException -{ - public override EvmExceptionType ExceptionType => EvmExceptionType.BadInstruction; -} diff --git a/src/Nethermind/Nethermind.Evm/BitmapHelper.cs b/src/Nethermind/Nethermind.Evm/BitmapHelper.cs index 20586adc08c6..eb198956a290 100644 --- a/src/Nethermind/Nethermind.Evm/BitmapHelper.cs +++ b/src/Nethermind/Nethermind.Evm/BitmapHelper.cs @@ -8,6 +8,7 @@ using System.Runtime.Intrinsics; namespace Nethermind.Evm; + public static class BitmapHelper { private static readonly byte[] _lookup = diff --git a/src/Nethermind/Nethermind.Evm/BlobGasCalculator.cs b/src/Nethermind/Nethermind.Evm/BlobGasCalculator.cs index 7dbe87e1de8e..4c3b4d6f5b1c 100644 --- a/src/Nethermind/Nethermind.Evm/BlobGasCalculator.cs +++ b/src/Nethermind/Nethermind.Evm/BlobGasCalculator.cs @@ -83,10 +83,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.Facade/Proxy/Models/Simulate/BlockOverride.cs b/src/Nethermind/Nethermind.Evm/BlockOverride.cs similarity index 96% rename from src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockOverride.cs rename to src/Nethermind/Nethermind.Evm/BlockOverride.cs index 4bb929aef316..fb0f7b6e52c2 100644 --- a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockOverride.cs +++ b/src/Nethermind/Nethermind.Evm/BlockOverride.cs @@ -6,7 +6,7 @@ using Nethermind.Core.Crypto; using Nethermind.Int256; -namespace Nethermind.Facade.Proxy.Models.Simulate; +namespace Nethermind.Evm; public class BlockOverride { diff --git a/src/Nethermind/Nethermind.Evm/ByteArrayExtensions.cs b/src/Nethermind/Nethermind.Evm/ByteArrayExtensions.cs index 57159706c744..d99b77804dcd 100644 --- a/src/Nethermind/Nethermind.Evm/ByteArrayExtensions.cs +++ b/src/Nethermind/Nethermind.Evm/ByteArrayExtensions.cs @@ -21,15 +21,6 @@ private static ZeroPaddedSpan SliceWithZeroPadding(this ReadOnlySpan span, return new ZeroPaddedSpan(default, length, padDirection); } - if (length == 1) - { - // why do we return zero length here? - // it was passing all the tests like this... - // return bytes.Length == 0 ? new byte[0] : new[] {bytes[startIndex]}; - return span.Length == 0 ? new ZeroPaddedSpan(default, 0, padDirection) : new ZeroPaddedSpan(span.Slice(startIndex, 1), 0, padDirection); - // return bytes.Length == 0 ? new ZeroPaddedSpan(default, 1) : new ZeroPaddedSpan(bytes.Slice(startIndex, 1), 0); - } - int copiedLength = Math.Min(span.Length - startIndex, length); return new ZeroPaddedSpan(span.Slice(startIndex, copiedLength), length - copiedLength, padDirection); } diff --git a/src/Nethermind/Nethermind.Evm/ByteCodeBuilder.cs b/src/Nethermind/Nethermind.Evm/ByteCodeBuilder.cs index a44ab9dae901..5fdc2fe0da78 100644 --- a/src/Nethermind/Nethermind.Evm/ByteCodeBuilder.cs +++ b/src/Nethermind/Nethermind.Evm/ByteCodeBuilder.cs @@ -208,7 +208,10 @@ public Prepare DynamicCallWithInput(Instruction callType, Address address, long PushData(0); PushData(input is not null ? input.Length : 32); PushData(0); - PushData(0); + if (callType == Instruction.CALL) + { + PushData(0); + } PushData(address); PushData(gasLimit); Op(callType); diff --git a/src/Nethermind/Nethermind.Evm/CallResult.cs b/src/Nethermind/Nethermind.Evm/CallResult.cs index 7e6dd24f2b88..d23ac7ab54e3 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()); @@ -42,7 +44,7 @@ public CallResult(ReadOnlyMemory output, bool? precompileSuccess, int from FromVersion = fromVersion; } - public CallResult(ICodeInfo? container, ReadOnlyMemory output, bool? precompileSuccess, int fromVersion, bool shouldRevert = false, EvmExceptionType exceptionType = EvmExceptionType.None) + public CallResult(CodeInfo? container, ReadOnlyMemory output, bool? precompileSuccess, int fromVersion, bool shouldRevert = false, EvmExceptionType exceptionType = EvmExceptionType.None) { StateToExecute = null; Output = (container, output); @@ -52,7 +54,7 @@ public CallResult(ICodeInfo? container, ReadOnlyMemory output, bool? preco FromVersion = fromVersion; } - public CallResult(EvmExceptionType exceptionType) + private CallResult(EvmExceptionType exceptionType) { StateToExecute = null; Output = (null, StatusCode.FailureBytes); @@ -61,8 +63,8 @@ public CallResult(EvmExceptionType exceptionType) ExceptionType = exceptionType; } - public EvmState? StateToExecute { get; } - public (ICodeInfo Container, ReadOnlyMemory Bytes) Output { get; } + public VmState? StateToExecute { get; } + public (CodeInfo Container, ReadOnlyMemory Bytes) Output { get; } public EvmExceptionType ExceptionType { get; } public bool ShouldRevert { get; } public bool? PrecompileSuccess { get; } @@ -70,5 +72,6 @@ public CallResult(EvmExceptionType exceptionType) //EvmExceptionType.Revert is returned when the top frame encounters a REVERT opcode, which is not an exception. public bool IsException => ExceptionType != EvmExceptionType.None && ExceptionType != EvmExceptionType.Revert; public int FromVersion { get; } + public string? SubstateError { get; init; } } } diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs index 4e2320bd3ff6..75a16c118201 100644 --- a/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs @@ -3,35 +3,101 @@ using System; using System.Threading; +using Nethermind.Core.Extensions; +using Nethermind.Evm.Precompiles; namespace Nethermind.Evm.CodeAnalysis; -public sealed class CodeInfo(ReadOnlyMemory code) : ICodeInfo, IThreadPoolWorkItem +public class CodeInfo : IThreadPoolWorkItem, IEquatable { - private static readonly JumpDestinationAnalyzer _emptyAnalyzer = new(Array.Empty()); - public static CodeInfo Empty { get; } = new(ReadOnlyMemory.Empty); - public ReadOnlyMemory Code { get; } = code; - ReadOnlySpan ICodeInfo.CodeSpan => Code.Span; + public static CodeInfo Empty { get; } = new(); + // Empty code sentinel + private static readonly JumpDestinationAnalyzer? _emptyAnalyzer = new(Empty, skipAnalysis: true); - private readonly JumpDestinationAnalyzer _analyzer = code.Length == 0 ? _emptyAnalyzer : new JumpDestinationAnalyzer(code); + // Empty + private CodeInfo() + { + _analyzer = null; + } - public bool IsEmpty => ReferenceEquals(_analyzer, _emptyAnalyzer); + protected CodeInfo(IPrecompile precompile, int version, ReadOnlyMemory code) + { + Precompile = precompile; + Version = version; + Code = code; + _analyzer = null; + } - public bool ValidateJump(int destination) + // Eof + protected CodeInfo(int version, ReadOnlyMemory code) { - return _analyzer.ValidateJump(destination); + Version = version; + Code = code; + _analyzer = null; } - void IThreadPoolWorkItem.Execute() + // Regular contract + public CodeInfo(ReadOnlyMemory code) { - _analyzer.Execute(); + Code = code; + _analyzer = code.Length == 0 ? _emptyAnalyzer : new JumpDestinationAnalyzer(this); } + // Precompile + public CodeInfo(IPrecompile? precompile) + { + Precompile = precompile; + _analyzer = null; + } + + public ReadOnlyMemory Code { get; } + public ReadOnlySpan CodeSpan => Code.Span; + + public IPrecompile? Precompile { get; } + + private readonly JumpDestinationAnalyzer _analyzer; + + public bool IsEmpty => ReferenceEquals(_analyzer, _emptyAnalyzer); + public bool IsPrecompile => Precompile is not null; + + public bool ValidateJump(int destination) + => _analyzer?.ValidateJump(destination) ?? false; + + /// + /// Gets the version of the code format. + /// The default implementation returns 0, representing a legacy code format or non-EOF code. + /// + public int Version { get; } = 0; + + void IThreadPoolWorkItem.Execute() + => _analyzer?.Execute(); + public void AnalyzeInBackgroundIfRequired() { - if (!ReferenceEquals(_analyzer, _emptyAnalyzer) && _analyzer.RequiresAnalysis) + if (!ReferenceEquals(_analyzer, _emptyAnalyzer) && (_analyzer?.RequiresAnalysis ?? false)) { ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false); } } + + public override bool Equals(object? obj) + => Equals(obj as CodeInfo); + + public override int GetHashCode() + { + if (IsPrecompile) + return Precompile?.GetType().GetHashCode() ?? 0; + return CodeSpan.FastHash(); + } + + public bool Equals(CodeInfo? other) + { + if (other is null) + return false; + if (ReferenceEquals(this, other)) + return true; + if (IsPrecompile || other.IsPrecompile) + return Precompile?.GetType() == other.Precompile?.GetType(); + return CodeSpan.SequenceEqual(other.CodeSpan); + } } diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs index 7069f9624653..7923f62aeafc 100644 --- a/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs @@ -16,7 +16,7 @@ namespace Nethermind.Evm.CodeAnalysis; -public sealed class JumpDestinationAnalyzer(ReadOnlyMemory code) +public sealed class JumpDestinationAnalyzer(CodeInfo codeInfo, bool skipAnalysis = false) { private const int PUSH1 = (int)Instruction.PUSH1; private const int PUSHx = PUSH1 - 1; @@ -24,10 +24,10 @@ public sealed class JumpDestinationAnalyzer(ReadOnlyMemory code) private const int BitShiftPerInt64 = 6; private static readonly long[]? _emptyJumpDestinationBitmap = new long[1]; - private long[]? _jumpDestinationBitmap = code.Length == 0 ? _emptyJumpDestinationBitmap : null; + private long[]? _jumpDestinationBitmap = (codeInfo.Code.Length == 0 || skipAnalysis) ? _emptyJumpDestinationBitmap : null; private object? _analysisComplete; - private ReadOnlyMemory MachineCode { get; } = code; + public ReadOnlyMemory MachineCode => codeInfo.Code; public bool ValidateJump(int destination) { @@ -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); } @@ -217,7 +217,7 @@ internal static long[] PopulateJumpDestinationBitmap_Vector512(long[] bitmap, Re int overflow = size - inThis; // Clear bits [lane .. lane+inThis-1] from both masks - ulong clearThis = (Bmi1.X64.IsSupported ? + ulong clearThis = (Bmi2.X64.IsSupported ? Bmi2.X64.ZeroHighBits(ulong.MaxValue, (uint)inThis) : ((1UL << inThis) - 1UL)) << lane; @@ -228,7 +228,7 @@ internal static long[] PopulateJumpDestinationBitmap_Vector512(long[] bitmap, Re // If it spilled, mark those lanes for the next chunk if (overflow > 0) { - carryMask = (Bmi1.X64.IsSupported ? + carryMask = (Bmi2.X64.IsSupported ? Bmi2.X64.ZeroHighBits(ulong.MaxValue, (uint)overflow) : ((1UL << overflow) - 1UL)); break; @@ -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/CodeDepositHandler.cs b/src/Nethermind/Nethermind.Evm/CodeDepositHandler.cs index b011de6e2ded..d06b24a73657 100644 --- a/src/Nethermind/Nethermind.Evm/CodeDepositHandler.cs +++ b/src/Nethermind/Nethermind.Evm/CodeDepositHandler.cs @@ -10,13 +10,10 @@ namespace Nethermind.Evm public static class CodeDepositHandler { private const byte InvalidStartingCodeByte = 0xEF; - public static long CalculateCost(IReleaseSpec spec, int byteCodeLength) - { - if (spec.LimitCodeSize && byteCodeLength > spec.MaxCodeSize) - return long.MaxValue; - - return GasCostOf.CodeDeposit * byteCodeLength; - } + public static long CalculateCost(IReleaseSpec spec, int byteCodeLength) => + spec.LimitCodeSize && byteCodeLength > spec.MaxCodeSize + ? long.MaxValue + : GasCostOf.CodeDeposit * byteCodeLength; public static bool CodeIsInvalid(IReleaseSpec spec, ReadOnlyMemory code, int fromVersion) => !CodeIsValid(spec, code, fromVersion); diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs b/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs index 43fd8a86b8b2..91ff0753e0e1 100644 --- a/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs +++ b/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs @@ -10,7 +10,7 @@ namespace Nethermind.Evm.CodeAnalysis; public static class CodeInfoFactory { - public static ICodeInfo CreateCodeInfo(ReadOnlyMemory code, IReleaseSpec spec, ValidationStrategy validationRules = ValidationStrategy.ExtractHeader) + public static CodeInfo CreateCodeInfo(ReadOnlyMemory code, IReleaseSpec spec, ValidationStrategy validationRules = ValidationStrategy.ExtractHeader) { if (spec.IsEofEnabled && code.Span.StartsWith(EofValidator.MAGIC) @@ -23,7 +23,7 @@ public static ICodeInfo CreateCodeInfo(ReadOnlyMemory code, IReleaseSpec s return codeInfo; } - public static bool CreateInitCodeInfo(ReadOnlyMemory data, IReleaseSpec spec, [NotNullWhen(true)] out ICodeInfo? codeInfo, out ReadOnlyMemory extraCallData) + public static bool CreateInitCodeInfo(ReadOnlyMemory data, IReleaseSpec spec, [NotNullWhen(true)] out CodeInfo? codeInfo, out ReadOnlyMemory extraCallData) { extraCallData = default; if (spec.IsEofEnabled && data.Span.StartsWith(EofValidator.MAGIC)) diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs index 163029357db7..1fb55fdaf67f 100644 --- a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs @@ -19,7 +19,7 @@ namespace Nethermind.Evm; public class CodeInfoRepository : ICodeInfoRepository { private static readonly CodeLruCache _codeCache = new(); - private readonly FrozenDictionary _localPrecompiles; + private readonly FrozenDictionary _localPrecompiles; private readonly IWorldState _worldState; public CodeInfoRepository(IWorldState worldState, IPrecompileProvider precompileProvider) @@ -28,7 +28,7 @@ public CodeInfoRepository(IWorldState worldState, IPrecompileProvider precompile _worldState = worldState; } - public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) + public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) { delegationAddress = null; if (vmSpec.IsPrecompile(codeSource)) // _localPrecompiles have to have all precompiles @@ -36,9 +36,9 @@ public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IR return _localPrecompiles[codeSource]; } - ICodeInfo cachedCodeInfo = InternalGetCachedCode(_worldState, codeSource, vmSpec); + CodeInfo cachedCodeInfo = InternalGetCachedCode(_worldState, codeSource, vmSpec); - if (!cachedCodeInfo.IsEmpty && TryGetDelegatedAddress(cachedCodeInfo.CodeSpan, out delegationAddress)) + if (!cachedCodeInfo.IsEmpty && ICodeInfoRepository.TryGetDelegatedAddress(cachedCodeInfo.CodeSpan, out delegationAddress)) { if (followDelegation) cachedCodeInfo = InternalGetCachedCode(_worldState, delegationAddress, vmSpec); @@ -47,21 +47,21 @@ public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IR return cachedCodeInfo; } - private ICodeInfo InternalGetCachedCode(Address codeSource, IReleaseSpec vmSpec) + private CodeInfo InternalGetCachedCode(Address codeSource, IReleaseSpec vmSpec) { ref readonly ValueHash256 codeHash = ref _worldState.GetCodeHash(codeSource); return InternalGetCachedCode(_worldState, in codeHash, vmSpec); } - private static ICodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, Address codeSource, IReleaseSpec vmSpec) + private static CodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, Address codeSource, IReleaseSpec vmSpec) { ValueHash256 codeHash = worldState.GetCodeHash(codeSource); return InternalGetCachedCode(worldState, in codeHash, vmSpec); } - private static ICodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, in ValueHash256 codeHash, IReleaseSpec vmSpec) + private static CodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, in ValueHash256 codeHash, IReleaseSpec vmSpec) { - ICodeInfo? cachedCodeInfo = null; + CodeInfo? cachedCodeInfo = null; if (codeHash == Keccak.OfAnEmptyString.ValueHash256) { cachedCodeInfo = CodeInfo.Empty; @@ -101,7 +101,7 @@ public void InsertCode(ReadOnlyMemory code, Address codeOwner, IReleaseSpe if (_worldState.InsertCode(codeOwner, in codeHash, code, spec) && _codeCache.Get(in codeHash) is null) { - ICodeInfo codeInfo = CodeInfoFactory.CreateCodeInfo(code, spec, ValidationStrategy.ExtractHeader); + CodeInfo codeInfo = CodeInfoFactory.CreateCodeInfo(code, spec, ValidationStrategy.ExtractHeader); _codeCache.Set(in codeHash, codeInfo); } } @@ -138,65 +138,49 @@ public ValueHash256 GetExecutableCodeHash(Address address, IReleaseSpec spec) return Keccak.OfAnEmptyString.ValueHash256; } - ICodeInfo codeInfo = InternalGetCachedCode(_worldState, address, spec); + CodeInfo codeInfo = InternalGetCachedCode(_worldState, address, spec); return codeInfo.IsEmpty ? Keccak.OfAnEmptyString.ValueHash256 : codeHash; } - /// - /// Parses delegation code to extract the contained address. - /// Assumes is delegation code! - /// - private static bool TryGetDelegatedAddress(ReadOnlySpan code, [NotNullWhen(true)] out Address? address) - { - if (Eip7702Constants.IsDelegatedCode(code)) - { - address = new Address(code[Eip7702Constants.DelegationHeader.Length..].ToArray()); - return true; - } - - address = null; - return false; - } - public bool TryGetDelegation(Address address, IReleaseSpec spec, [NotNullWhen(true)] out Address? delegatedAddress) => - TryGetDelegatedAddress(InternalGetCachedCode(_worldState, address, spec).CodeSpan, out delegatedAddress); + ICodeInfoRepository.TryGetDelegatedAddress(InternalGetCachedCode(_worldState, address, spec).CodeSpan, out delegatedAddress); public bool TryGetDelegation(in ValueHash256 codeHash, IReleaseSpec spec, [NotNullWhen(true)] out Address? delegatedAddress) => - TryGetDelegatedAddress(InternalGetCachedCode(_worldState, in codeHash, spec).CodeSpan, out delegatedAddress); + ICodeInfoRepository.TryGetDelegatedAddress(InternalGetCachedCode(_worldState, in codeHash, spec).CodeSpan, out delegatedAddress); private sealed class CodeLruCache { private const int CacheCount = 16; private const int CacheMax = CacheCount - 1; - private readonly ClockCache[] _caches; + private readonly ClockCache[] _caches; public CodeLruCache() { - _caches = new ClockCache[CacheCount]; + _caches = new ClockCache[CacheCount]; for (int i = 0; i < _caches.Length; i++) { // Cache per nibble to reduce contention as TxPool is very parallel - _caches[i] = new ClockCache(MemoryAllowance.CodeCacheSize / CacheCount); + _caches[i] = new ClockCache(MemoryAllowance.CodeCacheSize / CacheCount); } } - public ICodeInfo? Get(in ValueHash256 codeHash) + public CodeInfo? Get(in ValueHash256 codeHash) { - ClockCache cache = _caches[GetCacheIndex(codeHash)]; + ClockCache cache = _caches[GetCacheIndex(codeHash)]; return cache.Get(codeHash); } - public bool Set(in ValueHash256 codeHash, ICodeInfo codeInfo) + public bool Set(in ValueHash256 codeHash, CodeInfo codeInfo) { - ClockCache cache = _caches[GetCacheIndex(codeHash)]; + ClockCache cache = _caches[GetCacheIndex(codeHash)]; return cache.Set(codeHash, codeInfo); } private static int GetCacheIndex(in ValueHash256 codeHash) => codeHash.Bytes[^1] & CacheMax; - public bool TryGet(in ValueHash256 codeHash, [NotNullWhen(true)] out ICodeInfo? codeInfo) + public bool TryGet(in ValueHash256 codeHash, [NotNullWhen(true)] out CodeInfo? codeInfo) { codeInfo = Get(in codeHash); return codeInfo is not null; diff --git a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs index 140a3034b36d..82e5a8ee5b95 100644 --- a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs +++ b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs @@ -6,17 +6,18 @@ namespace Nethermind.Evm.CodeAnalysis; -public sealed class EofCodeInfo(in EofContainer container) : ICodeInfo +public sealed class EofCodeInfo : CodeInfo { - public EofContainer EofContainer { get; private set; } = container; - public ReadOnlyMemory Code => EofContainer.Container; - public int Version => EofContainer.Header.Version; - public bool IsEmpty => EofContainer.IsEmpty; + public EofCodeInfo(in EofContainer container) : base(container.Header.Version, container.Container) + { + EofContainer = container; + } + + public EofContainer EofContainer { get; private set; } public ReadOnlyMemory TypeSection => EofContainer.TypeSection; public ReadOnlyMemory CodeSection => EofContainer.CodeSection; public ReadOnlyMemory DataSection => EofContainer.DataSection; public ReadOnlyMemory ContainerSection => EofContainer.ContainerSection; - ReadOnlySpan ICodeInfo.CodeSpan => CodeSection.Span; public SectionHeader CodeSectionOffset(int sectionId) => EofContainer.Header.CodeSections[sectionId]; public SectionHeader? ContainerSectionOffset(int sectionId) => EofContainer.Header.ContainerSections.Value[sectionId]; diff --git a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/Handlers/EofV1.cs b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/Handlers/EofV1.cs index e71a41fc8b8d..b363652df5d4 100644 --- a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/Handlers/EofV1.cs +++ b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/Handlers/EofV1.cs @@ -10,8 +10,6 @@ using System.Runtime.InteropServices; using FastEnumUtility; using Nethermind.Core.Extensions; -using Nethermind.Evm; - using static Nethermind.Evm.EvmObjectFormat.EofValidator; namespace Nethermind.Evm.EvmObjectFormat.Handlers; @@ -785,7 +783,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 +1081,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 +1471,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/EvmObjectFormat/IEofVersionHandler.cs b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/IEofVersionHandler.cs index 3aa3a4fadb43..70b20f0089d6 100644 --- a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/IEofVersionHandler.cs +++ b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/IEofVersionHandler.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; namespace Nethermind.Evm.EvmObjectFormat; + interface IEofVersionHandler { bool TryParseEofHeader(ReadOnlyMemory code, ValidationStrategy strategy, [NotNullWhen(true)] out EofHeader? header); diff --git a/src/Nethermind/Nethermind.Evm/EvmPooledMemory.cs b/src/Nethermind/Nethermind.Evm/EvmPooledMemory.cs index e466680673c5..043a12513fae 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 a86286434cad..32ae7a664498 100644 --- a/src/Nethermind/Nethermind.Evm/EvmStack.cs +++ b/src/Nethermind/Nethermind.Evm/EvmStack.cs @@ -67,19 +67,20 @@ public void PushBytes(scoped ReadOnlySpan value) if (TTracingInst.IsActive) _tracer.ReportStackPush(value); - if (value.Length != WordSize) + if (value.Length >= WordSize) { - ref byte bytes = ref PushBytesRef(); - // Not full entry, clear first - Unsafe.As(ref bytes) = default; - value.CopyTo(MemoryMarshal.CreateSpan(ref Unsafe.Add(ref bytes, WordSize - value.Length), value.Length)); + Debug.Assert(value.Length == WordSize, "Trying to push more than 32 bytes to the stack."); + PushedHead() = Unsafe.As(ref MemoryMarshal.GetReference(value)); } else { - PushedHead() = Unsafe.As(ref MemoryMarshal.GetReference(value)); + ref byte bytes = ref PushBytesRef(); + Unsafe.As(ref bytes) = default; // Not full entry, clear first + value.CopyTo(MemoryMarshal.CreateSpan(ref Unsafe.Add(ref bytes, WordSize - value.Length), value.Length)); } } + public void PushBytes(scoped in ZeroPaddedSpan value) where TTracingInst : struct, IFlag { @@ -87,19 +88,20 @@ public void PushBytes(scoped in ZeroPaddedSpan value) _tracer.ReportStackPush(value); ReadOnlySpan valueSpan = value.Span; - if (valueSpan.Length != WordSize) + if (valueSpan.Length >= WordSize) { - ref byte bytes = ref PushBytesRef(); - // Not full entry, clear first - Unsafe.As(ref bytes) = default; - valueSpan.CopyTo(MemoryMarshal.CreateSpan(ref bytes, value.Length)); + Debug.Assert(value.Length == WordSize, "Trying to push more than 32 bytes to the stack."); + PushedHead() = Unsafe.As(ref MemoryMarshal.GetReference(valueSpan)); } else { - PushedHead() = Unsafe.As(ref MemoryMarshal.GetReference(valueSpan)); + ref byte bytes = ref PushBytesRef(); + Unsafe.As(ref bytes) = default; // Not full entry, clear first + valueSpan.CopyTo(MemoryMarshal.CreateSpan(ref bytes, value.Length)); } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PushByte(byte value) where TTracingInst : struct, IFlag @@ -262,7 +264,7 @@ public void PushZero() if (TTracingInst.IsActive) _tracer.ReportStackPush(Bytes.ZeroByteSpan); - // Single 32-byte store: Zero + // Single 32-byte store: Zero PushedHead() = default; } @@ -319,7 +321,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 a186862e72fd..ff60de7b2723 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 CodeInfo 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( + CodeInfo 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 03f48b11ffbd..68c6e5f97f2f 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 000000000000..dc843359911c --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/GasPolicy/EthereumGasPolicy.cs @@ -0,0 +1,215 @@ +// 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.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 IntrinsicGas CalculateIntrinsicGas(Transaction tx, IReleaseSpec spec) + { + long tokensInCallData = IGasPolicy.CalculateTokensInCallData(tx, spec); + long standard = GasCostOf.Transaction + + DataCost(tx, spec, tokensInCallData) + + CreateCost(tx, spec) + + IGasPolicy.AccessListCost(tx, spec) + + AuthorizationListCost(tx, spec); + long floorCost = IGasPolicy.CalculateFloorCost(tokensInCallData, spec); + return new IntrinsicGas(FromLong(standard), FromLong(floorCost)); + } + + [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 tokensInCallData) => + spec.GetBaseDataCost(tx) + tokensInCallData * GasCostOf.TxDataZero; + + 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] + 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 000000000000..6746c0c58eac --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/GasPolicy/IGasPolicy.cs @@ -0,0 +1,284 @@ +// 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 Nethermind.Core; +using Nethermind.Core.Eip2930; +using Nethermind.Core.Extensions; +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. + /// The 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 IntrinsicGas 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 multidimensional 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 the post-execution state. + /// + /// The current gas state after execution. + static abstract void OnAfterInstructionTrace(in TSelf gas); + + protected static long CalculateTokensInCallData(Transaction transaction, IReleaseSpec spec) + { + ReadOnlySpan data = transaction.Data.Span; + int totalZeros = data.CountZeros(); + return totalZeros + (data.Length - totalZeros) * spec.GetTxDataNonZeroMultiplier(); + } + + public static long AccessListCost(Transaction transaction, IReleaseSpec spec) + { + AccessList? accessList = transaction.AccessList; + if (accessList is not null) + { + if (!spec.UseTxAccessLists) + { + ThrowInvalidDataException(spec); + } + + (int addressesCount, int storageKeysCount) = accessList.Count; + return addressesCount * GasCostOf.AccessAccountListEntry + storageKeysCount * GasCostOf.AccessStorageListEntry; + } + + return 0; + + [DoesNotReturn, StackTraceHidden] + static void ThrowInvalidDataException(IReleaseSpec spec) => + throw new InvalidDataException($"Transaction with an access list received within the context of {spec.Name}. EIP-2930 is not enabled."); + } + + protected static long CalculateFloorCost(long tokensInCallData, IReleaseSpec spec) => + spec.IsEip7623Enabled + ? GasCostOf.Transaction + tokensInCallData * GasCostOf.TotalCostFloorPerTokenEip7623 + : 0L; +} + +/// +/// 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; +} diff --git a/src/Nethermind/Nethermind.Evm/IBlockhashProvider.cs b/src/Nethermind/Nethermind.Evm/IBlockhashProvider.cs index 76a93616df44..800129a5b999 100644 --- a/src/Nethermind/Nethermind.Evm/IBlockhashProvider.cs +++ b/src/Nethermind/Nethermind.Evm/IBlockhashProvider.cs @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Threading; +using System.Threading.Tasks; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; @@ -9,7 +11,7 @@ namespace Nethermind.Evm { public interface IBlockhashProvider { - Hash256? GetBlockhash(BlockHeader currentBlock, long number); - Hash256? GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec? spec); + Hash256? GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec spec); + Task Prefetch(BlockHeader currentBlock, CancellationToken token); } } diff --git a/src/Nethermind/Nethermind.Evm/ICodeInfo.cs b/src/Nethermind/Nethermind.Evm/ICodeInfo.cs deleted file mode 100644 index 40909e8abdd0..000000000000 --- a/src/Nethermind/Nethermind.Evm/ICodeInfo.cs +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; - -namespace Nethermind.Evm.CodeAnalysis; - -/// -/// Represents common code information for EVM execution. -/// Implementations include , (EVM Object Format), -/// and for precompiled contracts. -/// -public interface ICodeInfo -{ - /// - /// Gets the version of the code format. - /// The default implementation returns 0, representing a legacy code format or non-EOF code. - /// - int Version => 0; - - /// - /// Indicates whether the code is empty or not. - /// - bool IsEmpty { get; } - - /// - /// Gets the raw machine code as a segment. - /// This is the primary code section from which the EVM executes instructions. - /// - ReadOnlyMemory Code { get; } - - /// - /// Indicates whether this code represents a precompiled contract. - /// By default, this returns false. - /// - bool IsPrecompile => false; - - /// - /// Gets the code section. - /// By default, this returns the same contents as . - /// - ReadOnlyMemory CodeSection => Code; - ReadOnlySpan CodeSpan { get; } - - /// - /// Gets the data section, which is reserved for additional data segments in EOF. - /// By default, this returns an empty memory segment. - /// - ReadOnlyMemory DataSection => Memory.Empty; - - /// - /// Computes the offset to be added to the program counter when executing instructions. - /// By default, this returns 0, meaning no offset is applied. - /// - /// The program counter offset for this code format. - int PcOffset() => 0; - - /// - /// Validates whether a jump destination is permissible according to this code format. - /// By default, this returns false. - /// - /// The instruction index to validate. - /// true if the jump is valid; otherwise, false. - bool ValidateJump(int destination) => false; -} diff --git a/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs index 84bd43c0faa6..20cedaa8233d 100644 --- a/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs @@ -11,17 +11,34 @@ namespace Nethermind.Evm; public interface ICodeInfoRepository { - ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress); + CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress); ValueHash256 GetExecutableCodeHash(Address address, IReleaseSpec spec); void InsertCode(ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec); void SetDelegation(Address codeSource, Address authority, IReleaseSpec spec); bool TryGetDelegation(Address address, IReleaseSpec spec, [NotNullWhen(true)] out Address? delegatedAddress); + + /// + /// Parses delegation code to extract the contained address. + /// Assumes is delegation code! + /// + static bool TryGetDelegatedAddress(ReadOnlySpan code, [NotNullWhen(true)] out Address? address) + { + if (Eip7702Constants.IsDelegatedCode(code)) + { + address = new Address(code[Eip7702Constants.DelegationHeader.Length..].ToArray()); + return true; + } + + address = null; + return false; + } + } public static class CodeInfoRepositoryExtensions { - public static ICodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, Address codeSource, IReleaseSpec vmSpec) + public static CodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, Address codeSource, IReleaseSpec vmSpec) => codeInfoRepository.GetCachedCodeInfo(codeSource, vmSpec, out _); - public static ICodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress) + public static CodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress) => codeInfoRepository.GetCachedCodeInfo(codeSource, true, vmSpec, out delegationAddress); } diff --git a/src/Nethermind/Nethermind.Evm/IEvmMemory.cs b/src/Nethermind/Nethermind.Evm/IEvmMemory.cs index 4cd68736f318..5f4439109840 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/IOverridableCodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/IOverridableCodeInfoRepository.cs index e2edefad289c..5b9dd5dd3697 100644 --- a/src/Nethermind/Nethermind.Evm/IOverridableCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/IOverridableCodeInfoRepository.cs @@ -4,12 +4,13 @@ using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Evm.CodeAnalysis; -using Nethermind.Evm.State; namespace Nethermind.Evm; public interface IOverridableCodeInfoRepository : ICodeInfoRepository { - void SetCodeOverwrite(IReleaseSpec vmSpec, Address key, ICodeInfo value, Address? redirectAddress = null); - public void ResetOverrides(); + void SetCodeOverride(IReleaseSpec vmSpec, Address key, CodeInfo value); + void MovePrecompile(IReleaseSpec vmSpec, Address precompileAddr, Address targetAddr); + void ResetOverrides(); + void ResetPrecompileOverrides(); } diff --git a/src/Nethermind/Nethermind.Evm/IPrecompileProvider.cs b/src/Nethermind/Nethermind.Evm/IPrecompileProvider.cs index d6654879331d..6e17d2e8c3d3 100644 --- a/src/Nethermind/Nethermind.Evm/IPrecompileProvider.cs +++ b/src/Nethermind/Nethermind.Evm/IPrecompileProvider.cs @@ -9,5 +9,5 @@ namespace Nethermind.Evm; public interface IPrecompileProvider { - public FrozenDictionary GetPrecompiles(); + public FrozenDictionary GetPrecompiles(); } diff --git a/src/Nethermind/Nethermind.Evm/IVirtualMachine.cs b/src/Nethermind/Nethermind.Evm/IVirtualMachine.cs index f2bc60da5c89..c13306a47fe6 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 79de1c3389f8..3f52dd37c436 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 aaeec10e0bfa..7bffb4e634a1 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 24b15a7e66cb..2728f87e21b2 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,10 +109,7 @@ public static EvmExceptionType InstructionCall( Address codeSource = stack.PopAddress(); if (codeSource is null) goto StackUnderflow; - // Charge gas for accessing the account's code (including delegation logic if applicable). - if (!EvmCalculations.ChargeAccountAccessGasWithDelegation(ref gasAvailable, vm, codeSource)) goto OutOfGas; - - 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)) @@ -130,10 +127,23 @@ public static EvmExceptionType InstructionCall( goto StackUnderflow; } + // Pop additional parameters: data offset, data length, output offset, and output length. + if (!stack.PopUInt256(out UInt256 dataOffset) || + !stack.PopUInt256(out UInt256 dataLength) || + !stack.PopUInt256(out UInt256 outputOffset) || + !stack.PopUInt256(out UInt256 outputLength)) + 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. @@ -142,12 +152,10 @@ 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; @@ -155,37 +163,33 @@ public static EvmExceptionType InstructionCall( // 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; } - // Pop additional parameters: data offset, data length, output offset, and output length. - if (!stack.PopUInt256(out UInt256 dataOffset) || - !stack.PopUInt256(out UInt256 dataLength) || - !stack.PopUInt256(out UInt256 outputOffset) || - !stack.PopUInt256(out UInt256 outputLength)) - goto StackUnderflow; - - // Update gas: call cost, memory expansion for input and output, and extra gas. - 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)) + // 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); + CodeInfo codeInfo = vm.CodeInfoRepository.GetCachedCodeInfo(codeSource, spec); // If contract is large, charge for access 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) { @@ -196,7 +200,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) @@ -218,21 +222,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; } @@ -247,14 +251,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, @@ -272,22 +277,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); @@ -304,12 +309,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; @@ -322,21 +328,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; } @@ -347,15 +353,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 74e3ebe8bcaa..99c1b931bde0 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs @@ -7,8 +7,10 @@ using Nethermind.Core.Specs; using Nethermind.Evm.CodeAnalysis; using Nethermind.Evm.EvmObjectFormat; +using Nethermind.Evm.GasPolicy; namespace Nethermind.Evm; + using Int256; internal static partial class EvmInstructions @@ -17,14 +19,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); } /// @@ -32,6 +36,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. /// @@ -40,18 +45,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. @@ -62,20 +68,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) @@ -95,19 +101,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; } /// @@ -115,22 +123,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; @@ -144,20 +153,20 @@ 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; - ICodeInfo codeInfo = vm.CodeInfoRepository + CodeInfo codeInfo = vm.CodeInfoRepository .GetCachedCodeInfo(address, followDelegation: false, spec, out _); // Get the external code from the repository. @@ -166,7 +175,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; } @@ -179,7 +188,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) @@ -201,38 +210,39 @@ 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; // 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; @@ -260,7 +270,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 67048775508a..e8d9e1adbabc 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.ControlFlow.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.ControlFlow.cs @@ -6,9 +6,11 @@ using System.Runtime.Intrinsics; using Nethermind.Core.Specs; using Nethermind.Core; +using Nethermind.Evm.GasPolicy; using Nethermind.Evm.State; namespace Nethermind.Evm; + using Int256; internal static partial class EvmInstructions @@ -19,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)); @@ -42,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; } @@ -63,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. @@ -94,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 @@ -102,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; @@ -113,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; @@ -145,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; } @@ -162,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) || @@ -172,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. @@ -194,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; @@ -210,7 +219,7 @@ private static EvmExceptionType InstructionSelfDestruct(VirtualMachine vm, ref E // If Shanghai DDoS protection is active, charge the appropriate gas cost. if (spec.UseShanghaiDDosProtection) { - gasAvailable -= GasCostOf.SelfDestructEip150; + TGasPolicy.ConsumeSelfDestructGas(ref gas); } // Pop the inheritor address from the stack; signal underflow if missing. @@ -219,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; @@ -236,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; } @@ -244,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; } @@ -279,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; /// @@ -302,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) @@ -311,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 a8c8c57199a0..d433f76793e7 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,20 +82,14 @@ 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; - // Ensure the executing account exists in the world state. If not, create it with a zero balance. - if (!state.AccountExists(env.ExecutingAccount)) - { - state.CreateAccount(env.ExecutingAccount, UInt256.Zero); - } - // Pop parameters off the stack: value to transfer, memory position for the initialization code, // and the length of the initialization code. if (!stack.PopUInt256(out UInt256 value) || @@ -125,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. @@ -142,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); @@ -163,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); @@ -170,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: @@ -183,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, @@ -192,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; } @@ -200,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 CodeInfo? codeInfo, out _); // Take a snapshot of the current state. This allows the state to be reverted if contract creation fails. Snapshot snapshot = state.TakeSnapshot(); @@ -218,6 +218,7 @@ public static EvmExceptionType InstructionCreate( // If the contract address refers to a dead account, clear its storage before creation. if (state.IsDeadAccount(contractAddress)) { + // Note: Seems to be needed on block 21827914 for some reason state.ClearStorage(contractAddress); } @@ -226,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, @@ -237,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 374a69148511..f0015a844897 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 38c3a3284402..7e770d5ec07b 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs @@ -7,11 +7,12 @@ 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; + using Int256; internal static partial class EvmInstructions @@ -20,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. @@ -29,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. @@ -46,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. @@ -64,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; /// @@ -78,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); @@ -165,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); @@ -191,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); @@ -214,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); @@ -237,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); @@ -260,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); @@ -283,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); @@ -306,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); @@ -329,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; } @@ -393,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; @@ -408,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; @@ -420,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; } @@ -484,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, @@ -494,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; ref readonly UInt256 result = ref vm.WorldState.GetBalance(address); stack.PushUInt256(in result); @@ -521,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. - ref readonly UInt256 result = ref vm.WorldState.GetBalance(vm.EvmState.Env.ExecutingAccount); + ref readonly UInt256 result = ref vm.WorldState.GetBalance(vm.VmState.Env.ExecutingAccount); stack.PushUInt256(in result); return EvmExceptionType.None; @@ -545,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, @@ -555,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. @@ -591,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, @@ -601,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)) @@ -643,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; } @@ -664,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. @@ -695,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; @@ -739,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 eb9b85a2e8cd..9c5be6fa2845 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Eof.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Eof.cs @@ -9,12 +9,13 @@ 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; + using Int256; internal static partial class EvmInstructions @@ -59,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); @@ -91,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. @@ -103,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; @@ -118,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) @@ -152,16 +157,16 @@ 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; // Ensure the instruction is only valid for non-legacy (EOF) code. - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) 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. @@ -183,14 +188,14 @@ 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; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) 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. @@ -214,14 +219,14 @@ 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; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; - if (!EvmCalculations.UpdateGas(GasCostOf.DataSize, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.DataSize)) goto OutOfGas; stack.PushUInt32((uint)codeInfo.DataSection.Length); @@ -239,11 +244,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; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; // Pop destination memory offset, data section offset, and size. @@ -255,7 +260,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; @@ -264,11 +269,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) { @@ -291,13 +296,13 @@ 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; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) 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. @@ -317,13 +322,13 @@ 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; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; - if (!EvmCalculations.UpdateGas(GasCostOf.RJumpi, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.RJumpi)) goto OutOfGas; // Pop the condition word. @@ -351,13 +356,13 @@ 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; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) 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. @@ -391,15 +396,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; + CodeInfo 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; @@ -415,11 +421,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, @@ -446,17 +452,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; + CodeInfo 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; @@ -473,15 +480,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; + CodeInfo 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. @@ -511,14 +519,14 @@ 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; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; - if (!EvmCalculations.UpdateGas(GasCostOf.Dupn, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.Dupn)) goto OutOfGas; // Read the immediate operand. @@ -541,14 +549,14 @@ 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; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; - if (!EvmCalculations.UpdateGas(GasCostOf.Swapn, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.Swapn)) goto OutOfGas; // Immediate operand determines the swap index. @@ -570,14 +578,14 @@ 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; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; - if (!EvmCalculations.UpdateGas(GasCostOf.Swapn, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.Swapn)) goto OutOfGas; ReadOnlySpan codeSection = codeInfo.CodeSection.Span; @@ -607,18 +615,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. @@ -626,7 +635,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; @@ -643,11 +652,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) { @@ -658,7 +667,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; @@ -673,8 +682,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. @@ -689,15 +699,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(); @@ -722,13 +732,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); + CodeInfo 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, @@ -738,15 +749,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; @@ -764,17 +775,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++]; @@ -786,7 +798,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; @@ -797,7 +809,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; @@ -814,15 +828,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; + CodeInfo 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; @@ -850,7 +865,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 { @@ -860,7 +876,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. @@ -898,10 +914,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). @@ -916,22 +932,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. @@ -947,7 +966,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); @@ -958,7 +977,7 @@ public static EvmExceptionType InstructionEofCall(Virt } // 11. Retrieve and prepare the target code for execution. - ICodeInfo targetCodeInfo = vm.CodeInfoRepository.GetCachedCodeInfo(codeSource, spec); + CodeInfo targetCodeInfo = vm.CodeInfoRepository.GetCachedCodeInfo(codeSource, spec); // For delegate calls, calling a non-EOF (legacy) target is disallowed. if (typeof(TOpEofCall) == typeof(OpEofDelegateCall) @@ -971,10 +990,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(); @@ -982,7 +1002,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, @@ -992,15 +1012,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 0b1445b164fe..65c2c6f28159 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math1Param.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math1Param.cs @@ -6,11 +6,13 @@ 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; + using Word = Vector256; internal static partial class EvmInstructions @@ -39,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. @@ -109,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)) @@ -149,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 3cf253b52437..28c59de77917 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs @@ -1,14 +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.Extensions; -using static Nethermind.Evm.VirtualMachine; +using Nethermind.Evm.GasPolicy; using static System.Runtime.CompilerServices.Unsafe; +using static Nethermind.Evm.VirtualMachineStatics; namespace Nethermind.Evm; + using Int256; internal static partial class EvmInstructions @@ -37,22 +38,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; @@ -161,7 +164,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); + } + } } /// @@ -252,27 +265,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. @@ -282,7 +296,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) { @@ -295,7 +309,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 c6f07a0b80a0..f9600b8dde30 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math3Param.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math3Param.cs @@ -3,8 +3,10 @@ using System.Runtime.CompilerServices; using Nethermind.Core; +using Nethermind.Evm.GasPolicy; namespace Nethermind.Evm; + using Int256; internal static partial class EvmInstructions @@ -16,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 43971579dde7..c53d6bc19818 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Shifts.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Shifts.cs @@ -3,9 +3,11 @@ using System.Runtime.CompilerServices; using Nethermind.Core; +using Nethermind.Evm.GasPolicy; using static System.Runtime.CompilerServices.Unsafe; namespace Nethermind.Evm; + using Int256; internal static partial class EvmInstructions @@ -37,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; @@ -84,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 d1a5503f4683..ebe50e55f701 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs @@ -8,9 +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; @@ -22,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; } @@ -113,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; @@ -140,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; @@ -158,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; @@ -481,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; } @@ -497,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. @@ -522,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); } @@ -541,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); } @@ -562,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; @@ -586,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++) @@ -624,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 37a4e59e089a..1cd5b0f56ec5 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Storage.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Storage.cs @@ -6,9 +6,11 @@ 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; + using Int256; /// @@ -26,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); @@ -51,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); } @@ -75,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; @@ -107,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); } @@ -136,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; @@ -147,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) @@ -181,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; @@ -192,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) @@ -226,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) @@ -271,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. @@ -281,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) @@ -318,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. @@ -353,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. @@ -379,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; } @@ -417,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; @@ -443,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. @@ -588,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; @@ -601,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); @@ -640,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 0b212751da04..9677f40c3a55 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.cs @@ -4,10 +4,10 @@ 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 { @@ -16,14 +16,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++) { @@ -32,275 +34,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.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; + 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 bdb4935fed15..438b52c3b1c2 100644 --- a/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs +++ b/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs @@ -2,115 +2,38 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.IO; using Nethermind.Core; -using Nethermind.Core.Eip2930; -using Nethermind.Core.Extensions; using Nethermind.Core.Specs; -using Nethermind.Int256; +using Nethermind.Evm.GasPolicy; namespace Nethermind.Evm; - -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 implicit operator EthereumIntrinsicGas(IntrinsicGas gas) => + new(gas.Standard.Value, gas.FloorGas.Value); } public static class IntrinsicGasCalculator { - public static IntrinsicGas Calculate(Transaction transaction, IReleaseSpec releaseSpec) - { - var 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); - } - - private static long CreateCost(Transaction transaction, IReleaseSpec releaseSpec) => - transaction.IsContractCreation && releaseSpec.IsEip2Enabled ? GasCostOf.TxCreate : 0; - - private static long DataCost(Transaction transaction, IReleaseSpec releaseSpec) - { - long baseDataCost = transaction.IsContractCreation && releaseSpec.IsEip3860Enabled - ? EvmCalculations.Div32Ceiling((UInt256)transaction.Data.Length) * - GasCostOf.InitCodeWord - : 0; - - long tokensInCallData = CalculateTokensInCallData(transaction, releaseSpec); - - return baseDataCost + tokensInCallData * GasCostOf.TxDataZero; - } - - public static long AccessListCost(Transaction transaction, IReleaseSpec releaseSpec) - { - AccessList? accessList = transaction.AccessList; - if (accessList is not null) - { - if (!releaseSpec.UseTxAccessLists) - { - ThrowInvalidDataException(releaseSpec); - } - - (int addressesCount, int storageKeysCount) = accessList.Count; - return addressesCount * GasCostOf.AccessAccountListEntry + storageKeysCount * GasCostOf.AccessStorageListEntry; - } - - return 0; - - [DoesNotReturn, StackTraceHidden] - static void ThrowInvalidDataException(IReleaseSpec releaseSpec) - { - throw new InvalidDataException($"Transaction with an access list received within the context of {releaseSpec.Name}. EIP-2930 is not enabled."); - } - } - - private static long AuthorizationListCost(Transaction transaction, IReleaseSpec releaseSpec) - { - AuthorizationTuple[]? transactionAuthorizationList = transaction.AuthorizationList; - - if (transactionAuthorizationList is not null) - { - if (!releaseSpec.IsAuthorizationListEnabled) - { - ThrowInvalidDataException(releaseSpec); - } - - return transactionAuthorizationList.Length * GasCostOf.NewAccount; - } - - return 0; - - [DoesNotReturn, StackTraceHidden] - static void ThrowInvalidDataException(IReleaseSpec releaseSpec) - { - throw new InvalidDataException($"Transaction with an authorization list received within the context of {releaseSpec.Name}. EIP-7702 is not enabled."); - } - } - - private static long CalculateTokensInCallData(Transaction transaction, IReleaseSpec releaseSpec) - { - long txDataNonZeroMultiplier = releaseSpec.IsEip2028Enabled - ? GasCostOf.TxDataNonZeroMultiplierEip2028 - : GasCostOf.TxDataNonZeroMultiplier; - ReadOnlySpan data = transaction.Data.Span; - - int totalZeros = data.CountZeros(); - - return totalZeros + (data.Length - totalZeros) * txDataNonZeroMultiplier; - } - - private static long CalculateFloorCost(Transaction transaction, IReleaseSpec releaseSpec) - { - if (!releaseSpec.IsEip7623Enabled) return 0; - long tokensInCallData = CalculateTokensInCallData(transaction, releaseSpec); - - return GasCostOf.Transaction + tokensInCallData * GasCostOf.TotalCostFloorPerTokenEip7623; - } + /// + /// Calculates intrinsic gas with TGasPolicy type, allowing MultiGas breakdown for Arbitrum. + /// + private static IntrinsicGas Calculate(Transaction transaction, IReleaseSpec releaseSpec) + where TGasPolicy : struct, IGasPolicy => + TGasPolicy.CalculateIntrinsicGas(transaction, releaseSpec); + + /// + /// Non-generic backward-compatible Calculate method. + /// + public static EthereumIntrinsicGas Calculate(Transaction transaction, IReleaseSpec releaseSpec) => + Calculate(transaction, releaseSpec); + + public static long AccessListCost(Transaction transaction, IReleaseSpec releaseSpec) => + IGasPolicy.AccessListCost(transaction, releaseSpec); } diff --git a/src/Nethermind/Nethermind.Evm/InvalidCodeException.cs b/src/Nethermind/Nethermind.Evm/InvalidCodeException.cs deleted file mode 100644 index e7c662416c3d..000000000000 --- a/src/Nethermind/Nethermind.Evm/InvalidCodeException.cs +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Evm -{ - public class InvalidCodeException : EvmException - { - public override EvmExceptionType ExceptionType => EvmExceptionType.InvalidCode; - } -} diff --git a/src/Nethermind/Nethermind.Evm/Metrics.cs b/src/Nethermind/Nethermind.Evm/Metrics.cs index b4f5502b4161..0f94291471b6 100644 --- a/src/Nethermind/Nethermind.Evm/Metrics.cs +++ b/src/Nethermind/Nethermind.Evm/Metrics.cs @@ -19,6 +19,11 @@ public class Metrics private static readonly ZeroContentionCounter _codeDbCache = new(); [Description("Number of Code DB cache reads on thread.")] internal static long ThreadLocalCodeDbCache => _codeDbCache.ThreadLocalValue; + + /// + /// Gets thread-local code DB cache count. Use this for external access. + /// + public static long GetThreadLocalCodeDbCache() => _codeDbCache.ThreadLocalValue; internal static void IncrementCodeDbCache() => _codeDbCache.Increment(); [CounterMetric] [Description("Number of EVM exceptions thrown by contracts.")] @@ -168,6 +173,18 @@ internal static float BlockEstMedianGasPrice } } + /// + /// Gets block gas price data for external access. Returns (min, estMedian, ave, max). + /// Returns null if no gas data available (min is float.MaxValue). + /// + public static (float Min, float EstMedian, float Ave, float Max)? GetBlockGasPrices() + { + if (_blockMinGasPrice == float.MaxValue) + return null; + + return (_blockMinGasPrice, _blockEstMedianGasPrice, _blockAveGasPrice, _blockMaxGasPrice); + } + [GaugeMetric] [Description("Minimum tx gas price in block")] public static float GasPriceMin { get; private set; } diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/IPrecompile.cs b/src/Nethermind/Nethermind.Evm/Precompiles/IPrecompile.cs index b6f3c929476f..c1c06ef18083 100644 --- a/src/Nethermind/Nethermind.Evm/Precompiles/IPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm/Precompiles/IPrecompile.cs @@ -11,16 +11,13 @@ public interface IPrecompile { static virtual Address Address => Address.Zero; static virtual string Name => string.Empty; - + bool SupportsCaching => true; long BaseGasCost(IReleaseSpec releaseSpec); - long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec); - // N.B. returns byte array so that inputData cannot be returned + // N.B. returns a byte array so that inputData cannot be returned // this can lead to the wrong value being returned due to the cache modifying inputData - (byte[], bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec); - - protected static (byte[], bool) Failure { get; } = (Array.Empty(), false); + Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec); } diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/PrecompileHelper.cs b/src/Nethermind/Nethermind.Evm/Precompiles/PrecompileHelper.cs new file mode 100644 index 000000000000..c460c395cfa1 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/Precompiles/PrecompileHelper.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Concurrent; +using System.Reflection; + +namespace Nethermind.Evm.Precompiles; + +public static class PrecompileHelper +{ + private static readonly ConcurrentDictionary _names = new(); + + public static string GetStaticName(this IPrecompile precompile) + { + Type? type = precompile.GetType(); + string name = _names.GetOrAdd(type, t => + { + PropertyInfo? prop = t.GetProperty(nameof(IPrecompile.Name), BindingFlags.Static | BindingFlags.Public); + return prop is null ? string.Empty : prop.GetValue(null) as string; + }); + + return name; + } +} diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/PrecompileInfo.cs b/src/Nethermind/Nethermind.Evm/Precompiles/PrecompileInfo.cs deleted file mode 100644 index 66c6da879ea4..000000000000 --- a/src/Nethermind/Nethermind.Evm/Precompiles/PrecompileInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Evm.Precompiles; - -namespace Nethermind.Evm.CodeAnalysis; - -public sealed class PrecompileInfo(IPrecompile precompile) : ICodeInfo -{ - public ReadOnlyMemory Code => Array.Empty(); - ReadOnlySpan ICodeInfo.CodeSpan => Code.Span; - public IPrecompile? Precompile { get; } = precompile; - - public bool IsPrecompile => true; - public bool IsEmpty => false; -} diff --git a/src/Nethermind/Nethermind.Evm/ReleaseSpecExtensions.cs b/src/Nethermind/Nethermind.Evm/ReleaseSpecExtensions.cs index 41f103b03d43..95aaf8c6bdbb 100644 --- a/src/Nethermind/Nethermind.Evm/ReleaseSpecExtensions.cs +++ b/src/Nethermind/Nethermind.Evm/ReleaseSpecExtensions.cs @@ -4,94 +4,112 @@ using System; using Nethermind.Core; using Nethermind.Core.Specs; +using Nethermind.Int256; namespace Nethermind.Evm { public static class ReleaseSpecExtensions { - public static long GetClearReversalRefund(this IReleaseSpec spec) => - spec.UseHotAndColdStorage - ? RefundOf.SResetReversedHotCold - : spec.UseIstanbulNetGasMetering - ? RefundOf.SResetReversedEip2200 - : spec.UseConstantinopleNetGasMetering - ? RefundOf.SResetReversedEip1283 - : throw new InvalidOperationException("Asking about the net metered cost when net metering not enabled"); - - public static long GetSetReversalRefund(this IReleaseSpec spec) => - spec.UseHotAndColdStorage - ? RefundOf.SSetReversedHotCold - : spec.UseIstanbulNetGasMetering - ? RefundOf.SSetReversedEip2200 - : spec.UseConstantinopleNetGasMetering - ? RefundOf.SSetReversedEip1283 - : throw new InvalidOperationException("Asking about the net metered cost when net metering not enabled"); - - public static long GetSStoreResetCost(this IReleaseSpec spec) => - spec.UseHotAndColdStorage - ? GasCostOf.SReset - GasCostOf.ColdSLoad - : GasCostOf.SReset; - - public static long GetNetMeteredSStoreCost(this IReleaseSpec spec) => - spec.UseHotAndColdStorage - ? GasCostOf.WarmStateRead - : spec.UseIstanbulNetGasMetering - ? GasCostOf.SStoreNetMeteredEip2200 - : spec.UseConstantinopleNetGasMetering - ? GasCostOf.SStoreNetMeteredEip1283 - : throw new InvalidOperationException("Asking about the net metered cost when net metering not enabled"); - - public static long GetBalanceCost(this IReleaseSpec spec) => - spec.UseHotAndColdStorage - ? 0L - : spec.UseLargeStateDDosProtection - ? GasCostOf.BalanceEip1884 + extension(IReleaseSpec spec) + { + public long GetClearReversalRefund() => + spec.UseHotAndColdStorage + ? RefundOf.SResetReversedHotCold + : spec.UseIstanbulNetGasMetering + ? RefundOf.SResetReversedEip2200 + : spec.UseConstantinopleNetGasMetering + ? RefundOf.SResetReversedEip1283 + : throw new InvalidOperationException("Asking about the net metered cost when net metering not enabled"); + + public long GetSetReversalRefund() => + spec.UseHotAndColdStorage + ? RefundOf.SSetReversedHotCold + : spec.UseIstanbulNetGasMetering + ? RefundOf.SSetReversedEip2200 + : spec.UseConstantinopleNetGasMetering + ? RefundOf.SSetReversedEip1283 + : throw new InvalidOperationException("Asking about the net metered cost when net metering not enabled"); + + public long GetSStoreResetCost() => + spec.UseHotAndColdStorage + ? GasCostOf.SReset - GasCostOf.ColdSLoad + : GasCostOf.SReset; + + public long GetNetMeteredSStoreCost() => + spec.UseHotAndColdStorage + ? GasCostOf.WarmStateRead + : spec.UseIstanbulNetGasMetering + ? GasCostOf.SStoreNetMeteredEip2200 + : spec.UseConstantinopleNetGasMetering + ? GasCostOf.SStoreNetMeteredEip1283 + : throw new InvalidOperationException("Asking about the net metered cost when net metering not enabled"); + + public long GetBalanceCost() => + spec.UseHotAndColdStorage + ? 0L + : spec.UseLargeStateDDosProtection + ? GasCostOf.BalanceEip1884 + : spec.UseShanghaiDDosProtection + ? GasCostOf.BalanceEip150 + : GasCostOf.Balance; + + public long GetSLoadCost() => + spec.UseHotAndColdStorage + ? 0L + : spec.UseLargeStateDDosProtection + ? GasCostOf.SLoadEip1884 + : spec.UseShanghaiDDosProtection + ? GasCostOf.SLoadEip150 + : GasCostOf.SLoad; + + public long GetExtCodeHashCost() => + spec.UseHotAndColdStorage + ? 0L + : spec.UseLargeStateDDosProtection + ? GasCostOf.ExtCodeHashEip1884 + : GasCostOf.ExtCodeHash; + + public long GetExtCodeCost() => + spec.UseHotAndColdStorage + ? 0L : spec.UseShanghaiDDosProtection - ? GasCostOf.BalanceEip150 - : GasCostOf.Balance; - - public static long GetSLoadCost(this IReleaseSpec spec) => - spec.UseHotAndColdStorage - ? 0L - : spec.UseLargeStateDDosProtection - ? GasCostOf.SLoadEip1884 + ? GasCostOf.ExtCodeEip150 + : GasCostOf.ExtCode; + + public long GetCallCost() => + spec.UseHotAndColdStorage + ? 0L : spec.UseShanghaiDDosProtection - ? GasCostOf.SLoadEip150 - : GasCostOf.SLoad; - - public static long GetExtCodeHashCost(this IReleaseSpec spec) => - spec.UseHotAndColdStorage - ? 0L - : spec.UseLargeStateDDosProtection - ? GasCostOf.ExtCodeHashEip1884 - : GasCostOf.ExtCodeHash; - - public static long GetExtCodeCost(this IReleaseSpec spec) => - spec.UseHotAndColdStorage - ? 0L - : spec.UseShanghaiDDosProtection - ? GasCostOf.ExtCodeEip150 - : GasCostOf.ExtCode; - - public static long GetCallCost(this IReleaseSpec spec) => - spec.UseHotAndColdStorage - ? 0L - : spec.UseShanghaiDDosProtection - ? GasCostOf.CallEip150 - : GasCostOf.Call; - - public static long GetExpByteCost(this IReleaseSpec spec) => - spec.UseExpDDosProtection - ? GasCostOf.ExpByteEip160 - : GasCostOf.ExpByte; - - public static ulong GetMaxBlobGasPerBlock(this IReleaseSpec spec) => - spec.MaxBlobCount * Eip4844Constants.GasPerBlob; - - public static ulong GetMaxBlobGasPerTx(this IReleaseSpec spec) => - spec.MaxBlobsPerTx * Eip4844Constants.GasPerBlob; - - public static ulong GetTargetBlobGasPerBlock(this IReleaseSpec spec) => - spec.TargetBlobCount * Eip4844Constants.GasPerBlob; + ? GasCostOf.CallEip150 + : GasCostOf.Call; + + public long GetExpByteCost() => + spec.UseExpDDosProtection + ? GasCostOf.ExpByteEip160 + : GasCostOf.ExpByte; + + public ulong GetMaxBlobGasPerBlock() => + spec.MaxBlobCount * Eip4844Constants.GasPerBlob; + + public ulong GetMaxBlobGasPerTx() => + spec.MaxBlobsPerTx * Eip4844Constants.GasPerBlob; + + public ulong GetTargetBlobGasPerBlock() => + spec.TargetBlobCount * Eip4844Constants.GasPerBlob; + + public int MaxProductionBlobCount(int? blockProductionBlobLimit) => + blockProductionBlobLimit >= 0 + ? Math.Min(blockProductionBlobLimit.Value, (int)spec.MaxBlobCount) + : (int)spec.MaxBlobCount; + + public long GetTxDataNonZeroMultiplier() => + spec.IsEip2028Enabled ? GasCostOf.TxDataNonZeroMultiplierEip2028 : GasCostOf.TxDataNonZeroMultiplier; + + public long GetBaseDataCost(Transaction tx) => + tx.IsContractCreation && spec.IsEip3860Enabled + ? EvmCalculations.Div32Ceiling((UInt256)tx.Data.Length) * GasCostOf.InitCodeWord + : 0; + + } } } diff --git a/src/Nethermind/Nethermind.Evm/StackPool.cs b/src/Nethermind/Nethermind.Evm/StackPool.cs index a51014738ee0..3c5a13093888 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/IReadOnlyStateProviderExtensions.cs b/src/Nethermind/Nethermind.Evm/State/IReadOnlyStateProviderExtensions.cs index 3e12df463d50..119d43ef09b3 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 0a50362bedd1..803a18596ead 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 ref readonly UInt256 GetBalance(Address address); new ref readonly ValueHash256 GetCodeHash(Address address); bool HasStateForBlock(BlockHeader? baseBlock); @@ -129,9 +130,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 a0749cce8ab9..c0e73a6a3118 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 000000000000..96f1e1d7de81 --- /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/StateOverridesExtensions.cs b/src/Nethermind/Nethermind.Evm/StateOverridesExtensions.cs index e9d6fa5e0767..0a66d5a405f3 100644 --- a/src/Nethermind/Nethermind.Evm/StateOverridesExtensions.cs +++ b/src/Nethermind/Nethermind.Evm/StateOverridesExtensions.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.Core; using Nethermind.Core.Crypto; @@ -23,6 +24,7 @@ public static void ApplyStateOverridesNoCommit( { if (overrides is not null) { + overridableCodeInfoRepository.ResetPrecompileOverrides(); foreach ((Address address, AccountOverride accountOverride) in overrides) { if (!state.TryGetAccount(address, out AccountStruct account)) @@ -50,7 +52,7 @@ public static void ApplyStateOverrides( { state.ApplyStateOverridesNoCommit(overridableCodeInfoRepository, overrides, spec); - state.Commit(spec); + state.Commit(spec, commitRoots: true); state.CommitTree(blockNumber); state.RecalculateStateRoot(); } @@ -83,15 +85,27 @@ private static void UpdateCode( AccountOverride accountOverride, Address address) { + if (accountOverride.MovePrecompileToAddress is not null) + { + if (!overridableCodeInfoRepository.GetCachedCodeInfo(address, currentSpec).IsPrecompile) + { + throw new ArgumentException($"Account {address} is not a precompile"); + } + + overridableCodeInfoRepository.MovePrecompile( + currentSpec, + address, + accountOverride.MovePrecompileToAddress); + } + if (accountOverride.Code is not null) { stateProvider.InsertCode(address, accountOverride.Code, currentSpec); - overridableCodeInfoRepository.SetCodeOverwrite( + overridableCodeInfoRepository.SetCodeOverride( currentSpec, address, - new CodeInfo(accountOverride.Code), - accountOverride.MovePrecompileToAddress); + new CodeInfo(accountOverride.Code)); } } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/CancellationTxTracer.cs b/src/Nethermind/Nethermind.Evm/Tracing/CancellationTxTracer.cs index 7a2db1770ee3..206f7729156e 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 c99d7f27a0b0..d341d6c6c4af 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/CompositeTxTracer.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/CompositeTxTracer.cs @@ -37,6 +37,7 @@ public CompositeTxTracer(IList txTracers) IsTracingStorage |= t.IsTracingStorage; IsTracingAccess |= t.IsTracingAccess; IsTracingFees |= t.IsTracingFees; + IsTracingLogs |= t.IsTracingLogs; } } @@ -187,24 +188,12 @@ public void ReportOperationRemainingGas(long gas) } } - public void ReportOperationLogs(LogEntry log) - { - for (int index = 0; index < _txTracers.Count; index++) - { - ITxTracer innerTracer = _txTracers[index]; - if (innerTracer.IsTracingInstructions && innerTracer.IsTracingLogs) - { - innerTracer.ReportLog(log); - } - } - } - public void ReportLog(LogEntry log) { for (int index = 0; index < _txTracers.Count; index++) { ITxTracer innerTracer = _txTracers[index]; - if (innerTracer.IsTracingInstructions) + if (innerTracer.IsTracingLogs) { innerTracer.ReportLog(log); } @@ -324,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 e2d791ec8c96..a8b35aca16c8 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 d8500f9220b9..6b3133667957 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/TxTracer.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/TxTracer.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Evm.TransactionProcessing; @@ -13,23 +12,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/TransactionCollisionException.cs b/src/Nethermind/Nethermind.Evm/TransactionCollisionException.cs deleted file mode 100644 index 0c577ef83c04..000000000000 --- a/src/Nethermind/Nethermind.Evm/TransactionCollisionException.cs +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Evm -{ - public class TransactionCollisionException : EvmException - { - public override EvmExceptionType ExceptionType => EvmExceptionType.TransactionCollision; - } -} diff --git a/src/Nethermind/Nethermind.Evm/TransactionExtensions.cs b/src/Nethermind/Nethermind.Evm/TransactionExtensions.cs index 8ab7cb6ace81..811d29510f6b 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 24de0b64ba7d..fb778288e8db 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,8 @@ 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 +84,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 f43a7eb33dbd..126aeb066227 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; @@ -75,6 +90,11 @@ protected enum ExecutionOptions /// SkipValidation = 4, + /// + /// Marker option used by state pre-warmer + /// + Warmup = 8, + /// /// Skip potential fail checks and commit state after execution /// @@ -90,16 +110,16 @@ protected TransactionProcessorBase( ITransactionProcessor.IBlobBaseFeeCalculator? blobBaseFeeCalculator, ISpecProvider? specProvider, IWorldState? worldState, - IVirtualMachine? virtualMachine, + IVirtualMachine? virtualMachine, ICodeInfoRepository? codeInfoRepository, ILogManager? logManager) { - ArgumentNullException.ThrowIfNull(logManager, nameof(logManager)); - ArgumentNullException.ThrowIfNull(specProvider, nameof(specProvider)); - ArgumentNullException.ThrowIfNull(worldState, nameof(worldState)); - ArgumentNullException.ThrowIfNull(virtualMachine, nameof(virtualMachine)); - ArgumentNullException.ThrowIfNull(codeInfoRepository, nameof(codeInfoRepository)); - ArgumentNullException.ThrowIfNull(blobBaseFeeCalculator, nameof(blobBaseFeeCalculator)); + ArgumentNullException.ThrowIfNull(logManager); + ArgumentNullException.ThrowIfNull(specProvider); + ArgumentNullException.ThrowIfNull(worldState); + ArgumentNullException.ThrowIfNull(virtualMachine); + ArgumentNullException.ThrowIfNull(codeInfoRepository); + ArgumentNullException.ThrowIfNull(blobBaseFeeCalculator); Logger = logManager.GetClassLogger(); SpecProvider = specProvider; @@ -140,14 +160,14 @@ public TransactionResult Trace(Transaction transaction, ITxTracer txTracer) => ExecuteCore(transaction, txTracer, ExecutionOptions.SkipValidationAndCommit); public virtual TransactionResult Warmup(Transaction transaction, ITxTracer txTracer) => - ExecuteCore(transaction, txTracer, ExecutionOptions.SkipValidation); + ExecuteCore(transaction, txTracer, ExecutionOptions.Warmup | ExecutionOptions.SkipValidation); private TransactionResult ExecuteCore(Transaction tx, ITxTracer tracer, ExecutionOptions opts) { 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); } @@ -163,26 +183,33 @@ protected virtual TransactionResult Execute(Transaction tx, ITxTracer tracer, Ex // restore is CallAndRestore - previous call, we will restore state after the execution bool restore = opts.HasFlag(ExecutionOptions.Restore); - // commit - is for standard execute, we will commit thee state after execution + // commit - is for standard execute, we will commit the state after execution // !commit - is for build up during block production, we won't commit state after each transaction to support rollbacks // we commit only after all block is constructed 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); + UInt256 effectiveGasPrice = CalculateEffectiveGasPrice(tx, spec.IsEip1559Enabled, header.BaseFeePerGas, out UInt256 opcodeGasPrice); - VirtualMachine.SetTxExecutionContext(new(tx.SenderAddress, _codeInfoRepository, tx.BlobVersionedHashes, in effectiveGasPrice)); + VirtualMachine.SetTxExecutionContext(new(tx.SenderAddress!, _codeInfoRepository, tx.BlobVersionedHashes, in opcodeGasPrice)); UpdateMetrics(opts, effectiveGasPrice); 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); @@ -191,15 +218,19 @@ 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) : ExecuteEvmCall(tx, header, spec, tracer, opts, delegationRefunds, intrinsicGas, accessTracker, gasAvailable, env, out substate, out spentGas); PayFees(tx, header, spec, tracer, in substate, spentGas.SpentGas, premiumPerGas, blobBaseFee, statusCode); - tx.SpentGas = spentGas.SpentGas; + + //only main thread updates transaction + if (!opts.HasFlag(ExecutionOptions.Warmup)) + tx.SpentGas = spentGas.SpentGas; // Finalize if (restore) @@ -211,7 +242,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); @@ -248,15 +279,14 @@ protected virtual TransactionResult Execute(Transaction tx, ITxTracer tracer, Ex } } - if (substate.EvmExceptionType != EvmExceptionType.None) - return TransactionResult.EvmException(substate.EvmExceptionType); - - return TransactionResult.Ok; + return substate.EvmExceptionType != EvmExceptionType.None + ? TransactionResult.EvmException(substate.EvmExceptionType, substate.SubstateError) + : 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; } @@ -268,7 +298,7 @@ private int ProcessDelegations(Transaction tx, IReleaseSpec spec, in StackAccess int refunds = 0; foreach (AuthorizationTuple authTuple in tx.AuthorizationList) { - Address authority = (authTuple.Authority ??= Ecdsa.RecoverAddress(authTuple)); + Address authority = (authTuple.Authority ??= Ecdsa.RecoverAddress(authTuple))!; if (!IsValidForExecution(authTuple, accessTracker, out string? error)) { @@ -357,7 +387,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. @@ -367,14 +397,13 @@ private static void UpdateMetrics(ExecutionOptions opts, UInt256 effectiveGasPri /// The release spec with which the transaction will be executed /// Options (Flags) to use for execution /// Calculated intrinsic gas - /// /// protected virtual TransactionResult ValidateStatic( Transaction tx, BlockHeader header, IReleaseSpec spec, ExecutionOptions opts, - in IntrinsicGas intrinsicGas) + in IntrinsicGas intrinsicGas) { bool validate = !opts.HasFlag(ExecutionOptions.SkipValidation); @@ -402,10 +431,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) { @@ -413,7 +442,7 @@ protected virtual TransactionResult ValidateGas(Transaction tx, BlockHeader head return TransactionResult.GasLimitBelowIntrinsicGas; } - if (validate && tx.GasLimit > header.GasLimit - header.GasUsed) + if (tx.GasLimit > header.GasLimit - header.GasUsed) { TraceLogInvalidTx(tx, $"BLOCK_GAS_LIMIT_EXCEEDED {tx.GasLimit} > {header.GasLimit} - {header.GasUsed}"); return TransactionResult.BlockGasLimitExceeded; @@ -422,7 +451,6 @@ protected virtual TransactionResult ValidateGas(Transaction tx, BlockHeader head return TransactionResult.Ok; } - // TODO Should we remove this already protected virtual bool RecoverSenderIfNeeded(Transaction tx, IReleaseSpec spec, ExecutionOptions opts, in UInt256 effectiveGasPrice) { bool deleteCallerAccount = false; @@ -450,7 +478,7 @@ protected virtual bool RecoverSenderIfNeeded(Transaction tx, IReleaseSpec spec, if (!commit || noValidation || effectiveGasPrice.IsZero) { deleteCallerAccount = !commit || restore; - WorldState.CreateAccount(sender, in UInt256.Zero); + WorldState.CreateAccount(sender!, in UInt256.Zero); } } @@ -463,11 +491,14 @@ 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) + => TGasPolicy.CalculateIntrinsicGas(tx, spec); - protected virtual UInt256 CalculateEffectiveGasPrice(Transaction tx, bool eip1559Enabled, in UInt256 baseFee) => - tx.CalculateEffectiveGasPrice(eip1559Enabled, in baseFee); + protected virtual UInt256 CalculateEffectiveGasPrice(Transaction tx, bool eip1559Enabled, in UInt256 baseFee, out UInt256 opcodeGasPrice) + { + opcodeGasPrice = tx.CalculateEffectiveGasPrice(eip1559Enabled, in baseFee); + return opcodeGasPrice; + } protected virtual bool TryCalculatePremiumPerGas(Transaction tx, in UInt256 baseFee, out UInt256 premiumPerGas) => tx.TryCalculatePremiumPerGas(baseFee, out premiumPerGas); @@ -485,82 +516,86 @@ 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); + + BlockHeader header = VirtualMachine.BlockExecutionContext.Header; + if (validate && !TryCalculatePremiumPerGas(tx, header.BaseFeePerGas, out premiumPerGas)) + { + TraceLogInvalidTx(tx, "MINER_PREMIUM_IS_NEGATIVE"); + return TransactionResult.MinerPremiumNegative; + } - if (validate) + UInt256 senderBalance = WorldState.GetBalance(tx.SenderAddress!); + if (UInt256.SubtractUnderflow(in senderBalance, in tx.ValueRef, out UInt256 balanceLeft)) { - BlockHeader header = VirtualMachine.BlockExecutionContext.Header; - if (!TryCalculatePremiumPerGas(tx, header.BaseFeePerGas, out premiumPerGas)) - { - TraceLogInvalidTx(tx, "MINER_PREMIUM_IS_NEGATIVE"); - return TransactionResult.MinerPremiumNegative; - } + 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)) + bool overflows; + if (spec.IsEip1559Enabled && !tx.IsFree()) + { + overflows = UInt256.MultiplyOverflow((UInt256)tx.GasLimit, tx.MaxFeePerGas, out UInt256 maxGasFee); + if (overflows || balanceLeft < maxGasFee) { - TraceLogInvalidTx(tx, $"INSUFFICIENT_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; } - bool overflows; - if (spec.IsEip1559Enabled && !tx.IsFree()) + if (tx.SupportsBlobs) { - overflows = UInt256.MultiplyOverflow((UInt256)tx.GasLimit, tx.MaxFeePerGas, out UInt256 maxGasFee); - if (overflows || balanceLeft < maxGasFee) + 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_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_BLOB_GAS_FOR_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}"); + return TransactionResult.InsufficientSenderBalance; } } + } - overflows = UInt256.MultiplyOverflow((UInt256)tx.GasLimit, effectiveGasPrice, out senderReservedGasPayment); - if (!overflows && tx.SupportsBlobs) + overflows = UInt256.MultiplyOverflow((UInt256)tx.GasLimit, effectiveGasPrice, out senderReservedGasPayment); + if (!overflows && tx.SupportsBlobs) + { + overflows = !_blobBaseFeeCalculator.TryCalculateBlobBaseFee(header, tx, spec.BlobBaseFeeUpdateFraction, out blobBaseFee); + if (!overflows) { - overflows = !_blobBaseFeeCalculator.TryCalculateBlobBaseFee(header, tx, spec.BlobBaseFeeUpdateFraction, out blobBaseFee); - if (!overflows) - { - overflows = UInt256.AddOverflow(senderReservedGasPayment, blobBaseFee, out senderReservedGasPayment); - } + overflows = UInt256.AddOverflow(senderReservedGasPayment, blobBaseFee, out senderReservedGasPayment); } + } - if (overflows || senderReservedGasPayment > balanceLeft) - { - TraceLogInvalidTx(tx, $"INSUFFICIENT_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}"); - return TransactionResult.InsufficientSenderBalance; - } + if (overflows || senderReservedGasPayment > balanceLeft) + { + TraceLogInvalidTx(tx, $"INSUFFICIENT_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}"); + return TransactionResult.InsufficientSenderBalance; } - if (validate) WorldState.SubtractFromBalance(tx.SenderAddress, senderReservedGasPayment, spec); + if (!senderReservedGasPayment.IsZero) WorldState.SubtractFromBalance(tx.SenderAddress, senderReservedGasPayment, spec); return TransactionResult.Ok; } protected virtual TransactionResult IncrementNonce(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTracer tracer, ExecutionOptions opts) { - if (tx.Nonce != WorldState.GetNonce(tx.SenderAddress!)) + bool validate = !opts.HasFlag(ExecutionOptions.SkipValidation); + 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; } - WorldState.IncrementNonce(tx.SenderAddress); + UInt256 newNonce = validate || nonce < ulong.MaxValue ? nonce + 1 : 0; + WorldState.SetNonce(tx.SenderAddress, newNonce); + return TransactionResult.Ok; } @@ -579,8 +614,7 @@ 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; + CodeInfo? codeInfo; ReadOnlyMemory inputData = tx.IsMessageCall ? tx.Data : default; if (tx.IsContractCreation) { @@ -610,10 +644,10 @@ private TransactionResult BuildExecutionEnvironment( accessTracker.WarmUp(tx.SenderAddress!); } - env = new ExecutionEnvironment( + env = ExecutionEnvironment.Rent( codeInfo: codeInfo, executingAccount: recipient, - caller: tx.SenderAddress, + caller: tx.SenderAddress!, codeSource: recipient, callDepth: 0, transferValue: in tx.ValueRef, @@ -632,10 +666,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 @@ -663,65 +697,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 (!spec.IsEofEnabled || tx.IsLegacyContractCreation) + if (Logger.IsTrace) Logger.Trace("Restoring state from before transaction"); + WorldState.Restore(snapshot); + } + else + { + 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, @@ -730,6 +765,7 @@ private int ExecuteEvmCall( FailContractCreate: if (Logger.IsTrace) Logger.Trace("Restoring state from before transaction"); WorldState.Restore(snapshot); + gasConsumed = RefundOnFailContractCreation(tx, header, spec, opts); Complete: if (!opts.HasFlag(ExecutionOptions.SkipValidation)) @@ -738,10 +774,15 @@ private int ExecuteEvmCall( return statusCode; } - protected virtual bool DeployLegacyContract(IReleaseSpec spec, Address codeOwner, in TransactionSubstate substate, in StackAccessTracker accessedItems, ref long unspentGas) + protected virtual GasConsumed RefundOnFailContractCreation(Transaction tx, BlockHeader header, IReleaseSpec spec, ExecutionOptions opts) + { + return tx.GasLimit; + } + + 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; } @@ -751,7 +792,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(); @@ -761,20 +802,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; } @@ -796,16 +837,14 @@ private bool DeployEofContract(IReleaseSpec spec, Address codeOwner, in Transact VERSION_OFFSET // magic + version + Eof1.MINIMUM_HEADER_SECTION_SIZE // type section : (1 byte of separator + 2 bytes for size) + ONE_BYTE_LENGTH + TWO_BYTE_LENGTH + TWO_BYTE_LENGTH * deployCodeInfo.EofContainer.Header.CodeSections.Count // code section : (1 byte of separator + (CodeSections count) * 2 bytes for size) - + (deployCodeInfo.EofContainer.Header.ContainerSections is null - ? 0 // container section : (0 bytes if no container section is available) - : ONE_BYTE_LENGTH + TWO_BYTE_LENGTH + TWO_BYTE_LENGTH * deployCodeInfo.EofContainer.Header.ContainerSections.Value.Count) // container section : (1 byte of separator + (ContainerSections count) * 2 bytes for size) + + (ONE_BYTE_LENGTH + TWO_BYTE_LENGTH + TWO_BYTE_LENGTH * deployCodeInfo.EofContainer.Header.ContainerSections?.Count) ?? 0 // container section : (1 byte of separator + (ContainerSections count) * 2 bytes for size) ?? (0 bytes if no container section is available) + ONE_BYTE_LENGTH; // data section separator ushort dataSize = (ushort)(deployCodeInfo.DataSection.Length + auxExtraData.Length); 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) @@ -814,7 +853,7 @@ private bool DeployEofContract(IReleaseSpec spec, Address codeOwner, in Transact { accessedItems.WarmUpLargeContract(codeOwner); } - unspentGas -= codeDepositGasCost; + TGasPolicy.Consume(ref unspentGas, codeDepositGasCost); } return true; @@ -874,14 +913,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) @@ -889,7 +928,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) @@ -902,15 +941,20 @@ 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 - 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); @@ -918,14 +962,47 @@ protected virtual long CalculateClaimableRefund(long spentGas, long totalRefund, private static void ThrowInvalidDataException(string message) => throw new InvalidDataException(message); } - public readonly struct TransactionResult(string? error, EvmExceptionType evmException = EvmExceptionType.None) : IEquatable + /// + /// 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 { - [MemberNotNullWhen(false, nameof(TransactionExecuted))] - public string? Error { get; } = error; - public bool TransactionExecuted => Error is null; - public EvmExceptionType EvmExceptionType { get; } = evmException; + private TransactionResult(ErrorType error = ErrorType.None, EvmExceptionType evmException = EvmExceptionType.None) + { + Error = error; + EvmExceptionType = evmException; + } + public ErrorType Error { get; } + public string? SubstateError { get; private init; } + public bool TransactionExecuted => Error is ErrorType.None; + public EvmExceptionType EvmExceptionType { get; } - public static implicit operator TransactionResult(string? error) => new(error); + public string ErrorDescription => Error switch + { + ErrorType.BlockGasLimitExceeded => "Block gas limit exceeded", + ErrorType.GasLimitBelowIntrinsicGas => "gas limit below intrinsic gas", + ErrorType.InsufficientMaxFeePerGasForSenderBalance => "insufficient MaxFeePerGas for sender balance", + ErrorType.InsufficientSenderBalance => "insufficient sender balance", + ErrorType.MalformedTransaction => "malformed", + ErrorType.MinerPremiumNegative => "miner premium is negative", + ErrorType.NonceOverflow => "nonce overflow", + ErrorType.SenderHasDeployedCode => "sender has deployed code", + ErrorType.SenderNotSpecified => "sender not specified", + ErrorType.TransactionSizeOverMaxInitCodeSize => "EIP-3860 - transaction size over max init code size", + ErrorType.TransactionNonceTooHigh => "transaction nonce is too high", + ErrorType.TransactionNonceTooLow => "transaction nonce is too low", + _ => "" + }; + public static implicit operator TransactionResult(ErrorType error) => new(error); public static implicit operator bool(TransactionResult result) => result.TransactionExecuted; public bool Equals(TransactionResult other) => (TransactionExecuted && other.TransactionExecuted) || (Error == other.Error); public static bool operator ==(TransactionResult obj1, TransactionResult obj2) => obj1.Equals(obj2); @@ -933,25 +1010,43 @@ public readonly struct TransactionResult(string? error, EvmExceptionType evmExce public override bool Equals(object? obj) => obj is TransactionResult result && Equals(result); public override int GetHashCode() => TransactionExecuted ? 1 : Error.GetHashCode(); - public override string ToString() => Error is not null ? $"Fail : {Error}" : "Success"; + public override string ToString() => Error is not ErrorType.None ? $"Fail : {ErrorDescription}" : "Success"; - public static TransactionResult EvmException(EvmExceptionType evmExceptionType) + public static TransactionResult EvmException(EvmExceptionType evmExceptionType, string? substateError = null) { - return new TransactionResult(null, evmExceptionType); + return new TransactionResult(ErrorType.None, evmExceptionType) { SubstateError = substateError }; } public static readonly TransactionResult Ok = new(); - public static readonly TransactionResult BlockGasLimitExceeded = "Block gas limit exceeded"; - public static readonly TransactionResult GasLimitBelowIntrinsicGas = "gas limit below intrinsic gas"; - public static readonly TransactionResult InsufficientMaxFeePerGasForSenderBalance = "insufficient MaxFeePerGas for sender balance"; - public static readonly TransactionResult InsufficientSenderBalance = "insufficient sender balance"; - public static readonly TransactionResult MalformedTransaction = "malformed"; - public static readonly TransactionResult MinerPremiumNegative = "miner premium is negative"; - public static readonly TransactionResult NonceOverflow = "nonce overflow"; - public static readonly TransactionResult SenderHasDeployedCode = "sender has deployed code"; - public static readonly TransactionResult SenderNotSpecified = "sender not specified"; - public static readonly TransactionResult TransactionSizeOverMaxInitCodeSize = "EIP-3860 - transaction size over max init code size"; - public static readonly TransactionResult WrongTransactionNonce = "wrong transaction nonce"; + public static readonly TransactionResult BlockGasLimitExceeded = ErrorType.BlockGasLimitExceeded; + public static readonly TransactionResult GasLimitBelowIntrinsicGas = ErrorType.GasLimitBelowIntrinsicGas; + public static readonly TransactionResult InsufficientMaxFeePerGasForSenderBalance = ErrorType.InsufficientMaxFeePerGasForSenderBalance; + public static readonly TransactionResult InsufficientSenderBalance = ErrorType.InsufficientSenderBalance; + public static readonly TransactionResult MalformedTransaction = ErrorType.MalformedTransaction; + public static readonly TransactionResult MinerPremiumNegative = ErrorType.MinerPremiumNegative; + public static readonly TransactionResult NonceOverflow = ErrorType.NonceOverflow; + 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 TransactionNonceTooHigh = ErrorType.TransactionNonceTooHigh; + public static readonly TransactionResult TransactionNonceTooLow = ErrorType.TransactionNonceTooLow; + + public enum ErrorType + { + None, + BlockGasLimitExceeded, + GasLimitBelowIntrinsicGas, + InsufficientMaxFeePerGasForSenderBalance, + InsufficientSenderBalance, + MalformedTransaction, + MinerPremiumNegative, + NonceOverflow, + SenderHasDeployedCode, + SenderNotSpecified, + TransactionSizeOverMaxInitCodeSize, + TransactionNonceTooHigh, + TransactionNonceTooLow, + } } } diff --git a/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs b/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs index ee7003bf7d03..624ac196f00a 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionSubstate.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; @@ -50,16 +50,18 @@ public readonly ref struct TransactionSubstate public bool IsError => Error is not null && !ShouldRevert; public string? Error { get; } + public string? SubstateError { get; } public EvmExceptionType EvmExceptionType { get; } - public (ICodeInfo DeployCode, ReadOnlyMemory Bytes) Output { get; } + public (CodeInfo DeployCode, ReadOnlyMemory Bytes) Output { get; } public bool ShouldRevert { get; } public long Refund { get; } public IToArrayCollection Logs => _logs ?? _emptyLogs; public IHashSetEnumerableCollection
DestroyList => _destroyList ?? _emptyDestroyList; - public TransactionSubstate(EvmExceptionType exceptionType, bool isTracerConnected) + public TransactionSubstate(EvmExceptionType exceptionType, bool isTracerConnected, string? substateError = null) { Error = isTracerConnected ? exceptionType.ToString() : SomeError; + SubstateError = substateError; EvmExceptionType = exceptionType; Refund = 0; _destroyList = _emptyDestroyList; @@ -67,7 +69,7 @@ public TransactionSubstate(EvmExceptionType exceptionType, bool isTracerConnecte ShouldRevert = false; } - public static TransactionSubstate FailedInitCode => new TransactionSubstate("Eip 7698: Invalid CreateTx InitCode"); + public static TransactionSubstate FailedInitCode => new("Eip 7698: Invalid CreateTx InitCode"); private TransactionSubstate(string errorCode) { @@ -78,7 +80,7 @@ private TransactionSubstate(string errorCode) ShouldRevert = true; } - public TransactionSubstate((ICodeInfo eofDeployCode, ReadOnlyMemory bytes) output, + public TransactionSubstate((CodeInfo eofDeployCode, ReadOnlyMemory bytes) output, long refund, IHashSetEnumerableCollection
destroyList, IToArrayCollection logs, diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.Warmup.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.Warmup.cs index 02185b6332e4..8d50611b3f78 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.Warmup.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.Warmup.cs @@ -3,11 +3,14 @@ using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using Nethermind.Core; using Nethermind.Core.Crypto; 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; @@ -18,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]; @@ -37,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; @@ -145,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++) @@ -170,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; } } @@ -189,11 +191,13 @@ private class WarmupBlockhashProvider(ISpecProvider specProvider) : IBlockhashPr public Hash256 GetBlockhash(BlockHeader currentBlock, long number) => GetBlockhash(currentBlock, number, specProvider.GetSpec(currentBlock)); - public Hash256 GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec? spec) + public Hash256 GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec spec) { return Keccak.Compute(spec!.IsBlockHashInStateAvailable ? (Eip2935Constants.RingBufferSize + number).ToString() - : (number).ToString()); + : number.ToString()); } + + public Task Prefetch(BlockHeader currentBlock, CancellationToken token) => Task.CompletedTask; } } diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index 9b2d95f0cae9..5a1e2773ae57 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.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; @@ -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 unsafe class EthereumVirtualMachine( +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 readonly static UInt256 P255Int = (UInt256)System.Numerics.BigInteger.Pow(2, 255); - internal readonly static 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; + + public static readonly byte[] BytesZero = [0]; - internal static readonly byte[] BytesZero32 = + 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 @@ -152,7 +164,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; @@ -167,6 +179,7 @@ public virtual TransactionSubstate ExecuteTransaction( } Exception? failure; + string? substateError; try { CallResult callResult; @@ -174,7 +187,7 @@ public virtual TransactionSubstate ExecuteTransaction( // If the current state represents a precompiled contract, handle it separately. if (_currentState.IsPrecompile) { - callResult = ExecutePrecompile(_currentState, _txTracer.IsTracingActions, out failure); + callResult = ExecutePrecompile(_currentState, _txTracer.IsTracingActions, out failure, out substateError); if (failure is not null) { // Jump to the failure handler if a precompile error occurred. @@ -236,18 +249,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()) @@ -293,6 +306,7 @@ public virtual TransactionSubstate ExecuteTransaction( catch (Exception ex) when (ex is EvmException or OverflowException) { failure = ex; + substateError = null; goto Failure; } @@ -301,7 +315,7 @@ public virtual TransactionSubstate ExecuteTransaction( // Failure handling: attempts to process and possibly finalize the transaction after an error. Failure: - TransactionSubstate failSubstate = HandleFailure(failure, ref previousCallOutput, out bool shouldExit); + TransactionSubstate failSubstate = HandleFailure(failure, substateError, ref previousCallOutput, out bool shouldExit); if (shouldExit) { _currentState = null; @@ -310,7 +324,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; @@ -318,7 +332,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; @@ -340,13 +354,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 @@ -395,16 +409,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) { @@ -448,7 +462,7 @@ protected void HandleEofCreate(in CallResult callResult, EvmState previousState, /// protected void HandleLegacyCreate( in CallResult callResult, - EvmState previousState, + VmState previousState, long gasAvailableForCodeDeposit, ref bool previousStateSucceeded) { @@ -473,19 +487,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); @@ -512,7 +526,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); } } @@ -546,7 +560,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); @@ -576,7 +590,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); } } @@ -596,7 +610,7 @@ protected void HandleRevert(EvmState previousState, in CallResult callResult, re /// A if the failure occurs in the top-level call; otherwise, null /// to indicate that execution should continue with the parent call frame. /// - protected TransactionSubstate HandleFailure(Exception failure, scoped ref ZeroPaddedSpan previousCallOutput, out bool shouldExit) + protected TransactionSubstate HandleFailure(Exception failure, string? substateError, scoped ref ZeroPaddedSpan previousCallOutput, out bool shouldExit) where TTracingInst : struct, IFlag { // Log the exception if trace logging is enabled. @@ -637,7 +651,7 @@ protected TransactionSubstate HandleFailure(Exception failure, sco // For an OverflowException, force the error type to a generic Other error. EvmExceptionType finalErrorType = failure is OverflowException ? EvmExceptionType.Other : errorType; shouldExit = true; - return new TransactionSubstate(finalErrorType, txTracer.IsTracing); + return new TransactionSubstate(finalErrorType, txTracer.IsTracing, substateError); } // For nested call frames, prepare to revert to the parent frame. @@ -759,17 +773,20 @@ protected TransactionSubstate HandleException(scoped in CallResult callResult, s /// /// An output parameter that is set to the encountered exception if the precompile fails; otherwise, null. /// + /// + /// An output parameter that is set to detailed error if the precompile fails; otherwise, null. + /// /// /// 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) + 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, @@ -782,8 +799,10 @@ protected virtual CallResult ExecutePrecompile(EvmState currentState, bool isTra CallResult callResult = RunPrecompile(currentState); // If the precompile did not succeed, handle the failure conditions. - if (!callResult.PrecompileSuccess.Value) + if (callResult.PrecompileSuccess == false) { + substateError = callResult.SubstateError; + // If the failure is due to an exception (e.g., out-of-gas), set the corresponding failure exception. if (callResult.IsException) { @@ -791,19 +810,20 @@ protected virtual CallResult ExecutePrecompile(EvmState currentState, bool isTra goto Failure; } - // If running a precompile on a top-level call frame and it fails, assign a general execution failure. + // 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. failure = null; + substateError = null; return callResult; Failure: @@ -812,9 +832,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, @@ -858,18 +878,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 @@ -885,7 +905,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. @@ -903,14 +923,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) @@ -920,7 +942,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. @@ -931,13 +953,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); } } @@ -954,77 +976,82 @@ private void RevertParityTouchBugAccount() } } - protected static bool UpdateGas(long gasCost, ref long gasAvailable) - { - if (gasAvailable < gasCost) - { - return false; - } - - gasAvailable -= gasCost; - return true; - } - - private enum StorageAccessType - { - SLOAD, - SSTORE - } - - 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 { - (ReadOnlyMemory output, bool success) = precompile.Run(callData, spec); - CallResult callResult = new(output, precompileSuccess: success, fromVersion: 0, shouldRevert: !success); - return callResult; + Result output = precompile.Run(callData, spec); + bool success = output; + return new( + success ? output.Data : [], + precompileSuccess: success, + fromVersion: 0, + shouldRevert: !success, + exceptionType: !success ? EvmExceptionType.PrecompileFailure : EvmExceptionType.None + ) + { + 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); - CallResult callResult = new(output: default, precompileSuccess: false, fromVersion: 0, shouldRevert: true); - return callResult; + 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}"; } /// @@ -1064,9 +1091,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) @@ -1100,7 +1127,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) @@ -1110,7 +1137,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)); } } @@ -1121,24 +1148,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: @@ -1163,10 +1190,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, @@ -1179,32 +1206,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; + CodeInfo 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); @@ -1213,7 +1241,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); @@ -1222,9 +1250,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++; @@ -1233,30 +1264,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) @@ -1270,8 +1305,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 { @@ -1292,35 +1327,35 @@ 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 byte[] data) { - return new CallResult(state); + // Fall back to returning a CallResult with a byte array as the return data. + return new CallResult(null, data, null, codeInfo.Version); } - else if (ReturnData is EofCodeInfo eofCodeInfo) + else if (ReturnData is VmState state) { - return new CallResult(eofCodeInfo, ReturnDataBuffer, null, codeInfo.Version); + return new CallResult(state); } - // Fall back to returning a CallResult with a byte array as the return data. - return new CallResult(null, (byte[])ReturnData, null, codeInfo.Version); + return ReturnEof(codeInfo); Revert: // Return a CallResult indicating a revert. 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. - static ReadOnlySpan GetInstructions(ICodeInfo codeInfo) + static ReadOnlySpan GetInstructions(CodeInfo codeInfo) { ReadOnlySpan codeBytes = codeInfo.CodeSpan; return MemoryMarshal.CreateReadOnlySpan( @@ -1328,6 +1363,10 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(codeBytes)), codeBytes.Length); } + [MethodImpl(MethodImplOptions.NoInlining)] + CallResult ReturnEof(CodeInfo codeInfo) + => new(ReturnData as EofCodeInfo, ReturnDataBuffer, null, codeInfo.Version); + [DoesNotReturn] static void ThrowOperationCanceledException() => throw new OperationCanceledException("Cancellation Requested"); } @@ -1352,27 +1391,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; @@ -1432,3 +1464,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 872db9b4ea8e..0636d957ba75 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.ExternalSigner.Plugin/ClefSignerPlugin.cs b/src/Nethermind/Nethermind.ExternalSigner.Plugin/ClefSignerPlugin.cs index 6ef0780e4d94..4407c839eb3b 100644 --- a/src/Nethermind/Nethermind.ExternalSigner.Plugin/ClefSignerPlugin.cs +++ b/src/Nethermind/Nethermind.ExternalSigner.Plugin/ClefSignerPlugin.cs @@ -9,7 +9,6 @@ using Nethermind.Consensus; using Nethermind.KeyStore.Config; using System.Configuration; -using Nethermind.Wallet; using Nethermind.Serialization.Json; namespace Nethermind.ExternalSigner.Plugin; diff --git a/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs b/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs index e82e8efe8b8c..002762043805 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; @@ -22,10 +21,11 @@ using Nethermind.Consensus.Tracing; using Nethermind.Core.Test.Modules; using Nethermind.Evm; +using Nethermind.Facade.Eth; using Nethermind.Facade.Find; using Nethermind.Facade.Proxy.Models.Simulate; using Nethermind.Facade.Simulate; -using NSubstitute.Core; +using Nethermind.Core.Specs; namespace Nethermind.Facade.Test; @@ -36,6 +36,7 @@ public class BlockchainBridgeTests private IReceiptStorage _receiptStorage; private ITransactionProcessor _transactionProcessor; private ManualTimestamper _timestamper; + private ISpecProvider _specProvider; private IContainer _container; [SetUp] @@ -56,6 +57,7 @@ public Task SetUp() .AddScoped(_transactionProcessor) .Build(); + _specProvider = _container.Resolve(); _blockchainBridge = _container.Resolve(); return Task.CompletedTask; } @@ -69,14 +71,16 @@ public void TearDown() [Test] public void get_transaction_returns_null_when_transaction_not_found() { - _blockchainBridge.GetTransaction(TestItem.KeccakA).Should().Be((null, null, null)); + _blockchainBridge.TryGetTransaction(TestItem.KeccakA, out TransactionLookupResult? result).Should().BeFalse(); + result.Should().BeNull(); } [Test] public void get_transaction_returns_null_when_block_not_found() { _receiptStorage.FindBlockHash(TestItem.KeccakA).Returns(TestItem.KeccakB); - _blockchainBridge.GetTransaction(TestItem.KeccakA).Should().Be((null, null, null)); + _blockchainBridge.TryGetTransaction(TestItem.KeccakA, out TransactionLookupResult? result).Should().BeFalse(); + result.Should().BeNull(); } [Test] @@ -102,9 +106,16 @@ public void get_transaction_returns_receipt_and_transaction_when_found() _receiptStorage.FindBlockHash(receipt.TxHash!).Returns(TestItem.KeccakB); } _receiptStorage.Get(block).Returns(receipts); - var expectation = (receipts[index], Build.A.Transaction.WithNonce((UInt256)index).WithHash(TestItem.Keccaks[index]).TestObject, UInt256.Zero); - var result = _blockchainBridge.GetTransaction(transactions[index].Hash!); - result.Should().BeEquivalentTo(expectation); + _blockchainBridge.TryGetTransaction(transactions[index].Hash!, out TransactionLookupResult? result).Should().BeTrue(); + result!.Value.Transaction.Should().BeEquivalentTo(Build.A.Transaction.WithNonce((UInt256)index).WithHash(TestItem.Keccaks[index]).TestObject); + result.Value.ExtraData.Should().BeEquivalentTo(new TransactionForRpcContext( + chainId: _specProvider.ChainId, + blockHash: block.Hash, + blockNumber: block.Number, + txIndex: receipts[index].Index, + blockTimestamp: block.Timestamp, + baseFee: UInt256.Zero, + receipt: receipts[index])); } [Test] @@ -350,31 +361,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 f5cd1c2d8304..fb58a49c2e90 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.Test/LogIndexBuilderTests.cs b/src/Nethermind/Nethermind.Facade.Test/LogIndexBuilderTests.cs new file mode 100644 index 000000000000..8f5977660136 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade.Test/LogIndexBuilderTests.cs @@ -0,0 +1,273 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Receipts; +using Nethermind.Blockchain.Synchronization; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Events; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test; +using Nethermind.Core.Test.Builders; +using Nethermind.Db.LogIndex; +using Nethermind.Facade.Find; +using Nethermind.Logging; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Facade.Test; + +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class LogIndexBuilderTests +{ + private class TestLogIndexStorage : ILogIndexStorage + { + private int? _minBlockNumber; + private int? _maxBlockNumber; + + public bool Enabled => true; + + public event EventHandler? NewMaxBlockNumber; + public event EventHandler? NewMinBlockNumber; + + public int? MinBlockNumber + { + get => _minBlockNumber; + init => _minBlockNumber = value; + } + + public int? MaxBlockNumber + { + get => _maxBlockNumber; + init => _maxBlockNumber = value; + } + + public IEnumerator GetEnumerator(Address address, int from, int to) => + throw new NotImplementedException(); + + public IEnumerator GetEnumerator(int topicIndex, Hash256 topic, int from, int to) => + throw new NotImplementedException(); + + public string GetDbSize() => 0L.SizeToString(); + + public LogIndexAggregate Aggregate(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats = null) => + new(batch); + + public virtual Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null) + { + var min = Math.Min(aggregate.FirstBlockNum, aggregate.LastBlockNum); + var max = Math.Max(aggregate.FirstBlockNum, aggregate.LastBlockNum); + + if (_minBlockNumber is null || min < _minBlockNumber) + { + if (_minBlockNumber is not null && max != _minBlockNumber - 1) + throw new InvalidOperationException("Invalid receipts order."); + + _minBlockNumber = min; + NewMinBlockNumber?.Invoke(this, min); + } + + if (_maxBlockNumber is null || max > _maxBlockNumber) + { + if (_maxBlockNumber is not null && min != _maxBlockNumber + 1) + throw new InvalidOperationException("Invalid receipts order."); + + _maxBlockNumber = max; + NewMaxBlockNumber?.Invoke(this, max); + } + + return Task.CompletedTask; + } + + public Task RemoveReorgedAsync(BlockReceipts block) => Task.CompletedTask; + + public Task StopAsync() => Task.CompletedTask; + + public ValueTask DisposeAsync() => ValueTask.CompletedTask; + } + + private class FailingLogIndexStorage(int failAfter, Exception exception) : TestLogIndexStorage + { + private int _callCount; + + public override Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null) + { + return Interlocked.Increment(ref _callCount) <= failAfter + ? base.AddReceiptsAsync(aggregate, stats) + : throw exception; + } + } + + private const int MaxReorgDepth = 8; + private const int MaxBlock = 100; + private const int MaxSyncBlock = MaxBlock - MaxReorgDepth; + private const int BatchSize = 10; + + private ILogIndexConfig _config = null!; + private IBlockTree _blockTree = null!; + private ISyncConfig _syncConfig = null!; + private IReceiptStorage _receiptStorage = null!; + private ILogManager _logManager = null!; + private List _testDisposables = null!; + + [SetUp] + public void SetUp() + { + _config = new LogIndexConfig { Enabled = true, MaxReorgDepth = MaxReorgDepth, MaxBatchSize = BatchSize }; + _blockTree = Build.A.BlockTree().OfChainLength(MaxBlock + 1).BlockTree; + _syncConfig = new SyncConfig { FastSync = true, SnapSync = true }; + _receiptStorage = Substitute.For(); + _logManager = new TestLogManager(); + _testDisposables = []; + + Block head = _blockTree.Head!; + _blockTree.SyncPivot = (head.Number, head.Hash); + _syncConfig.PivotNumber = _blockTree.SyncPivot.BlockNumber; + + _receiptStorage + .Get(Arg.Any()) + .Returns(c => []); + } + + [TearDown] + public async Task TearDownAsync() + { + foreach (var disposable in _testDisposables) + { + if (disposable is IAsyncDisposable asyncDisposable) + await asyncDisposable.DisposeAsync(); + else if (disposable is IDisposable disposable1) + disposable1.Dispose(); + } + } + + private LogIndexBuilder GetService(ILogIndexStorage logIndexStorage) + { + return new LogIndexBuilder( + logIndexStorage, _config, _blockTree, _syncConfig, _receiptStorage, _logManager + ).AddTo(_testDisposables); + } + + [Test] + [CancelAfter(60_000)] + public async Task Should_SyncToBarrier( + [Values(1, 10)] int minBarrier, + [Values(1, 16, MaxBlock)] int batchSize, + [Values( + new[] { -1, -1 }, // -1 is treated as null + new[] { 0, MaxSyncBlock / 2 }, + new[] { MaxSyncBlock / 2, MaxSyncBlock / 2 }, + new[] { MaxSyncBlock / 2, MaxSyncBlock }, + new[] { 5, MaxSyncBlock - 5 } + )] + int[] synced, + CancellationToken cancellation + ) + { + _config.MaxBatchSize = batchSize; + _syncConfig.AncientReceiptsBarrier = minBarrier; + Assert.That(_syncConfig.AncientReceiptsBarrierCalc, Is.EqualTo(minBarrier)); + + var expectedMin = minBarrier <= 1 ? 0 : synced[0] < 0 ? minBarrier : Math.Min(synced[0], minBarrier); + var storage = new TestLogIndexStorage + { + MinBlockNumber = synced[0] < 0 ? null : synced[0], + MaxBlockNumber = synced[1] < 0 ? null : synced[1] + }; + + LogIndexBuilder builder = GetService(storage); + + Task completion = WaitBlocksAsync(storage, expectedMin, MaxSyncBlock, cancellation); + await builder.StartAsync(); + await completion; + + using (Assert.EnterMultipleScope()) + { + Assert.That(builder.LastError, Is.Null); + + Assert.That(storage.MinBlockNumber, Is.EqualTo(expectedMin)); + Assert.That(storage.MaxBlockNumber, Is.EqualTo(MaxSyncBlock)); + } + } + + [Test] + public async Task Should_ForwardError( + [Values(0, 1, 4)] int failAfter + ) + { + var exception = new Exception(nameof(Should_ForwardError)); + LogIndexBuilder builder = GetService(new FailingLogIndexStorage(failAfter, exception)); + + await builder.StartAsync(); + + using (Assert.EnterMultipleScope()) + { + Exception thrown = Assert.ThrowsAsync(() => builder.BackwardSyncCompletion.WaitAsync(TimeSpan.FromSeconds(999))); + Assert.That(thrown, Is.EqualTo(exception)); + Assert.That(builder.LastError, Is.EqualTo(exception)); + } + } + + [Test] + [Sequential] + public async Task Should_CompleteImmediately_IfAlreadySynced( + [Values(1, 10, 10, 10)] int minBarrier, + [Values(0, 00, 05, 10)] int minBlock + ) + { + Assert.That(minBlock, Is.LessThanOrEqualTo(minBarrier)); + + _syncConfig.AncientReceiptsBarrier = minBarrier; + LogIndexBuilder builder = GetService(new FailingLogIndexStorage(0, new("Should not set new receipts.")) + { + MinBlockNumber = minBlock, + MaxBlockNumber = MaxSyncBlock + }); + + await builder.StartAsync(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(builder.BackwardSyncCompletion.IsCompleted); + Assert.That(builder.LastError, Is.Null); + Assert.That(builder.LastUpdate, Is.Null); + } + } + + private static Task WaitMaxBlockAsync(TestLogIndexStorage storage, int blockNumber, CancellationToken cancellation) + { + if (storage.MaxBlockNumber >= blockNumber) + return Task.CompletedTask; + + return Wait.ForEventCondition( + cancellation, + e => storage.NewMaxBlockNumber += e, + e => storage.NewMaxBlockNumber -= e, + e => e >= blockNumber + ); + } + + private static Task WaitMinBlockAsync(TestLogIndexStorage storage, int blockNumber, CancellationToken cancellation) + { + if (storage.MinBlockNumber <= blockNumber) + return Task.CompletedTask; + + return Wait.ForEventCondition( + cancellation, + e => storage.NewMinBlockNumber += e, + e => storage.NewMinBlockNumber -= e, + e => e <= blockNumber + ); + } + + private static Task WaitBlocksAsync(TestLogIndexStorage storage, int minBlock, int maxBlock, CancellationToken cancellation) => Task.WhenAll( + WaitMinBlockAsync(storage, minBlock, cancellation), + WaitMaxBlockAsync(storage, maxBlock, cancellation) + ); +} diff --git a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs index 9ab051834b29..d425405ceaa4 100644 --- a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs +++ b/src/Nethermind/Nethermind.Facade/BlockchainBridge.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; @@ -22,6 +22,7 @@ using Nethermind.Core.Specs; using Nethermind.Evm.TransactionProcessing; using Nethermind.Facade.Filters; +using Nethermind.Facade.Eth; using Nethermind.State; using Nethermind.Config; using Nethermind.Facade.Find; @@ -44,8 +45,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,12 +110,33 @@ private bool TryGetCanonicalTransaction( return (null, 0, null, 0); } - 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) - ? (null, transaction, null) - : (null, null, null); + public bool TryGetTransaction(Hash256 txHash, [NotNullWhen(true)] out TransactionLookupResult? result, bool checkTxnPool = true) + { + if (TryGetCanonicalTransaction(txHash, out Transaction? tx, out TxReceipt? txReceipt, out Block? block, out TxReceipt[]? _)) + { + TransactionForRpcContext extraData = new( + chainId: specProvider.ChainId, + blockHash: block.Hash, + blockNumber: block.Number, + txIndex: txReceipt!.Index, + blockTimestamp: block.Timestamp, + baseFee: block.BaseFeePerGas, + receipt: txReceipt); + + + result = new TransactionLookupResult(tx, extraData); + return true; + } + + if (checkTxnPool && txPool.TryGetPendingTransaction(txHash, out Transaction? transaction)) + { + result = new TransactionLookupResult(transaction, new(specProvider.ChainId)); + return true; + } + + result = null; + return false; + } public TxReceipt? GetReceipt(Hash256 txHash) { @@ -135,7 +157,8 @@ public CallOutput Call(BlockHeader header, Transaction tx, Dictionary 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( @@ -322,17 +347,17 @@ 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; } public int NewBlockFilter() { - BlockFilter filter = filterStore.CreateBlockFilter(blockTree.Head!.Number); + BlockFilter filter = filterStore.CreateBlockFilter(); filterStore.SaveFilter(filter); return filter.Id; } @@ -375,9 +400,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) @@ -399,9 +424,9 @@ public IEnumerable FindLogs(LogFilter filter, CancellationToken cance { var error = txResult switch { - { TransactionExecuted: true } when txResult.EvmExceptionType is not EvmExceptionType.None => txResult.EvmExceptionType.GetEvmExceptionDescription(), + { TransactionExecuted: true } when txResult.EvmExceptionType is not (EvmExceptionType.None or EvmExceptionType.Revert) => txResult.SubstateError ?? txResult.EvmExceptionType.GetEvmExceptionDescription(), { TransactionExecuted: true } when tracerError is not null => tracerError, - { TransactionExecuted: false, Error: not null } => txResult.Error, + { TransactionExecuted: false, Error: not TransactionResult.ErrorType.None } => txResult.ErrorDescription, _ => null }; diff --git a/src/Nethermind/Nethermind.Facade/CallOutput.cs b/src/Nethermind/Nethermind.Facade/CallOutput.cs index 550d90dd279d..7f46c85128d1 100644 --- a/src/Nethermind/Nethermind.Facade/CallOutput.cs +++ b/src/Nethermind/Nethermind.Facade/CallOutput.cs @@ -16,5 +16,7 @@ public class CallOutput public bool InputError { get; set; } + public bool ExecutionReverted { get; set; } + public AccessList? AccessList { get; set; } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/BlockForRpc.cs b/src/Nethermind/Nethermind.Facade/Eth/BlockForRpc.cs index c50b960e3a31..d8f0b0b65315 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/BlockForRpc.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/BlockForRpc.cs @@ -18,14 +18,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; @@ -33,7 +32,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]; @@ -64,6 +63,12 @@ public BlockForRpc(Block block, bool includeFullTransactionData, ISpecProvider s { ParentBeaconBlockRoot = block.ParentBeaconBlockRoot; } + + // Set TD only if network is not merged + if (specProvider.MergeBlockNumber is null) + { + TotalDifficulty = block.Difficulty.IsZero ? null : block.TotalDifficulty ?? UInt256.Zero; + } } Number = block.Number; @@ -73,7 +78,7 @@ public BlockForRpc(Block block, bool includeFullTransactionData, ISpecProvider s Size = _blockDecoder.GetLength(block, RlpBehaviors.None); StateRoot = block.StateRoot; Timestamp = block.Timestamp; - TotalDifficulty = block.Difficulty.IsZero ? null : block.TotalDifficulty ?? UInt256.Zero; + if (!skipTxs) { Transactions = includeFullTransactionData @@ -100,30 +105,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; } @@ -169,7 +167,15 @@ private static object[] GetTransactionsForRpc(Block block, ulong chainId) TransactionForRpc[] txs = new TransactionForRpc[transactions.Length]; for (var i = 0; i < transactions.Length; i++) { - txs[i] = TransactionForRpc.FromTransaction(transactions[i], block.Hash, block.Number, i, block.BaseFeePerGas, chainId); + TransactionForRpcContext extraData = new( + chainId: chainId, + blockHash: block.Hash, + blockNumber: block.Number, + txIndex: i, + blockTimestamp: block.Timestamp, + baseFee: block.BaseFeePerGas, + receipt: null); + txs[i] = TransactionForRpc.FromTransaction(transactions[i], extraData); } return txs; } diff --git a/src/Nethermind/Nethermind.Facade/Eth/FacadeJsonContext.cs b/src/Nethermind/Nethermind.Facade/Eth/FacadeJsonContext.cs new file mode 100644 index 000000000000..4204be813233 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/FacadeJsonContext.cs @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json.Serialization; +using Nethermind.Facade.Eth.RpcTransaction; +using Nethermind.Facade.Filters; + +namespace Nethermind.Facade.Eth; + +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Metadata, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(BlockForRpc))] +[JsonSerializable(typeof(FilterLog))] +[JsonSerializable(typeof(TransactionForRpc))] +[JsonSerializable(typeof(SyncingResult))] +public partial class FacadeJsonContext : JsonSerializerContext; diff --git a/src/Nethermind/Nethermind.Facade/Eth/IFromTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/IFromTransaction.cs index cebf8c7b8d27..9a7736e16c5a 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/IFromTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/IFromTransaction.cs @@ -10,15 +10,45 @@ namespace Nethermind.Facade.Eth; public interface IFromTransaction : ITxTyped where T : TransactionForRpc { - static abstract T FromTransaction(Transaction tx, TransactionConverterExtraData extraData); + static abstract T FromTransaction(Transaction tx, in TransactionForRpcContext extraData); } -public readonly struct TransactionConverterExtraData +public readonly record struct TransactionForRpcContext { - public ulong? ChainId { get; init; } - public Hash256? BlockHash { get; init; } - public long? BlockNumber { get; init; } - public int? TxIndex { get; init; } - public UInt256? BaseFee { get; init; } - public TxReceipt Receipt { get; init; } + public ulong? ChainId { get; } + public Hash256? BlockHash { get; } + public long? BlockNumber { get; } + public ulong? BlockTimestamp { get; } + public int? TxIndex { get; } + public UInt256? BaseFee { get; } + public TxReceipt? Receipt { get; } + + public TransactionForRpcContext(ulong chainId) + { + ChainId = chainId; + BlockHash = null; + BlockNumber = null; + BlockTimestamp = null; + TxIndex = null; + BaseFee = null; + Receipt = null; + } + + public TransactionForRpcContext( + ulong chainId, + Hash256 blockHash, + long blockNumber, + int txIndex, + ulong blockTimestamp, + UInt256 baseFee, + TxReceipt? receipt = null) + { + ChainId = chainId; + BlockHash = blockHash; + BlockNumber = blockNumber; + BlockTimestamp = blockTimestamp; + TxIndex = txIndex; + BaseFee = baseFee; + Receipt = receipt; + } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/AccessListForRpc.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/AccessListForRpc.cs index 7527ee93a49d..add251980e3c 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/AccessListForRpc.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/AccessListForRpc.cs @@ -18,8 +18,10 @@ public class AccessListForRpc { private readonly IEnumerable _items; - [JsonConstructor] - public AccessListForRpc() { } + public AccessListForRpc() + { + _items = Array.Empty(); + } private AccessListForRpc(IEnumerable items) { @@ -33,10 +35,9 @@ private class Item [JsonConverter(typeof(StorageCellIndexConverter))] public IEnumerable? StorageKeys { get; set; } - [JsonConstructor] public Item() { } - public Item(Address address, IEnumerable storageKeys) + public Item(Address address, IEnumerable? storageKeys) { Address = address; StorageKeys = storageKeys; @@ -67,10 +68,98 @@ public class JsonConverter : JsonConverter { public override AccessListForRpc? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - List? list = JsonSerializer.Deserialize>(ref reader, options); - return list is null ? null : new AccessListForRpc(list); + if (reader.TokenType != JsonTokenType.StartArray) + throw new JsonException("Expected start of array"); + + const int maxItems = 1000; + const int maxStorageKeysPerItem = 1000; + const int maxStorageKeys = 10000; + + var items = new List(); + int itemCount = 0; + int totalItemStorageItemsCount = 0; + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + break; + + if (itemCount >= maxItems) + throw new JsonException($"Access list cannot have more than {maxItems} items."); + + if (reader.TokenType != JsonTokenType.StartObject) + throw new JsonException("Expected start of item object"); + + Address? address = null; + List? storageKeys = null; + + // Read Item properties + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + break; + + if (reader.TokenType != JsonTokenType.PropertyName) + throw new JsonException("Expected property name"); + + string propName = reader.GetString() ?? throw new JsonException("Property name cannot be null"); + reader.Read(); // move to property value + + if (string.Equals(propName, nameof(Item.Address), StringComparison.OrdinalIgnoreCase)) + { + address = JsonSerializer.Deserialize
(ref reader, options); + } + else if (string.Equals(propName, nameof(Item.StorageKeys), StringComparison.OrdinalIgnoreCase)) + { + if (reader.TokenType == JsonTokenType.Null) + { + storageKeys = null; + } + else if (reader.TokenType == JsonTokenType.StartArray) + { + storageKeys = new List(); + int currentItemStorageItemsCount = 0; + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + break; + + if (currentItemStorageItemsCount >= maxStorageKeysPerItem) + throw new JsonException($"An item cannot have more than {maxStorageKeysPerItem} storage keys."); + + if (totalItemStorageItemsCount >= maxStorageKeys) + throw new JsonException($"Access List cannot have more than {maxStorageKeys} storage keys."); + + UInt256 key = JsonSerializer.Deserialize(ref reader, options); + storageKeys.Add(key); + currentItemStorageItemsCount++; + totalItemStorageItemsCount++; + } + } + else + { + throw new JsonException("Expected array or null for StorageKeys"); + } + } + else + { + // Skip unknown properties + reader.Skip(); + } + } + + if (address is null) + throw new JsonException("Item missing required Address"); + + items.Add(new Item(address, storageKeys)); + itemCount++; + } + + return new AccessListForRpc(items); } + public override void Write(Utf8JsonWriter writer, AccessListForRpc value, JsonSerializerOptions options) { JsonSerializer.Serialize(writer, value._items, options); diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/AccessListTransactionForRpc.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/AccessListTransactionForRpc.cs index 5c8c16880a02..7b4f847ea9c4 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/AccessListTransactionForRpc.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/AccessListTransactionForRpc.cs @@ -3,7 +3,6 @@ using System.Text.Json.Serialization; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Int256; namespace Nethermind.Facade.Eth.RpcTransaction; @@ -27,24 +26,26 @@ public class AccessListTransactionForRpc : LegacyTransactionForRpc, IFromTransac [JsonConstructor] public AccessListTransactionForRpc() { } - public AccessListTransactionForRpc(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null, ulong? chainId = null) - : base(transaction, txIndex, blockHash, blockNumber) + public AccessListTransactionForRpc(Transaction transaction, in TransactionForRpcContext extraData) + : base(transaction, extraData) { AccessList = AccessListForRpc.FromAccessList(transaction.AccessList); YParity = transaction.Signature?.RecoveryId ?? 0; - ChainId = transaction.ChainId ?? chainId ?? BlockchainIds.Mainnet; + ChainId = transaction.ChainId ?? extraData.ChainId ?? BlockchainIds.Mainnet; V = YParity ?? 0; } - public override Transaction ToTransaction() + public override Result ToTransaction(bool validateUserInput = false) { - var tx = base.ToTransaction(); + Result baseResult = base.ToTransaction(validateUserInput); + if (baseResult.IsError) return baseResult; + Transaction tx = baseResult.Data; tx.AccessList = AccessList?.ToAccessList() ?? Core.Eip2930.AccessList.Empty; return tx; } - public new static AccessListTransactionForRpc FromTransaction(Transaction tx, TransactionConverterExtraData extraData) - => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber, chainId: extraData.ChainId); + public new static AccessListTransactionForRpc FromTransaction(Transaction tx, in TransactionForRpcContext extraData) + => new(tx, extraData); } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/AuthorizationListForRpc.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/AuthorizationListForRpc.cs index 275e9a001f6d..aa89959ff209 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/AuthorizationListForRpc.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/AuthorizationListForRpc.cs @@ -19,8 +19,10 @@ public class AuthorizationListForRpc : IEnumerable { private readonly IEnumerable _tuples; - [JsonConstructor] - public AuthorizationListForRpc() { } + public AuthorizationListForRpc() + { + _tuples = Array.Empty(); + } private AuthorizationListForRpc(IEnumerable tuples) { @@ -36,7 +38,6 @@ public class RpcAuthTuple public UInt256 S { get; set; } public UInt256 R { get; set; } - [JsonConstructor] public RpcAuthTuple() { } public RpcAuthTuple(UInt256 chainId, ulong nonce, Address address, ulong yParity, UInt256 s, UInt256 r) @@ -79,7 +80,7 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } - private class JsonConverter : JsonConverter + public class JsonConverter : JsonConverter { public override AuthorizationListForRpc? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/BlobTransactionForRpc.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/BlobTransactionForRpc.cs index f683fcbdadb0..d2cc7bda4b5a 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/BlobTransactionForRpc.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/BlobTransactionForRpc.cs @@ -3,7 +3,7 @@ using System.Text.Json.Serialization; using Nethermind.Core; -using Nethermind.Core.Crypto; +using Nethermind.Crypto; using Nethermind.Int256; namespace Nethermind.Facade.Eth.RpcTransaction; @@ -15,11 +15,8 @@ public class BlobTransactionForRpc : EIP1559TransactionForRpc, IFromTransaction< public override TxType? Type => TxType; [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - [JsonDiscriminator] public UInt256? MaxFeePerBlobGas { get; set; } - // TODO: Each item should be a 32 byte array - // Currently we don't enforce this (hashes can have any length) [JsonIgnore(Condition = JsonIgnoreCondition.Never)] [JsonDiscriminator] public byte[][]? BlobVersionedHashes { get; set; } @@ -30,23 +27,43 @@ public class BlobTransactionForRpc : EIP1559TransactionForRpc, IFromTransaction< [JsonConstructor] public BlobTransactionForRpc() { } - public BlobTransactionForRpc(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null, UInt256? baseFee = null, ulong? chainId = null) - : base(transaction, txIndex, blockHash, blockNumber, baseFee, chainId) + public BlobTransactionForRpc(Transaction transaction, in TransactionForRpcContext extraData) + : base(transaction, extraData) { MaxFeePerBlobGas = transaction.MaxFeePerBlobGas ?? 0; BlobVersionedHashes = transaction.BlobVersionedHashes ?? []; } - public override Transaction ToTransaction() + public override Result ToTransaction(bool validateUserInput = false) { - var tx = base.ToTransaction(); + if (BlobVersionedHashes is null || BlobVersionedHashes.Length == 0) + return RpcTransactionErrors.AtLeastOneBlobInBlobTransaction; + + foreach (byte[]? hash in BlobVersionedHashes) + { + if (hash is null || hash.Length != Eip4844Constants.BytesPerBlobVersionedHash) + return RpcTransactionErrors.InvalidBlobVersionedHashSize; + + if (hash[0] != KzgPolynomialCommitments.KzgBlobHashVersionV1) + return RpcTransactionErrors.InvalidBlobVersionedHashVersion; + } + + if (To is null) + return RpcTransactionErrors.MissingToInBlobTx; + + if (validateUserInput && MaxFeePerBlobGas?.IsZero == true) + return RpcTransactionErrors.ZeroMaxFeePerBlobGas; + + Result baseResult = base.ToTransaction(validateUserInput); + if (!baseResult) return baseResult; + Transaction tx = baseResult.Data; tx.MaxFeePerBlobGas = MaxFeePerBlobGas; tx.BlobVersionedHashes = BlobVersionedHashes; return tx; } - public new static BlobTransactionForRpc FromTransaction(Transaction tx, TransactionConverterExtraData extraData) - => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber, baseFee: extraData.BaseFee, chainId: extraData.ChainId); + public new static BlobTransactionForRpc FromTransaction(Transaction tx, in TransactionForRpcContext extraData) + => new(tx, extraData); } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/EIP1559TransactionForRpc.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/EIP1559TransactionForRpc.cs index 5ced3fc972b8..5ded6600e1ae 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/EIP1559TransactionForRpc.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/EIP1559TransactionForRpc.cs @@ -4,7 +4,6 @@ using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Core.Extensions; -using Nethermind.Core.Crypto; using Nethermind.Int256; namespace Nethermind.Facade.Eth.RpcTransaction; @@ -26,22 +25,39 @@ public class EIP1559TransactionForRpc : AccessListTransactionForRpc, IFromTransa [JsonConstructor] public EIP1559TransactionForRpc() { } - public EIP1559TransactionForRpc(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null, UInt256? baseFee = null, ulong? chainId = null) - : base(transaction, txIndex, blockHash, blockNumber, chainId) + public EIP1559TransactionForRpc(Transaction transaction, in TransactionForRpcContext extraData) + : base(transaction, extraData) { MaxFeePerGas = transaction.MaxFeePerGas; MaxPriorityFeePerGas = transaction.MaxPriorityFeePerGas; - GasPrice = baseFee is not null - ? transaction.CalculateEffectiveGasPrice(eip1559Enabled: true, baseFee.Value) + // ReSharper disable once VirtualMemberCallInConstructor + GasPrice = extraData.BaseFee is not null + ? transaction.CalculateEffectiveGasPrice(eip1559Enabled: true, extraData.BaseFee.Value) : transaction.MaxFeePerGas; } - public override Transaction ToTransaction() + public override Result ToTransaction(bool validateUserInput = false) { - var tx = base.ToTransaction(); + if (validateUserInput) + { + // Reject ambiguous input: both gasPrice and EIP-1559 fields + if (GasPrice is not null && (MaxFeePerGas is not null || MaxPriorityFeePerGas is not null)) + return RpcTransactionErrors.GasPriceInEip1559; - tx.GasPrice = MaxPriorityFeePerGas ?? 0; - tx.DecodedMaxFeePerGas = MaxFeePerGas ?? 0; + // Reject zero maxFeePerGas from user input + if (MaxFeePerGas?.IsZero == true) + return RpcTransactionErrors.ZeroMaxFeePerGas; + + if (MaxFeePerGas < MaxPriorityFeePerGas) + return RpcTransactionErrors.MaxFeePerGasSmallerThanMaxPriorityFeePerGas(MaxFeePerGas, MaxPriorityFeePerGas); + } + + Result baseResult = base.ToTransaction(validateUserInput); + if (baseResult.IsError) return baseResult; + + Transaction tx = baseResult.Data; + tx.GasPrice = MaxPriorityFeePerGas ?? UInt256.Zero; + tx.DecodedMaxFeePerGas = MaxFeePerGas ?? UInt256.Zero; return tx; } @@ -49,6 +65,6 @@ public override Transaction ToTransaction() public override bool ShouldSetBaseFee() => base.ShouldSetBaseFee() || MaxFeePerGas.IsPositive() || MaxPriorityFeePerGas.IsPositive(); - public new static EIP1559TransactionForRpc FromTransaction(Transaction tx, TransactionConverterExtraData extraData) - => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber, baseFee: extraData.BaseFee, chainId: extraData.ChainId); + public new static EIP1559TransactionForRpc FromTransaction(Transaction tx, in TransactionForRpcContext extraData) + => new(tx, extraData); } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/LegacyTransactionForRpc.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/LegacyTransactionForRpc.cs index 229b720c6cef..de781ced6f1a 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/LegacyTransactionForRpc.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/LegacyTransactionForRpc.cs @@ -8,6 +8,7 @@ using Nethermind.Core.Extensions; using Nethermind.Crypto; using Nethermind.Int256; +// ReSharper disable VirtualMemberCallInConstructor namespace Nethermind.Facade.Eth.RpcTransaction; @@ -59,8 +60,8 @@ public class LegacyTransactionForRpc : TransactionForRpc, ITxTyped, IFromTransac [JsonConstructor] public LegacyTransactionForRpc() { } - public LegacyTransactionForRpc(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null) - : base(transaction, txIndex, blockHash, blockNumber) + public LegacyTransactionForRpc(Transaction transaction, in TransactionForRpcContext extraData) + : base(transaction, extraData) { Nonce = transaction.Nonce; To = transaction.To; @@ -69,7 +70,6 @@ public LegacyTransactionForRpc(Transaction transaction, int? txIndex = null, Has Value = transaction.Value; Input = transaction.Data.AsArray(); GasPrice = transaction.GasPrice; - ChainId = transaction.ChainId; Signature? signature = transaction.Signature; if (signature is null) @@ -77,42 +77,40 @@ public LegacyTransactionForRpc(Transaction transaction, int? txIndex = null, Has R = UInt256.Zero; S = UInt256.Zero; V = 0; + ChainId = transaction.ChainId; } else { R = new UInt256(signature.R.Span, true); S = new UInt256(signature.S.Span, true); V = signature.V; + ChainId = transaction.ChainId ?? signature.ChainId; } } - public override Transaction ToTransaction() + public override Result ToTransaction(bool validateUserInput = false) { - var tx = base.ToTransaction(); + if (validateUserInput && To is null && Input is null or { Length: 0 }) + return RpcTransactionErrors.ContractCreationWithoutData; - tx.Nonce = Nonce ?? 0; // TODO: Should we pick the last nonce? + Result baseResult = base.ToTransaction(validateUserInput); + if (baseResult.IsError) return baseResult; + + Transaction tx = baseResult.Data; + tx.Nonce = Nonce ?? UInt256.Zero; // TODO: Should we pick the last nonce? tx.To = To; tx.GasLimit = Gas ?? 90_000; - tx.Value = Value ?? 0; + tx.Value = Value ?? UInt256.Zero; tx.Data = Input; - tx.GasPrice = GasPrice ?? 0; + tx.GasPrice = GasPrice ?? UInt256.Zero; tx.ChainId = ChainId; - tx.SenderAddress = From ?? Address.SystemUser; - if ((R != 0 || S != 0) && (R is not null || S is not null)) + tx.SenderAddress = From ?? Address.Zero; + if ((R?.IsZero == false || S?.IsZero == false) && (R is not null || S is not null)) { - ulong v; - if (V is null) - { - v = 0; - } - else if (V.Value > 1) - { - v = V.Value.ToUInt64(null); // non protected - } - else - { - v = EthereumEcdsaExtensions.CalculateV(ChainId ?? 0, V.Value == 1); // protected - } + ulong v = V is null ? 0 + : V.Value > 1 + ? V.Value.ToUInt64(null) // non protected + : EthereumEcdsaExtensions.CalculateV(ChainId ?? 0, V.Value == 1); // protected tx.Signature = new(R ?? UInt256.Zero, S ?? UInt256.Zero, v); } @@ -122,18 +120,18 @@ public override Transaction ToTransaction() public override void EnsureDefaults(long? gasCap) { - if (gasCap is null || gasCap == 0) + if (gasCap is null or 0) gasCap = long.MaxValue; - Gas = Gas is null || Gas == 0 + Gas = Gas is null or 0 ? gasCap : Math.Min(gasCap.Value, Gas.Value); - From ??= Address.SystemUser; + From ??= Address.Zero; } public override bool ShouldSetBaseFee() => GasPrice.IsPositive(); - public static LegacyTransactionForRpc FromTransaction(Transaction tx, TransactionConverterExtraData extraData) => - new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber); + public static LegacyTransactionForRpc FromTransaction(Transaction tx, in TransactionForRpcContext extraData) => + new(tx, extraData); } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcTransactionErrors.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcTransactionErrors.cs new file mode 100644 index 000000000000..d48aee14cc87 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcTransactionErrors.cs @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Int256; + +namespace Nethermind.Facade.Eth.RpcTransaction; + +public static class RpcTransactionErrors +{ + public const string ContractCreationWithoutData = "contract creation without any data provided"; + public const string GasPriceInEip1559 = "both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified"; + public const string ZeroMaxFeePerGas = "maxFeePerGas must be non-zero"; + public const string AtLeastOneBlobInBlobTransaction = "need at least 1 blob for a blob transaction"; + public const string InvalidBlobVersionedHashSize = "blob versioned hash must be 32 bytes"; + public const string InvalidBlobVersionedHashVersion = "blob versioned hash version must be 0x01"; + public const string MissingToInBlobTx = "missing \"to\" in blob transaction"; + public const string ZeroMaxFeePerBlobGas = "maxFeePerBlobGas, if specified, must be non-zero"; + + public static string MaxFeePerGasSmallerThanMaxPriorityFeePerGas(UInt256? maxFeePerGas, UInt256? maxPriorityFeePerGas) + => $"maxFeePerGas ({maxFeePerGas}) < maxPriorityFeePerGas ({maxPriorityFeePerGas})"; +} diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/SetCodeTransactionForRpc.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/SetCodeTransactionForRpc.cs index cfe953bc549f..52c1ca5e1246 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/SetCodeTransactionForRpc.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/SetCodeTransactionForRpc.cs @@ -3,8 +3,6 @@ using System.Text.Json.Serialization; using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Int256; namespace Nethermind.Facade.Eth.RpcTransaction; @@ -20,21 +18,23 @@ public class SetCodeTransactionForRpc : EIP1559TransactionForRpc, IFromTransacti [JsonConstructor] public SetCodeTransactionForRpc() { } - public SetCodeTransactionForRpc(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null, UInt256? baseFee = null, ulong? chainId = null) - : base(transaction, txIndex, blockHash, blockNumber, baseFee, chainId) + public SetCodeTransactionForRpc(Transaction transaction, in TransactionForRpcContext extraData) + : base(transaction, extraData) { AuthorizationList = AuthorizationListForRpc.FromAuthorizationList(transaction.AuthorizationList); } - public override Transaction ToTransaction() + public override Result ToTransaction(bool validateUserInput = false) { - var tx = base.ToTransaction(); + Result baseResult = base.ToTransaction(validateUserInput); + if (baseResult.IsError) return baseResult; + Transaction tx = baseResult.Data; tx.AuthorizationList = AuthorizationList?.ToAuthorizationList() ?? []; return tx; } - public new static SetCodeTransactionForRpc FromTransaction(Transaction tx, TransactionConverterExtraData extraData) - => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber, baseFee: extraData.BaseFee, chainId: extraData.ChainId); + public new static SetCodeTransactionForRpc FromTransaction(Transaction tx, in TransactionForRpcContext extraData) + => new(tx, extraData); } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/TransactionForRpc.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/TransactionForRpc.cs index 327514e932aa..f7fa2288734c 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/TransactionForRpc.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/TransactionForRpc.cs @@ -10,7 +10,6 @@ using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Int256; namespace Nethermind.Facade.Eth.RpcTransaction; @@ -43,27 +42,25 @@ public abstract class TransactionForRpc [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public long? BlockNumber { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public ulong? BlockTimestamp { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public long? Gas { get; set; } [JsonConstructor] protected TransactionForRpc() { } - protected TransactionForRpc(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null) + protected TransactionForRpc(Transaction transaction, in TransactionForRpcContext extraData) { Hash = transaction.Hash; - TransactionIndex = txIndex; - BlockHash = blockHash; - BlockNumber = blockNumber; + TransactionIndex = extraData.TxIndex; + BlockHash = extraData.BlockHash; + BlockNumber = extraData.BlockNumber; + BlockTimestamp = extraData.BlockTimestamp; } - public virtual Transaction ToTransaction() - { - return new Transaction - { - Type = Type ?? default, - }; - } + public virtual Result ToTransaction(bool validateUserInput = false) => new Transaction { Type = Type ?? default }; public abstract void EnsureDefaults(long? gasCap); public abstract bool ShouldSetBaseFee(); @@ -71,7 +68,7 @@ public virtual Transaction ToTransaction() internal class TransactionJsonConverter : JsonConverter { private static readonly List _txTypes = []; - private delegate TransactionForRpc FromTransactionFunc(Transaction tx, TransactionConverterExtraData extraData); + private delegate TransactionForRpc FromTransactionFunc(Transaction tx, in TransactionForRpcContext extraData); /// /// Transaction type is determined based on type field or type-specific fields present in the request @@ -123,27 +120,39 @@ internal static void RegisterTransactionType() where T : TransactionForRpc, I public override TransactionForRpc? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - const string TypeFieldKey = nameof(TransactionForRpc.Type); // Copy the reader so we can do a double parse: // The first parse is used to check for fields, while the second parses the entire Transaction Utf8JsonReader txTypeReader = reader; JsonObject untyped = JsonSerializer.Deserialize(ref txTypeReader, options); - TxType? txType = null; - if (untyped.TryGetPropertyValue(TypeFieldKey, out JsonNode? node)) + Type concreteTxType = DeriveTxType(untyped, options); + + return (TransactionForRpc?)JsonSerializer.Deserialize(ref reader, concreteTxType, options); + } + + private Type DeriveTxType(JsonObject untyped, JsonSerializerOptions options) + { + Type defaultTxType = typeof(EIP1559TransactionForRpc); + const string gasPriceFieldKey = nameof(LegacyTransactionForRpc.GasPrice); + const string typeFieldKey = nameof(TransactionForRpc.Type); + + if (untyped.TryGetPropertyValue(typeFieldKey, out JsonNode? node)) { - txType = node.Deserialize(options); + TxType? setType = node.Deserialize(options); + if (setType is not null) + { + return _txTypes.FirstOrDefault(p => p.TxType == setType)?.Type ?? + throw new JsonException("Unknown transaction type"); + } } - Type concreteTxType = - ( - txType is not null - ? _txTypes.FirstOrDefault(p => p.TxType == txType) - : _txTypes.FirstOrDefault(p => p.DiscriminatorProperties.Any(name => untyped.ContainsKey(name)), _txTypes[^1]) - )?.Type - ?? throw new JsonException("Unknown transaction type"); + if (untyped.ContainsKey(gasPriceFieldKey)) + { + return typeof(LegacyTransactionForRpc); + } - return (TransactionForRpc?)JsonSerializer.Deserialize(ref reader, concreteTxType, options); + return _txTypes + .FirstOrDefault(p => p.DiscriminatorProperties.Any(untyped.ContainsKey))?.Type ?? defaultTxType; } public override void Write(Utf8JsonWriter writer, TransactionForRpc value, JsonSerializerOptions options) @@ -151,7 +160,7 @@ public override void Write(Utf8JsonWriter writer, TransactionForRpc value, JsonS JsonSerializer.Serialize(writer, value, value.GetType(), options); } - public static TransactionForRpc FromTransaction(Transaction tx, TransactionConverterExtraData extraData) + public static TransactionForRpc FromTransaction(Transaction tx, in TransactionForRpcContext extraData) { return _txTypes.FirstOrDefault(t => t.TxType == tx.Type)?.FromTransactionFunc(tx, extraData) ?? throw new ArgumentException("No converter for transaction type"); @@ -166,15 +175,8 @@ class TxTypeInfo } } - public static TransactionForRpc FromTransaction(Transaction transaction, Hash256? blockHash = null, long? blockNumber = null, int? txIndex = null, UInt256? baseFee = null, ulong? chainId = null) => - TransactionJsonConverter.FromTransaction(transaction, new() - { - ChainId = chainId, - TxIndex = txIndex, - BlockHash = blockHash, - BlockNumber = blockNumber, - BaseFee = baseFee - }); + public static TransactionForRpc FromTransaction(Transaction transaction, in TransactionForRpcContext? extraData = null) => + TransactionJsonConverter.FromTransaction(transaction, extraData ?? default); public static void RegisterTransactionType() where T : TransactionForRpc, IFromTransaction, ITxTyped => TransactionJsonConverter.RegisterTransactionType(); } diff --git a/src/Nethermind/Nethermind.Facade/Filters/AddressFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/AddressFilter.cs index 4e71a417b8e5..dbbd31ae246a 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/BlockFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/BlockFilter.cs index feb09afed739..afc7e8898d51 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/BlockFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/BlockFilter.cs @@ -3,8 +3,7 @@ namespace Nethermind.Blockchain.Filters { - public class BlockFilter(int id, long startBlockNumber) : FilterBase(id) + public class BlockFilter(int id) : FilterBase(id) { - public long StartBlockNumber { get; set; } = startBlockNumber; } } diff --git a/src/Nethermind/Nethermind.Facade/Filters/FilterManager.cs b/src/Nethermind/Nethermind.Facade/Filters/FilterManager.cs index 4cdf04664915..179c8648e199 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 dc17f1ba2b51..254b54da26bf 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/FilterStore.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/FilterStore.cs @@ -4,7 +4,6 @@ using System; using NonBlocking; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading; @@ -17,7 +16,7 @@ namespace Nethermind.Blockchain.Filters { - public class FilterStore : IFilterStore + public sealed class FilterStore : IDisposable { private readonly TimeSpan _timeout; private int _currentFilterId = -1; @@ -84,6 +83,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(); @@ -107,18 +116,17 @@ public IEnumerable GetFilters() where T : FilterBase ? filter as T : null; - public BlockFilter CreateBlockFilter(long startBlockNumber, bool setId = true) => - new(GetFilterId(setId), startBlockNumber); + public BlockFilter CreateBlockFilter(bool setId = true) => + new(GetFilterId(setId)); 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 +139,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 +148,6 @@ public void SaveFilter(FilterBase filter) { _currentFilterId = Math.Max(filter.Id, _currentFilterId); } - - _filters[filter.Id] = filter; } private int GetFilterId(bool generateId) @@ -157,7 +163,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 +200,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 27a07e029b89..000000000000 --- 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 a3ed3c53979a..000000000000 --- 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(long startBlockNumber, 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/LogFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/LogFilter.cs index fc17885d64b6..e15f2cc5aaea 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/LogFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/LogFilter.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 Nethermind.Blockchain.Filters.Topics; @@ -19,6 +19,11 @@ public class LogFilter( public TopicsFilter TopicsFilter { get; } = topicsFilter; public BlockParameter FromBlock { get; } = fromBlock; public BlockParameter ToBlock { get; } = toBlock; + public bool UseIndex { get; set; } = true; + + public bool AcceptsAnyBlock => + AddressFilter.Addresses.Count == 0 && + TopicsFilter.AcceptsAnyBlock; public bool Accepts(LogEntry logEntry) => AddressFilter.Accepts(logEntry.Address) && TopicsFilter.Accepts(logEntry); diff --git a/src/Nethermind/Nethermind.Facade/Filters/LogIndexFilterVisitor.cs b/src/Nethermind/Nethermind.Facade/Filters/LogIndexFilterVisitor.cs new file mode 100644 index 000000000000..824c21538065 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Filters/LogIndexFilterVisitor.cs @@ -0,0 +1,187 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using Nethermind.Blockchain.Filters; +using Nethermind.Blockchain.Filters.Topics; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Db.LogIndex; + +[assembly: InternalsVisibleTo("Nethermind.Blockchain.Test")] + +namespace Nethermind.Facade.Filters; + +/// +/// Converts tree and block range into an enumerator of block numbers from , +/// by building corresponding "tree of enumerators". +/// +public class LogIndexFilterVisitor(ILogIndexStorage storage, LogFilter filter, int fromBlock, int toBlock) : IEnumerable +{ + internal sealed class IntersectEnumerator(IEnumerator e1, IEnumerator e2) : IEnumerator + { + public bool MoveNext() + { + bool has1 = e1.MoveNext(); + bool has2 = e2.MoveNext(); + + while (has1 && has2) + { + int c1 = e1.Current; + int c2 = e2.Current; + if (c1 == c2) + { + Current = c1; + return true; + } + + if (c1 < c2) has1 = e1.MoveNext(); + else has2 = e2.MoveNext(); + } + + return false; + } + + public void Reset() + { + e1.Reset(); + e2.Reset(); + } + + public int Current { get; private set; } + + object? IEnumerator.Current => Current; + + public void Dispose() + { + e1.Dispose(); + e2.Dispose(); + } + } + + internal sealed class UnionEnumerator(IEnumerator e1, IEnumerator e2) : IEnumerator + { + private bool _has1 = e1.MoveNext(); + private bool _has2 = e2.MoveNext(); + + public bool MoveNext() => + (_has1, _has2) switch + { + (true, true) => e1.Current.CompareTo(e2.Current) switch + { + 0 => MoveNext(e1, out _has1) && MoveNext(e2, out _has2), + < 0 => MoveNext(e1, out _has1), + > 0 => MoveNext(e2, out _has2) + }, + (true, false) => MoveNext(e1, out _has1), + (false, true) => MoveNext(e2, out _has2), + (false, false) => false + }; + + private bool MoveNext(IEnumerator enumerator, out bool has) + { + Current = enumerator.Current; + has = enumerator.MoveNext(); + return true; + } + + public void Reset() + { + e1.Reset(); + e2.Reset(); + _has1 = e1.MoveNext(); + _has2 = e2.MoveNext(); + } + + public int Current { get; private set; } + + object? IEnumerator.Current => Current; + + public void Dispose() + { + e1.Dispose(); + e2.Dispose(); + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public IEnumerator GetEnumerator() + { + IEnumerator? addressEnumerator = Visit(filter.AddressFilter); + IEnumerator? topicEnumerator = Visit(filter.TopicsFilter); + + if (addressEnumerator is not null && topicEnumerator is not null) + return new IntersectEnumerator(addressEnumerator, topicEnumerator); + + return addressEnumerator ?? topicEnumerator ?? throw new InvalidOperationException("Provided filter covers whole block range."); + } + + private IEnumerator? Visit(AddressFilter addressFilter) + { + IEnumerator? result = null; + + foreach (AddressAsKey address in addressFilter.Addresses) + { + IEnumerator next = Visit(address); + result = result is null ? next : new UnionEnumerator(result, next); + } + + return result; + } + + private IEnumerator? Visit(TopicsFilter topicsFilter) + { + IEnumerator result = null; + + var topicIndex = 0; + foreach (TopicExpression expression in topicsFilter.Expressions) + { + if (Visit(topicIndex++, expression) is not { } next) + continue; + + result = result is null ? next : new IntersectEnumerator(result, next); + } + + return result; + } + + private IEnumerator? Visit(int topicIndex, TopicExpression expression) => expression switch + { + AnyTopic => null, + OrExpression orExpression => Visit(topicIndex, orExpression), + SpecificTopic specificTopic => Visit(topicIndex, specificTopic.Topic), + _ => throw new ArgumentOutOfRangeException($"Unknown topic expression type: {expression.GetType().Name}.") + }; + + private IEnumerator? Visit(int topicIndex, OrExpression orExpression) + { + IEnumerator? result = null; + + foreach (TopicExpression expression in orExpression.SubExpressions) + { + if (Visit(topicIndex, expression) is not { } next) + continue; + + result = result is null ? next : new UnionEnumerator(result, next); + } + + return result; + } + + private IEnumerator Visit(Address address) => + storage.GetEnumerator(address, fromBlock, toBlock); + + private IEnumerator Visit(int topicIndex, Hash256 topic) => + storage.GetEnumerator(topicIndex, topic, fromBlock, toBlock); +} + +public static class LogIndexFilterVisitorExtensions +{ + public static IEnumerable EnumerateBlockNumbersFor(this ILogIndexStorage storage, LogFilter filter, long fromBlock, long toBlock) => + new LogIndexFilterVisitor(storage, filter, (int)fromBlock, (int)toBlock).Select(static i => (long)i); +} diff --git a/src/Nethermind/Nethermind.Facade/Filters/NullFilterManager.cs b/src/Nethermind/Nethermind.Facade/Filters/NullFilterManager.cs deleted file mode 100644 index 6fc634af8d23..000000000000 --- 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 b35a1e5f0ad1..000000000000 --- 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(long startBlockNumber, 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/AnyTopic.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopic.cs index f8278d7ff632..bce87e8886f3 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopic.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopic.cs @@ -10,6 +10,8 @@ public class AnyTopic : TopicExpression { public static readonly AnyTopic Instance = new(); + public override bool AcceptsAnyBlock => true; + private AnyTopic() { } public override bool Accepts(Hash256 topic) => true; diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs index 60ce1106828b..a1b8c1bed972 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs @@ -2,31 +2,28 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; +using System.Linq; using Nethermind.Blockchain.Receipts; using Nethermind.Core; using Nethermind.Core.Crypto; 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 IEnumerable Expressions => expressions; + public override bool AcceptsAnyBlock => expressions.Length == 0 || expressions.Any(static e => e.AcceptsAnyBlock); 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 +42,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 +64,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 +80,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/OrExpression.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/OrExpression.cs index 5b8e3a4fb29d..406e4ee48671 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/OrExpression.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/OrExpression.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; using System.Linq; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -12,6 +13,9 @@ public class OrExpression : TopicExpression, IEquatable { private readonly TopicExpression[] _subexpressions; + public IEnumerable SubExpressions => _subexpressions; + public override bool AcceptsAnyBlock => _subexpressions.Any(static e => e.AcceptsAnyBlock); + public OrExpression(params TopicExpression[] subexpressions) { _subexpressions = subexpressions; diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs index 7386afe49ad4..1e490caa472a 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; using System.Linq; using Nethermind.Blockchain.Receipts; using Nethermind.Core; @@ -9,16 +10,15 @@ 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; + private readonly TopicExpression[] _expressions = expressions; - public SequenceTopicsFilter(params TopicExpression[] expressions) - { - _expressions = expressions; - } + public override IEnumerable Expressions => _expressions; + public override bool AcceptsAnyBlock => _expressions.Length == 0 || _expressions.All(static e => e.AcceptsAnyBlock); public override bool Accepts(LogEntry entry) => Accepts(entry.Topics); diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/SpecificTopic.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/SpecificTopic.cs index a9dee55bc8c1..91aa003889c4 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/SpecificTopic.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/SpecificTopic.cs @@ -11,6 +11,9 @@ public class SpecificTopic : TopicExpression private readonly Hash256 _topic; private Bloom.BloomExtract _bloomExtract; + public Hash256 Topic => _topic; + public override bool AcceptsAnyBlock => false; + public SpecificTopic(Hash256 topic) { _topic = topic; diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicExpression.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicExpression.cs index 5193e0bfba8b..f344435bc680 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicExpression.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicExpression.cs @@ -8,6 +8,8 @@ namespace Nethermind.Blockchain.Filters.Topics { public abstract class TopicExpression { + public abstract bool AcceptsAnyBlock { get; } + public abstract bool Accepts(Hash256 topic); public abstract bool Accepts(ref Hash256StructRef topic); diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicsFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicsFilter.cs index 0cdc7ece380f..17b15c591c53 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicsFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicsFilter.cs @@ -1,12 +1,16 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Collections.Generic; using Nethermind.Core; namespace Nethermind.Blockchain.Filters.Topics { public abstract class TopicsFilter { + public abstract IEnumerable Expressions { get; } + public abstract bool AcceptsAnyBlock { get; } + public abstract bool Accepts(LogEntry entry); public abstract bool Accepts(ref LogEntryStructRef entry); diff --git a/src/Nethermind/Nethermind.Facade/Find/ILogIndexBuilder.cs b/src/Nethermind/Nethermind.Facade/Find/ILogIndexBuilder.cs new file mode 100644 index 000000000000..3f898f360640 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Find/ILogIndexBuilder.cs @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading.Tasks; +using Nethermind.Core.ServiceStopper; + +namespace Nethermind.Facade.Find +{ + public interface ILogIndexBuilder : IAsyncDisposable, IStoppableService + { + Task StartAsync(); + bool IsRunning { get; } + + int MaxTargetBlockNumber { get; } + int MinTargetBlockNumber { get; } + + DateTimeOffset? LastUpdate { get; } + Exception? LastError { get; } + } +} diff --git a/src/Nethermind/Nethermind.Facade/Find/IndexedLogFinder.cs b/src/Nethermind/Nethermind.Facade/Find/IndexedLogFinder.cs new file mode 100644 index 000000000000..2c9827d6c607 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Find/IndexedLogFinder.cs @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading; +using Nethermind.Blockchain.Filters; +using Nethermind.Blockchain.Find; +using Nethermind.Blockchain.Receipts; +using Nethermind.Core; +using Nethermind.Db.Blooms; +using Nethermind.Db.LogIndex; +using Nethermind.Facade.Filters; +using Nethermind.Logging; + +namespace Nethermind.Facade.Find; + +/// +/// Extended that adds log index support for faster eth_getLogs queries. +/// When the log index is available and applicable, it uses the index to identify relevant blocks +/// before fetching logs from those specific blocks. +/// +public class IndexedLogFinder( + IBlockFinder blockFinder, + IReceiptFinder receiptFinder, + IReceiptStorage receiptStorage, + IBloomStorage bloomStorage, + ILogManager logManager, + IReceiptsRecovery receiptsRecovery, + ILogIndexStorage logIndexStorage, + int maxBlockDepth = 1000, + int minBlocksToUseIndex = 32) + : LogFinder(blockFinder, receiptFinder, receiptStorage, bloomStorage, logManager, receiptsRecovery, maxBlockDepth) +{ + private readonly ILogIndexStorage _logIndexStorage = logIndexStorage ?? throw new ArgumentNullException(nameof(logIndexStorage)); + + public override IEnumerable FindLogs(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken = default) => + GetLogIndexRange(filter, fromBlock, toBlock) is not { } indexRange + ? base.FindLogs(filter, fromBlock, toBlock, cancellationToken) + : FindIndexedLogs(filter, fromBlock, toBlock, indexRange, cancellationToken); + + private IEnumerable FindIndexedLogs(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, (int from, int to) indexRange, CancellationToken cancellationToken) + { + if (indexRange.from > fromBlock.Number && FindHeaderOrLogError(indexRange.from - 1, cancellationToken) is { } beforeIndex) + { + foreach (FilterLog log in base.FindLogs(filter, fromBlock, beforeIndex, cancellationToken)) + yield return log; + } + + cancellationToken.ThrowIfCancellationRequested(); + + IEnumerable indexNumbers = _logIndexStorage.EnumerateBlockNumbersFor(filter, indexRange.from, indexRange.to); + foreach (FilterLog log in FilterLogsInBlocksParallel(filter, indexNumbers, cancellationToken)) + { + yield return log; + } + + cancellationToken.ThrowIfCancellationRequested(); + + if (indexRange.to < toBlock.Number && FindHeaderOrLogError(indexRange.to + 1, cancellationToken) is { } afterIndex) + { + foreach (FilterLog log in base.FindLogs(filter, afterIndex, toBlock, cancellationToken)) + yield return log; + } + } + + private (int from, int to)? GetLogIndexRange(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock) + { + bool tryUseIndex = filter.UseIndex; + filter.UseIndex = false; + + if (!tryUseIndex || !_logIndexStorage.Enabled || filter.AcceptsAnyBlock) + return null; + + if (_logIndexStorage.MinBlockNumber is not { } indexFrom || _logIndexStorage.MaxBlockNumber is not { } indexTo) + return null; + + (int from, int to) range = ( + Math.Max((int)fromBlock.Number, indexFrom), + Math.Min((int)toBlock.Number, indexTo) + ); + + if (range.from > range.to) + return null; + + if (range.to - range.from + 1 < minBlocksToUseIndex) + return null; + + filter.UseIndex = true; + return range; + } +} diff --git a/src/Nethermind/Nethermind.Facade/Find/LogFinder.cs b/src/Nethermind/Nethermind.Facade/Find/LogFinder.cs index b1eddee6df12..d1b4d5636152 100644 --- a/src/Nethermind/Nethermind.Facade/Find/LogFinder.cs +++ b/src/Nethermind/Nethermind.Facade/Find/LogFinder.cs @@ -18,37 +18,26 @@ namespace Nethermind.Facade.Find { - public class LogFinder : ILogFinder + public class LogFinder( + IBlockFinder? blockFinder, + IReceiptFinder? receiptFinder, + IReceiptStorage? receiptStorage, + IBloomStorage? bloomStorage, + ILogManager? logManager, + IReceiptsRecovery? receiptsRecovery, + int maxBlockDepth = 1000) + : ILogFinder { private static int ParallelExecutions = 0; private static int ParallelLock = 0; - private readonly IReceiptFinder _receiptFinder; - private readonly IReceiptStorage _receiptStorage; - private readonly IBloomStorage _bloomStorage; - private readonly IReceiptsRecovery _receiptsRecovery; - private readonly int _maxBlockDepth; - private readonly int _rpcConfigGetLogsThreads; - private readonly IBlockFinder _blockFinder; - private readonly ILogger _logger; - - public LogFinder(IBlockFinder? blockFinder, - IReceiptFinder? receiptFinder, - IReceiptStorage? receiptStorage, - IBloomStorage? bloomStorage, - ILogManager? logManager, - IReceiptsRecovery? receiptsRecovery, - int maxBlockDepth = 1000) - { - _blockFinder = blockFinder ?? throw new ArgumentNullException(nameof(blockFinder)); - _receiptFinder = receiptFinder ?? throw new ArgumentNullException(nameof(receiptFinder)); - _receiptStorage = receiptStorage ?? throw new ArgumentNullException(nameof(receiptStorage)); ; - _bloomStorage = bloomStorage ?? throw new ArgumentNullException(nameof(bloomStorage)); - _receiptsRecovery = receiptsRecovery ?? throw new ArgumentNullException(nameof(receiptsRecovery)); - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - _maxBlockDepth = maxBlockDepth; - _rpcConfigGetLogsThreads = Math.Max(1, Environment.ProcessorCount / 4); - } + private readonly IReceiptFinder _receiptFinder = receiptFinder ?? throw new ArgumentNullException(nameof(receiptFinder)); + private readonly IReceiptStorage _receiptStorage = receiptStorage ?? throw new ArgumentNullException(nameof(receiptStorage)); + private readonly IBloomStorage _bloomStorage = bloomStorage ?? throw new ArgumentNullException(nameof(bloomStorage)); + private readonly IReceiptsRecovery _receiptsRecovery = receiptsRecovery ?? throw new ArgumentNullException(nameof(receiptsRecovery)); + private readonly int _rpcConfigGetLogsThreads = Math.Max(1, Environment.ProcessorCount / 4); + private readonly IBlockFinder _blockFinder = blockFinder ?? throw new ArgumentNullException(nameof(blockFinder)); + private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); public IEnumerable FindLogs(LogFilter filter, CancellationToken cancellationToken = default) { @@ -65,7 +54,7 @@ BlockHeader FindHeader(BlockParameter blockParameter, string name, bool headLimi return FindLogs(filter, fromBlock, toBlock, cancellationToken); } - public IEnumerable FindLogs(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken = default) + public virtual IEnumerable FindLogs(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -97,36 +86,37 @@ public IEnumerable FindLogs(LogFilter filter, BlockHeader fromBlock, private static bool ShouldUseBloomDatabase(BlockHeader fromBlock, BlockHeader toBlock) { - var blocksToSearch = toBlock.Number - fromBlock.Number + 1; + long blocksToSearch = toBlock.Number - fromBlock.Number + 1; return blocksToSearch > 1; // if we are searching only in 1 block skip bloom index altogether, this can be tweaked } private IEnumerable FilterLogsWithBloomsIndex(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken) { - BlockHeader FindBlockHeader(long blockNumber, CancellationToken token) + IEnumerable EnumerateBlockNumbers(LogFilter f, long from, long to) { - token.ThrowIfCancellationRequested(); - var block = _blockFinder.FindHeader(blockNumber); - if (block is null) + IBloomEnumeration enumeration = _bloomStorage.GetBlooms(from, to); + foreach (Bloom bloom in enumeration) { - if (_logger.IsError) _logger.Error($"Could not find block {blockNumber} in database. eth_getLogs will return incomplete results."); + if (f.Matches(bloom) && enumeration.TryGetBlockNumber(out var blockNumber)) + { + yield return blockNumber; + } } - - return block; } - IEnumerable FilterBlocks(LogFilter f, long @from, long to, bool runParallel, CancellationToken token) + return FilterLogsInBlocksParallel(filter, EnumerateBlockNumbers(filter, fromBlock.Number, toBlock.Number), cancellationToken); + } + + protected IEnumerable FilterLogsInBlocksParallel(LogFilter filter, IEnumerable blockNumbers, CancellationToken cancellationToken) + { + static IEnumerable ParallelizeWithLock(IEnumerable blocks, bool runParallel, CancellationToken ct) { try { - var enumeration = _bloomStorage.GetBlooms(from, to); - foreach (var bloom in enumeration) + foreach (long blockNumber in blocks) { - token.ThrowIfCancellationRequested(); - if (f.Matches(bloom) && enumeration.TryGetBlockNumber(out var blockNumber)) - { - yield return blockNumber; - } + yield return blockNumber; + ct.ThrowIfCancellationRequested(); } } finally @@ -145,7 +135,7 @@ IEnumerable FilterBlocks(LogFilter f, long @from, long to, bool runParalle int parallelExecutions = Interlocked.Increment(ref ParallelExecutions) - 1; bool canRunParallel = parallelLock == 0; - IEnumerable filterBlocks = FilterBlocks(filter, fromBlock.Number, toBlock.Number, canRunParallel, cancellationToken); + IEnumerable filterBlocks = ParallelizeWithLock(blockNumbers, canRunParallel, cancellationToken); if (canRunParallel) { @@ -160,7 +150,7 @@ IEnumerable FilterBlocks(LogFilter f, long @from, long to, bool runParalle } return filterBlocks - .SelectMany(blockNumber => FindLogsInBlock(filter, FindBlockHeader(blockNumber, cancellationToken), cancellationToken)); + .SelectMany(blockNumber => FindLogsInBlock(filter, FindHeaderOrLogError(blockNumber, cancellationToken), cancellationToken)); } private bool CanUseBloomDatabase(BlockHeader toBlock, BlockHeader fromBlock) @@ -191,7 +181,7 @@ private bool CanUseBloomDatabase(BlockHeader toBlock, BlockHeader fromBlock) private IEnumerable FilterLogsIteratively(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken) { int count = 0; - while (count < _maxBlockDepth && fromBlock.Number <= (toBlock?.Number ?? fromBlock.Number)) + while (count < maxBlockDepth && fromBlock.Number <= (toBlock?.Number ?? fromBlock.Number)) { foreach (var filterLog in FindLogsInBlock(filter, fromBlock, cancellationToken)) { @@ -206,11 +196,11 @@ private IEnumerable FilterLogsIteratively(LogFilter filter, BlockHead } private IEnumerable FindLogsInBlock(LogFilter filter, BlockHeader block, CancellationToken cancellationToken) => - filter.Matches(block.Bloom) + filter.Matches(block.Bloom!) ? FindLogsInBlock(filter, block.Hash, block.Number, block.Timestamp, cancellationToken) : []; - private IEnumerable FindLogsInBlock(LogFilter filter, Hash256 blockHash, long blockNumber, ulong blockTimestamp, CancellationToken cancellationToken) + private IEnumerable FindLogsInBlock(LogFilter filter, Hash256? blockHash, long blockNumber, ulong blockTimestamp, CancellationToken cancellationToken) { if (blockHash is not null) { @@ -348,5 +338,18 @@ void RecoverReceiptsData(Hash256 hash, TxReceipt[] receipts) } } } + + protected BlockHeader? FindHeaderOrLogError(long blockNumber, CancellationToken token) + { + token.ThrowIfCancellationRequested(); + + BlockHeader? block = _blockFinder.FindHeader(blockNumber); + if (block is null && _logger.IsError) + { + _logger.Error($"Could not find block {blockNumber} in database. eth_getLogs will return incomplete results."); + } + + return block; + } } } diff --git a/src/Nethermind/Nethermind.Facade/Find/LogIndexBuilder.cs b/src/Nethermind/Nethermind.Facade/Find/LogIndexBuilder.cs new file mode 100644 index 000000000000..3112b36545f6 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Find/LogIndexBuilder.cs @@ -0,0 +1,495 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using System.Threading.Tasks.Dataflow; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Receipts; +using Nethermind.Blockchain.Synchronization; +using Nethermind.Core; +using Nethermind.Db.LogIndex; +using Nethermind.Logging; +using Timer = System.Timers.Timer; +using static System.Threading.Tasks.TaskCreationOptions; + +namespace Nethermind.Facade.Find; + +// TODO: reduce periodic logging +public sealed class LogIndexBuilder : ILogIndexBuilder +{ + private sealed class ProcessingQueue( + TransformBlock, LogIndexAggregate> aggregateBlock, + ActionBlock addReceiptsBlock) + { + public int QueueCount => aggregateBlock.InputCount + addReceiptsBlock.InputCount; + public Task WriteAsync(IReadOnlyList batch, CancellationToken cancellation) => aggregateBlock.SendAsync(batch, cancellation); + public Task Completion => Task.WhenAll(aggregateBlock.Completion, addReceiptsBlock.Completion); + } + + private struct DirectionState() + { + public ProcessingQueue? Queue; + public ProgressLogger? Progress; + public readonly TaskCompletionSource Completion = new(RunContinuationsAsynchronously); + } + + [InlineArray(2)] + private struct DirectionStates + { + private DirectionState _element; + } + + private readonly IBlockTree _blockTree; + private readonly ISyncConfig _syncConfig; + private readonly ILogger _logger; + + private readonly CancellationTokenSource _cancellationSource = new(); + private CancellationToken CancellationToken => _cancellationSource.Token; + + private int MaxReorgDepth => _config.MaxReorgDepth!.Value; + private static readonly TimeSpan NewBlockWaitTimeout = TimeSpan.FromSeconds(5); + + private readonly ILogIndexStorage _logIndexStorage; + private readonly ILogIndexConfig _config; + private readonly IReceiptStorage _receiptStorage; + private readonly ILogManager _logManager; + private Timer? _progressLoggerTimer; + + private readonly TaskCompletionSource _pivotSource = new(RunContinuationsAsynchronously); + private readonly Task _pivotTask; + + private readonly List _tasks = new(); + + private DirectionStates _directions; + + private ref DirectionState Direction(bool isForward) => ref _directions[isForward ? 1 : 0]; + + private LogIndexUpdateStats _stats; + + public string Description => "log index builder"; + + public Task BackwardSyncCompletion => Direction(isForward: false).Completion.Task; + + public LogIndexBuilder(ILogIndexStorage logIndexStorage, ILogIndexConfig config, + IBlockTree blockTree, ISyncConfig syncConfig, IReceiptStorage receiptStorage, + ILogManager logManager) + { + ArgumentNullException.ThrowIfNull(logIndexStorage); + ArgumentNullException.ThrowIfNull(blockTree); + ArgumentNullException.ThrowIfNull(receiptStorage); + ArgumentNullException.ThrowIfNull(logManager); + ArgumentNullException.ThrowIfNull(syncConfig); + + _config = config; + _logIndexStorage = logIndexStorage; + _blockTree = blockTree; + _syncConfig = syncConfig; + _receiptStorage = receiptStorage; + _logManager = logManager; + _logger = logManager.GetClassLogger(); + _pivotTask = _pivotSource.Task; + _stats = new(_logIndexStorage); + + Direction(isForward: false) = new(); + Direction(isForward: true) = new(); + } + + private void StartProcessing(bool isForward) + { + // Do not start backward sync if the target is already reached + if (!isForward && _logIndexStorage.MinBlockNumber <= MinTargetBlockNumber) + { + MarkCompleted(false); + return; + } + + ref DirectionState dir = ref Direction(isForward); + dir.Queue = BuildQueue(isForward); + dir.Progress = new(GetLogPrefix(isForward), _logManager); + + _tasks.AddRange( + Task.Run(() => DoQueueBlocks(isForward), CancellationToken), + dir.Queue.Completion + ); + } + + public async Task StartAsync() + { + try + { + if (!_config.Enabled) + return; + + _receiptStorage.ReceiptsInserted += OnReceiptsInserted; + + TrySetPivot(_logIndexStorage.MaxBlockNumber); + TrySetPivot((int)_blockTree.SyncPivot.BlockNumber); + + if (!_pivotTask.IsCompleted && _logger.IsInfo) + _logger.Info($"{GetLogPrefix()}: waiting for the first block..."); + + await _pivotTask; + + StartProcessing(isForward: true); + StartProcessing(isForward: false); + + UpdateProgress(); + LogProgress(); + + _progressLoggerTimer = new(TimeSpan.FromSeconds(30)); + _progressLoggerTimer.AutoReset = true; + _progressLoggerTimer.Elapsed += (_, _) => LogProgress(); + _progressLoggerTimer.Start(); + + IsRunning = true; + } + catch (Exception ex) + { + await HandleExceptionAsync(ex); + } + } + + public async Task StopAsync() + { + if (!_config.Enabled) + return; + + await _cancellationSource.CancelAsync(); + + _pivotSource.TrySetCanceled(CancellationToken); + _progressLoggerTimer?.Stop(); + + foreach (Task task in _tasks) + { + try + { + await task; + } + catch (Exception ex) + { + await HandleExceptionAsync(ex, isStopping: true); + } + } + + await _logIndexStorage.StopAsync(); + + IsRunning = false; + } + + public async ValueTask DisposeAsync() + { + await _logIndexStorage.DisposeAsync(); + _progressLoggerTimer?.Dispose(); + _cancellationSource.Dispose(); + } + + private void LogStats() + { + LogIndexUpdateStats stats = _stats; + + if (stats is not { BlocksAdded: > 0 }) + return; + + _stats = new(_logIndexStorage); + + if (_logger.IsInfo) + { + _logger.Info(_config.DetailedLogs + ? $"{GetLogPrefix()}: {stats:d}" + : $"{GetLogPrefix()}: {stats}" + ); + } + } + + private void LogProgress() + { + LogStats(); + Direction(isForward: false).Progress?.LogProgress(); + Direction(isForward: true).Progress?.LogProgress(); + } + + private bool TrySetPivot(int? blockNumber) + { + if (blockNumber is not { } number || number is 0) + return false; + + if (_pivotSource.Task.IsCompleted) + return false; + + number = Math.Max(MinTargetBlockNumber, number); + number = Math.Min(MaxTargetBlockNumber, number); + + if (number is 0) + return false; + + if (!TryGetBlockReceipts(number, out _)) + return false; + + if (!_pivotSource.TrySetResult(number)) + return false; + + _logger.Info($"{GetLogPrefix()}: using block {number} as pivot."); + return true; + } + + private void OnReceiptsInserted(object? sender, ReceiptsEventArgs args) + { + int next = (int)args.BlockHeader.Number; + if (TrySetPivot(next)) + _receiptStorage.ReceiptsInserted -= OnReceiptsInserted; + } + + public int MaxTargetBlockNumber => (int)Math.Max(_blockTree.BestKnownNumber - MaxReorgDepth, 0); + + // Block 0 should always be present + public int MinTargetBlockNumber => (int)(_syncConfig.AncientReceiptsBarrierCalc <= 1 ? 0 : _syncConfig.AncientReceiptsBarrierCalc); + + public bool IsRunning { get; private set; } + public DateTimeOffset? LastUpdate { get; private set; } + public Exception? LastError { get; private set; } + + private ProcessingQueue BuildQueue(bool isForward) + { + var aggregateBlock = new TransformBlock, LogIndexAggregate>( + batch => Aggregate(batch, isForward), + new() + { + BoundedCapacity = _config.MaxAggregationQueueSize, + MaxDegreeOfParallelism = _config.MaxAggregationParallelism, + CancellationToken = CancellationToken, + SingleProducerConstrained = true + } + ); + + var addReceiptsBlock = new ActionBlock( + aggr => AddReceiptsAsync(aggr, isForward), + new() + { + BoundedCapacity = _config.MaxSavingQueueSize, + MaxDegreeOfParallelism = 1, + CancellationToken = CancellationToken, + SingleProducerConstrained = true + } + ); + + aggregateBlock.Completion.ContinueWith(t => HandleExceptionAsync(t.Exception), TaskContinuationOptions.OnlyOnFaulted); + addReceiptsBlock.Completion.ContinueWith(t => HandleExceptionAsync(t.Exception), TaskContinuationOptions.OnlyOnFaulted); + + aggregateBlock.LinkTo(addReceiptsBlock, new() { PropagateCompletion = true }); + return new(aggregateBlock, addReceiptsBlock); + } + + private async Task HandleExceptionAsync(Exception? exception, bool isStopping = false) + { + if (exception is null) + return; + + if (exception is AggregateException a) + exception = a.InnerException; + + if (exception is OperationCanceledException oc && oc.CancellationToken == CancellationToken) + return; // Cancelled + + if (_logger.IsError) + _logger.Error($"{GetLogPrefix()} failed. Please restart the client.", exception); + + LastError = exception; + + Direction(isForward: false).Completion.TrySetException(exception!); + Direction(isForward: true).Completion.TrySetException(exception!); + + if (!isStopping) + await StopAsync(); + } + + private LogIndexAggregate Aggregate(IReadOnlyList batch, bool isForward) => _logIndexStorage.Aggregate(batch, !isForward, _stats); + + private async Task AddReceiptsAsync(LogIndexAggregate aggregate, bool isForward) + { + if (GetNextBlockNumber(_logIndexStorage, isForward) is { } next && next != aggregate.FirstBlockNum) + throw new($"{GetLogPrefix(isForward)}: non sequential batches: ({aggregate.FirstBlockNum} instead of {next})."); + + await _logIndexStorage.AddReceiptsAsync(aggregate, _stats); + LastUpdate = DateTimeOffset.Now; + + UpdateProgress(); + + if (_logIndexStorage.MinBlockNumber <= MinTargetBlockNumber) + MarkCompleted(false); + } + + private async Task DoQueueBlocks(bool isForward) + { + try + { + int pivotNumber = await _pivotTask; + + ProcessingQueue queue = Direction(isForward).Queue!; + + int? next = GetNextBlockNumber(isForward); + if (next is not { } start) + { + if (isForward) + { + start = pivotNumber; + } + else + { + start = pivotNumber - 1; + } + } + + BlockReceipts[] buffer = new BlockReceipts[_config.MaxBatchSize]; + while (!CancellationToken.IsCancellationRequested) + { + if (!isForward && start < MinTargetBlockNumber) + { + if (_logger.IsTrace) + _logger.Trace($"{GetLogPrefix(isForward)}: queued last block"); + + return; + } + + int batchSize = _config.MaxBatchSize; + int end = isForward ? start + batchSize - 1 : start - batchSize + 1; + end = Math.Max(end, MinTargetBlockNumber); + end = Math.Min(end, MaxTargetBlockNumber); + + // from - inclusive, to - exclusive + var (from, to) = isForward + ? (start, end + 1) + : (end, start + 1); + + var timestamp = Stopwatch.GetTimestamp(); + Array.Clear(buffer); + ReadOnlySpan batch = GetNextBatch(from, to, buffer, isForward, CancellationToken); + + if (batch.Length == 0) + { + // TODO: stop waiting immediately when receipts become available + await Task.Delay(NewBlockWaitTimeout, CancellationToken); + continue; + } + + _stats.LoadingReceipts.Include(Stopwatch.GetElapsedTime(timestamp)); + + start = GetNextBlockNumber(batch[^1].BlockNumber, isForward); + await queue.WriteAsync(batch.ToArray(), CancellationToken); + } + } + catch (Exception ex) + { + await HandleExceptionAsync(ex); + } + + if (_logger.IsTrace) + _logger.Trace($"{GetLogPrefix(isForward)}: queueing completed."); + } + + private void UpdateProgress() + { + if (!_pivotTask.IsCompletedSuccessfully) return; + var pivotNumber = _pivotTask.Result; + + DirectionState forward = Direction(isForward: true); + if (forward.Progress is { HasEnded: false } forwardProgress) + { + forwardProgress.TargetValue = Math.Max(0, _blockTree.BestKnownNumber - MaxReorgDepth - pivotNumber + 1); + forwardProgress.Update(_logIndexStorage.MaxBlockNumber is { } max ? max - pivotNumber + 1 : 0); + forwardProgress.CurrentQueued = forward.Queue!.QueueCount; + } + + DirectionState backward = Direction(isForward: false); + if (backward.Progress is { HasEnded: false } backwardProgress) + { + backwardProgress.TargetValue = pivotNumber - MinTargetBlockNumber; + backwardProgress.Update(_logIndexStorage.MinBlockNumber is { } min ? pivotNumber - min : 0); + backwardProgress.CurrentQueued = backward.Queue!.QueueCount; + } + } + + private void MarkCompleted(bool isForward) + { + DirectionState dir = Direction(isForward); + if (!dir.Completion.TrySetResult()) + return; + + dir.Progress?.MarkEnd(); + + if (_logger.IsInfo) + _logger.Info($"{GetLogPrefix(isForward)}: completed."); + } + + private static int? GetNextBlockNumber(ILogIndexStorage storage, bool isForward) => isForward ? storage.MaxBlockNumber + 1 : storage.MinBlockNumber - 1; + + private int? GetNextBlockNumber(bool isForward) => GetNextBlockNumber(_logIndexStorage, isForward); + + private static int GetNextBlockNumber(int last, bool isForward) => isForward ? last + 1 : last - 1; + + private ReadOnlySpan GetNextBatch(int from, int to, BlockReceipts[] buffer, bool isForward, CancellationToken token) + { + if (to <= from) + return ReadOnlySpan.Empty; + + if (to - from > buffer.Length) + throw new InvalidOperationException($"{GetLogPrefix()}: buffer size is too small: {buffer.Length} / {to - from}"); + + // Check the immediate next block first + int nextIndex = isForward ? from : to - 1; + if (!TryGetBlockReceipts(nextIndex, out buffer[0])) + return ReadOnlySpan.Empty; + + Parallel.For(from, to, new() + { + CancellationToken = token, + MaxDegreeOfParallelism = _config.MaxReceiptsParallelism + }, i => + { + int bufferIndex = isForward ? i - from : to - 1 - i; + if (buffer[bufferIndex] == default) + TryGetBlockReceipts(i, out buffer[bufferIndex]); + }); + + int endIndex = Array.IndexOf(buffer, default); + return endIndex < 0 ? buffer : buffer.AsSpan(..endIndex); + } + + // TODO: move to IReceiptStorage? + private bool TryGetBlockReceipts(int i, out BlockReceipts blockReceipts) + { + blockReceipts = default; + + if (_blockTree.FindBlock(i, BlockTreeLookupOptions.ExcludeTxHashes) is not { Hash: not null } block) + { + return false; + } + + if (!block.Header.HasTransactions) + { + blockReceipts = new(i, []); + return true; + } + + TxReceipt[] receipts = _receiptStorage.Get(block) ?? []; + + if (receipts.Length == 0) + { + return false; // block should have transactions but nothing in storage + } + + blockReceipts = new(i, receipts); + return true; + } + + private static string GetLogPrefix(bool? isForward = null) => isForward switch + { + true => "Log index sync (Forward)", + false => "Log index sync (Backward)", + _ => "Log index sync" + }; +} diff --git a/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs b/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs index d2f41be173a9..d384a1b4aaa8 100644 --- a/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs +++ b/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Collections.Generic; -using System.Threading; using Nethermind.Blockchain.Filters; using Nethermind.Blockchain.Find; using Nethermind.Core; @@ -10,10 +8,12 @@ using Nethermind.Evm; using Nethermind.Facade.Filters; using Nethermind.Facade.Find; -using Nethermind.Facade.Simulate; using Nethermind.Facade.Proxy.Models.Simulate; -using Nethermind.Int256; +using Nethermind.Facade.Simulate; using Nethermind.Trie; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; using Block = Nethermind.Core.Block; namespace Nethermind.Facade @@ -26,7 +26,7 @@ public interface IBlockchainBridge : ILogFinder Address? RecoverTxSender(Transaction tx); TxReceipt GetReceipt(Hash256 txHash); (TxReceipt? Receipt, ulong BlockTimestamp, TxGasInfo? GasInfo, int LogIndexStart) GetTxReceiptInfo(Hash256 txHash); - (TxReceipt? Receipt, Transaction? Transaction, UInt256? baseFee) GetTransaction(Hash256 txHash, bool checkTxnPool = true); + bool TryGetTransaction(Hash256 txHash, [NotNullWhen(true)] out TransactionLookupResult? result, bool checkTxnPool = true); CallOutput Call(BlockHeader header, Transaction tx, Dictionary? stateOverride = null, CancellationToken cancellationToken = default); SimulateOutput Simulate(BlockHeader header, SimulatePayload payload, ISimulateBlockTracerFactory simulateBlockTracerFactory, long gasCapLimit, CancellationToken cancellationToken); CallOutput EstimateGas(BlockHeader header, Transaction tx, int errorMarginBasisPoints, Dictionary? stateOverride = null, CancellationToken cancellationToken = default); @@ -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 53765e367592..0f9e168c9f8e 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/Proxy/Models/Simulate/Error.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/Error.cs index 6819d2772e4f..83ccda5594cb 100644 --- a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/Error.cs +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/Error.cs @@ -1,11 +1,17 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Text.Json.Serialization; +using Nethermind.Evm; + namespace Nethermind.Facade.Proxy.Models.Simulate; public class Error { public int Code { get; set; } - public string Message { get; set; } - public string? Data { get; set; } + public string? Message { get; set; } + public byte[]? Data { get; set; } + + [JsonIgnore] + public EvmExceptionType EvmException { get; set; } } diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/Log.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/Log.cs index 1e0f05d12c34..6a17b28caf02 100644 --- a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/Log.cs +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/Log.cs @@ -15,6 +15,7 @@ public class Log public Hash256 TransactionHash { get; set; } public ulong TransactionIndex { get; set; } public Hash256 BlockHash { get; set; } + public ulong BlockTimestamp { get; set; } public ulong LogIndex { get; set; } public bool Removed { get; set; } = false; diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/SimulateCallResult.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/SimulateCallResult.cs index 74f2f411f2db..4e799072ebf6 100644 --- a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/SimulateCallResult.cs +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/SimulateCallResult.cs @@ -11,5 +11,5 @@ public class SimulateCallResult public byte[]? ReturnData { get; set; } public ulong? GasUsed { get; set; } public Error? Error { get; set; } - public IEnumerable Logs { get; set; } = []; + public ICollection Logs { get; set; } = []; } diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/SimulatePayload.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/SimulatePayload.cs index ac215e5a1df9..6a4522abaa39 100644 --- a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/SimulatePayload.cs +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/SimulatePayload.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Text.Json.Serialization; namespace Nethermind.Facade.Proxy.Models.Simulate; diff --git a/src/Nethermind/Nethermind.Facade/Simulate/GethStyleSimulateBlockTracerFactory.cs b/src/Nethermind/Nethermind.Facade/Simulate/GethStyleSimulateBlockTracerFactory.cs index e3b2160d5678..ac529a5c5cc2 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/GethStyleSimulateBlockTracerFactory.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/GethStyleSimulateBlockTracerFactory.cs @@ -9,6 +9,7 @@ using Nethermind.Blockchain.Tracing.GethStyle; namespace Nethermind.Facade.Simulate; + public class GethStyleSimulateBlockTracerFactory(GethTraceOptions options) : ISimulateBlockTracerFactory { public IBlockTracer CreateSimulateBlockTracer(bool isTracingLogs, IWorldState worldState, ISpecProvider spec, BlockHeader block) => diff --git a/src/Nethermind/Nethermind.Facade/Simulate/ISimulateBlockTracerFactory.cs b/src/Nethermind/Nethermind.Facade/Simulate/ISimulateBlockTracerFactory.cs index 3abcfe4d2084..58218b26cd61 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/ISimulateBlockTracerFactory.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/ISimulateBlockTracerFactory.cs @@ -7,6 +7,7 @@ using Nethermind.Evm.Tracing; namespace Nethermind.Facade.Simulate; + public interface ISimulateBlockTracerFactory { public abstract IBlockTracer CreateSimulateBlockTracer(bool isTracingLogs, IWorldState worldState, ISpecProvider spec, BlockHeader block); diff --git a/src/Nethermind/Nethermind.Facade/Simulate/ParityStyleSimulateBlockTracerFactory.cs b/src/Nethermind/Nethermind.Facade/Simulate/ParityStyleSimulateBlockTracerFactory.cs index 7c3fee3f078d..b2f94ee17626 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/ParityStyleSimulateBlockTracerFactory.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/ParityStyleSimulateBlockTracerFactory.cs @@ -8,6 +8,7 @@ using Nethermind.Blockchain.Tracing.ParityStyle; namespace Nethermind.Facade.Simulate; + public class ParityStyleSimulateBlockTracerFactory(ParityTraceTypes types) : ISimulateBlockTracerFactory { public IBlockTracer CreateSimulateBlockTracer(bool isTracingLogs, IWorldState worldState, ISpecProvider spec, BlockHeader block) => diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockMutatorTracerFactory.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockMutatorTracerFactory.cs index 9d8e677b4a5e..6bf000f81cc7 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockMutatorTracerFactory.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockMutatorTracerFactory.cs @@ -8,8 +8,9 @@ using Nethermind.Facade.Proxy.Models.Simulate; namespace Nethermind.Facade.Simulate; + public class SimulateBlockMutatorTracerFactory : ISimulateBlockTracerFactory { public IBlockTracer CreateSimulateBlockTracer(bool isTracingLogs, IWorldState worldState, ISpecProvider spec, BlockHeader block) => - new SimulateBlockMutatorTracer(isTracingLogs); + new SimulateBlockTracer(isTracingLogs); } diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockMutatorTracer.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockTracer.cs similarity index 50% rename from src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockMutatorTracer.cs rename to src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockTracer.cs index 8915db78558d..193cc117879c 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockMutatorTracer.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockTracer.cs @@ -1,39 +1,39 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Nethermind.Blockchain.Tracing; using Nethermind.Core; +using Nethermind.Core.Crypto; using Nethermind.Evm.Tracing; using Nethermind.Facade.Proxy.Models.Simulate; namespace Nethermind.Facade.Simulate; -public class SimulateBlockMutatorTracer(bool isTracingLogs) : BlockTracerBase +public class SimulateBlockTracer(bool isTracingLogs) : BlockTracerBase { private ulong _txIndex = 0; - private Block? _currentBlock; + private ulong _blockNumber; + private ulong _blockTimestamp; - protected override SimulateTxMutatorTracer OnStart(Transaction? tx) + protected override SimulateTxTracer OnStart(Transaction? tx) { if (tx?.Hash is not null) { - ulong currentTxIndex = _txIndex++; - return new(isTracingLogs, tx, (ulong)_currentBlock!.Number, _currentBlock!.Hash!, ++_txIndex); + return new(isTracingLogs, tx, _blockNumber, Hash256.Zero, _blockTimestamp, _txIndex++); } - return (SimulateTxMutatorTracer)NullTxTracer.Instance; + return (SimulateTxTracer)NullTxTracer.Instance; } - protected override SimulateCallResult OnEnd(SimulateTxMutatorTracer txTracer) => txTracer.TraceResult!; + protected override SimulateCallResult OnEnd(SimulateTxTracer txTracer) => txTracer.TraceResult!; - public void ReapplyBlockHash() + public void ReapplyBlockHash(Hash256 hash) { foreach (SimulateCallResult simulateCallResult in TxTraces) { foreach (Log log in simulateCallResult.Logs) { - log.BlockHash = _currentBlock!.Hash!; + log.BlockHash = hash; } } } @@ -41,7 +41,8 @@ public void ReapplyBlockHash() public override void StartNewBlockTrace(Block block) { _txIndex = 0; - _currentBlock = block; + _blockNumber = (ulong)block.Number; + _blockTimestamp = block.Timestamp; base.StartNewBlockTrace(block); } } diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockValidationTransactionsExecutor.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockValidationTransactionsExecutor.cs index e4a114505980..c7a7ed3f777f 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockValidationTransactionsExecutor.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockValidationTransactionsExecutor.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Threading; using Nethermind.Blockchain.Tracing; using Nethermind.Consensus.Processing; @@ -37,7 +36,7 @@ public TxReceipt[] ProcessTransactions(Block block, ProcessingOptions processing long startingGasLeft = simulateState.TotalGasLeft; if (!simulateState.Validate) { - processingOptions |= ProcessingOptions.ForceProcessing | ProcessingOptions.DoNotVerifyNonce | ProcessingOptions.NoValidation; + processingOptions |= ProcessingOptions.ForceProcessing | ProcessingOptions.NoValidation; } var result = baseTransactionExecutor.ProcessTransactions(block, processingOptions, receiptsTracer, token); @@ -48,10 +47,6 @@ public TxReceipt[] ProcessTransactions(Block block, ProcessingOptions processing { currentGasUsedTotal += txReceipt.GasUsed; txReceipt.GasUsedTotal = currentGasUsedTotal; - - // For some reason, the logs from geth when processing the block is missing but not in the output from tracer. - // this cause the receipt root to be different than us. So we simulate it here. - txReceipt.Logs = []; } block.Header.GasUsed = startingGasLeft - simulateState.TotalGasLeft; diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockhashProvider.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockhashProvider.cs index 607d999d06e6..00f3645aadd0 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockhashProvider.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateBlockhashProvider.cs @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Threading; +using System.Threading.Tasks; using Nethermind.Blockchain; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -12,7 +14,7 @@ namespace Nethermind.Facade.Simulate; public sealed class SimulateBlockhashProvider(IBlockhashProvider blockhashProvider, IBlockTree blockTree) : IBlockhashProvider { - public Hash256? GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec? spec) + public Hash256? GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec spec) { long bestKnown = blockTree.BestKnownNumber; return bestKnown < number && blockTree.BestSuggestedHeader is not null @@ -20,11 +22,5 @@ public sealed class SimulateBlockhashProvider(IBlockhashProvider blockhashProvid : blockhashProvider.GetBlockhash(currentBlock, number, spec); } - public Hash256? GetBlockhash(BlockHeader currentBlock, long number) - { - long bestKnown = blockTree.BestKnownNumber; - return bestKnown < number && blockTree.BestSuggestedHeader is not null - ? blockhashProvider.GetBlockhash(blockTree.BestSuggestedHeader!, bestKnown) - : blockhashProvider.GetBlockhash(currentBlock, number); - } + public Task Prefetch(BlockHeader currentBlock, CancellationToken token) => blockhashProvider.Prefetch(currentBlock, token); } diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateBridgeHelper.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateBridgeHelper.cs index 62cc466aba7b..3b3dc0b029fb 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateBridgeHelper.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateBridgeHelper.cs @@ -15,11 +15,11 @@ using Nethermind.State; using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using Nethermind.Evm.State; +using Nethermind.Evm.TransactionProcessing; using Transaction = Nethermind.Core.Transaction; namespace Nethermind.Facade.Simulate; @@ -36,6 +36,7 @@ private void PrepareState( BlockStateCall blockStateCall, IWorldState stateProvider, IOverridableCodeInfoRepository codeInfoRepository, + long blockNumber, IReleaseSpec releaseSpec) { stateProvider.ApplyStateOverridesNoCommit(codeInfoRepository, blockStateCall.StateOverrides, releaseSpec); @@ -46,6 +47,9 @@ private void PrepareState( { stateProvider.CreateAccountIfNotExists(address, 0, 0); } + + stateProvider.Commit(releaseSpec, commitRoots: true); + stateProvider.CommitTree(blockNumber); } public SimulateOutput TrySimulate( @@ -64,41 +68,48 @@ public SimulateOutput TrySimulate( try { - if (!TrySimulate(parent, payload, tracer, env, list, gasCapLimit, cancellationToken, out string? error)) - { - result.Error = error; - } + Simulate(parent, payload, tracer, env, list, gasCapLimit, cancellationToken); + } + catch (ArgumentException ex) + { + result.Error = ex.Message; + result.IsInvalidInput = true; + } + catch (InvalidTransactionException ex) + { + result.Error = ex.Reason.ErrorDescription; + result.TransactionResult = ex.Reason; } catch (InsufficientBalanceException ex) { result.Error = ex.Message; + result.TransactionResult = TransactionResult.InsufficientSenderBalance; } catch (Exception ex) { - result.Error = ex.ToString(); + result.Error = ex.Message; } return result; } - private bool TrySimulate(BlockHeader parent, + private void Simulate(BlockHeader parent, SimulatePayload payload, IBlockTracer tracer, SimulateReadOnlyBlocksProcessingScope env, List> output, long gasCapLimit, - CancellationToken cancellationToken, - [NotNullWhen(false)] out string? error) + CancellationToken cancellationToken) { IBlockTree blockTree = env.BlockTree; IWorldState stateProvider = env.WorldState; parent = GetParent(parent, payload, blockTree); - env.SimulateRequestState.TotalGasLeft = long.Min(parent.GasLimit, gasCapLimit); + env.SimulateRequestState.TotalGasLeft = gasCapLimit; if (payload.BlockStateCalls is not null) { - Dictionary nonceCache = new(); + Dictionary nonceCache = new(); IBlockTracer cancellationBlockTracer = tracer.WithCancellation(cancellationToken); foreach (BlockStateCall blockCall in payload.BlockStateCalls) @@ -106,6 +117,7 @@ private bool TrySimulate(BlockHeader parent, nonceCache.Clear(); (BlockHeader callHeader, IReleaseSpec spec) = GetCallHeader(env.SpecProvider, blockCall, parent, payload.Validation); + env.SimulateRequestState.BlockGasLeft = callHeader.GasLimit; callHeader.Hash = callHeader.CalculateHash(); TransactionWithSourceDetails[] calls = blockCall.Calls ?? []; @@ -114,11 +126,11 @@ private bool TrySimulate(BlockHeader parent, .Select((c) => c.HadGasLimitInRequest) .ToArray(); + PrepareState(blockCall, env.WorldState, env.CodeInfoRepository, callHeader.Number, spec); + BlockBody body = AssembleBody(calls, stateProvider, nonceCache, spec); Block callBlock = new Block(callHeader, body); - PrepareState(blockCall, env.WorldState, env.CodeInfoRepository, spec); - ProcessingOptions processingFlags = payload.Validation ? SimulateProcessingOptions : SimulateProcessingOptions | ProcessingOptions.NoValidation; @@ -137,9 +149,9 @@ private bool TrySimulate(BlockHeader parent, blockTree.SuggestBlock(processedBlock, BlockTreeSuggestOptions.ForceSetAsMain); blockTree.UpdateHeadBlock(processedBlock.Hash!); - if (tracer is SimulateBlockMutatorTracer simulateTracer) + if (tracer is SimulateBlockTracer simulateTracer) { - simulateTracer.ReapplyBlockHash(); + simulateTracer.ReapplyBlockHash(processedBlock.Hash); } SimulateBlockResult blockResult = new(processedBlock, payload.ReturnFullTransactionObjects, specProvider) @@ -151,15 +163,12 @@ private bool TrySimulate(BlockHeader parent, parent = processedBlock.Header; } } - - error = null; - return true; } private BlockBody AssembleBody( TransactionWithSourceDetails[] calls, IWorldState stateProvider, - Dictionary nonceCache, + Dictionary nonceCache, IReleaseSpec spec) { Transaction[] transactions = calls @@ -204,19 +213,19 @@ private static BlockHeader GetParent(BlockHeader parent, SimulatePayload nonceCache) + Dictionary nonceCache) { Transaction? transaction = transactionDetails.Transaction; transaction.SenderAddress ??= Address.Zero; if (!transactionDetails.HadNonceInRequest) { - ref UInt256 cachedNonce = ref CollectionsMarshal.GetValueRefOrAddDefault(nonceCache, transaction.SenderAddress, out bool exist); + ref ulong cachedNonce = ref CollectionsMarshal.GetValueRefOrAddDefault(nonceCache, transaction.SenderAddress, out bool exist); if (!exist) { if (stateProvider.TryGetAccount(transaction.SenderAddress, out AccountStruct test)) { - cachedNonce = test.Nonce; + cachedNonce = test.Nonce.ToUInt64(null); } // else // Todo think if we shall create account here } @@ -252,11 +261,21 @@ private Transaction CreateTransaction( [], requestsHash: parent.RequestsHash) { - MixHash = parent.MixHash, - IsPostMerge = parent.Difficulty == 0, + MixHash = Hash256.Zero, RequestsHash = parent.RequestsHash, }; + if ((ForkActivation)result.Number >= specProvider.MergeBlockNumber) + { + result.Difficulty = UInt256.Zero; + result.IsPostMerge = true; + } + else + { + result.Difficulty = parent.Difficulty; + result.IsPostMerge = false; + } + IReleaseSpec spec = specProvider.GetSpec(result); if (spec.WithdrawalsEnabled) result.WithdrawalsRoot = Keccak.EmptyTreeHash; @@ -274,7 +293,7 @@ private Transaction CreateTransaction( result.BaseFeePerGas = 0; } - result.ExcessBlobGas = spec.IsEip4844Enabled ? BlobGasCalculator.CalculateExcessBlobGas(parent, spec) : (ulong?)0; + result.ExcessBlobGas = spec.IsEip4844Enabled ? BlobGasCalculator.CalculateExcessBlobGas(parent, spec) : null; block.BlockOverrides?.ApplyOverrides(result); diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryBlockStore.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryBlockStore.cs index ea2f6f219b19..a4b0be4b9dbc 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryBlockStore.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryBlockStore.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using Nethermind.Blockchain.Blocks; using Nethermind.Core; -using Nethermind.Core.Buffers; using Nethermind.Core.Crypto; using Nethermind.Serialization.Rlp; @@ -53,16 +52,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 bcc2487ed0a1..4410d89aeb75 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryHeaderStore.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryHeaderStore.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using Nethermind.Blockchain.Headers; using Nethermind.Core; @@ -22,13 +21,13 @@ public class SimulateDictionaryHeaderStore(IHeaderStore readonlyBaseHeaderStore) public void Insert(BlockHeader header) { - _headerDict[header.Hash] = header; + _headerDict[header.Hash!] = header; InsertBlockNumber(header.Hash, header.Number); } public void BulkInsert(IReadOnlyList headers) { - foreach (var header in headers) + foreach (BlockHeader header in headers) { Insert(header); } @@ -36,17 +35,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) { @@ -55,10 +50,7 @@ public void BulkInsert(IReadOnlyList headers) return header; } - public void Cache(BlockHeader header) - { - Insert(header); - } + public void Cache(BlockHeader header) => Insert(header); public void Delete(Hash256 blockHash) { @@ -66,13 +58,10 @@ public void Delete(Hash256 blockHash) _blockNumberDict.Remove(blockHash); } - public void InsertBlockNumber(Hash256 blockHash, long blockNumber) - { - _blockNumberDict[blockHash] = blockNumber; - } + public void InsertBlockNumber(Hash256 blockHash, long blockNumber) => _blockNumberDict[blockHash] = blockNumber; - public long? GetBlockNumber(Hash256 blockHash) - { - return _blockNumberDict.TryGetValue(blockHash, out var blockNumber) ? blockNumber : readonlyBaseHeaderStore.GetBlockNumber(blockHash); - } + public long? GetBlockNumber(Hash256 blockHash) => + _blockNumberDict.TryGetValue(blockHash, out var blockNumber) ? blockNumber : readonlyBaseHeaderStore.GetBlockNumber(blockHash); + + public BlockHeader? Get(Hash256 blockHash, long? blockNumber = null) => Get(blockHash, true, blockNumber); } diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateOutput.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateOutput.cs index 04cd2d539674..55ababa23aea 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateOutput.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateOutput.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; +using Nethermind.Evm.TransactionProcessing; using Nethermind.Facade.Proxy.Models.Simulate; namespace Nethermind.Facade.Simulate; @@ -9,7 +10,8 @@ namespace Nethermind.Facade.Simulate; public class SimulateOutput { public string? Error { get; set; } - public int? ErrorCode { get; set; } + public bool IsInvalidInput { get; set; } + public TransactionResult TransactionResult { get; set; } public IReadOnlyList> Items { get; init; } } diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnv.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnv.cs index e779fa4dd8d8..900a8c4bf6ad 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnv.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnv.cs @@ -3,7 +3,6 @@ using System; using Nethermind.Blockchain; -using Nethermind.Blockchain.Tracing; using Nethermind.Consensus.Processing; using Nethermind.Core; using Nethermind.Core.Specs; @@ -15,7 +14,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 +61,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 75191899dfdd..435937157438 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnvFactory.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnvFactory.cs @@ -37,16 +37,21 @@ public ISimulateReadOnlyBlocksProcessingEnv Create() IReadOnlyDbProvider editableDbProvider = new ReadOnlyDbProvider(dbProvider, true); IOverridableEnv overridableEnv = overridableEnvFactory.Create(); - BlockTree tempBlockTree = CreateTempBlockTree(editableDbProvider, specProvider, logManager, editableDbProvider); + IHeaderStore mainHeaderStore = new HeaderStore(editableDbProvider.HeadersDb, editableDbProvider.BlockNumbersDb); + SimulateDictionaryHeaderStore tmpHeaderStore = new(mainHeaderStore); + BlockTree tempBlockTree = CreateTempBlockTree(editableDbProvider, specProvider, logManager, editableDbProvider, tmpHeaderStore); BlockTreeOverlay overrideBlockTree = new BlockTreeOverlay(baseBlockTree, tempBlockTree); ILifetimeScope envLifetimeScope = rootLifetimeScope.BeginLifetimeScope((builder) => builder .AddModule(overridableEnv) // worldstate related override here + .AddSingleton(editableDbProvider) .AddSingleton(overrideBlockTree) .AddSingleton(overrideBlockTree) + .AddSingleton(tmpHeaderStore) + .AddSingleton(c => c.Resolve()) + .AddSingleton() .AddModule(validationModules) .AddDecorator() - .AddDecorator() .AddDecorator() .AddDecorator() .AddDecorator() @@ -56,18 +61,22 @@ public ISimulateReadOnlyBlocksProcessingEnv Create() .AddScoped() .AddScoped()); + envLifetimeScope.Disposer.AddInstanceForDisposal(editableDbProvider); rootLifetimeScope.Disposer.AddInstanceForAsyncDisposal(envLifetimeScope); return envLifetimeScope.Resolve(); } - private static BlockTree CreateTempBlockTree(IReadOnlyDbProvider readOnlyDbProvider, ISpecProvider? specProvider, ILogManager logManager, IReadOnlyDbProvider editableDbProvider) + private static BlockTree CreateTempBlockTree( + IReadOnlyDbProvider readOnlyDbProvider, + ISpecProvider? specProvider, + ILogManager logManager, + IReadOnlyDbProvider editableDbProvider, + SimulateDictionaryHeaderStore tmpHeaderStore) { - IBlockStore mainblockStore = new BlockStore(editableDbProvider.BlocksDb); - IHeaderStore mainHeaderStore = new HeaderStore(editableDbProvider.HeadersDb, editableDbProvider.BlockNumbersDb); - SimulateDictionaryHeaderStore tmpHeaderStore = new(mainHeaderStore); + 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/SimulateRequestState.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateRequestState.cs index 4ddd9701db52..d21232c3cfa1 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateRequestState.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateRequestState.cs @@ -10,5 +10,6 @@ public class SimulateRequestState public bool Validate { get; set; } public UInt256? BlobBaseFeeOverride { get; set; } public long TotalGasLeft { get; set; } + public long BlockGasLeft { get; set; } public bool[] TxsWithExplicitGas { get; set; } } diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateTransactionProcessorAdapter.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateTransactionProcessorAdapter.cs index aee055c80e7b..b78cea3aba98 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateTransactionProcessorAdapter.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateTransactionProcessorAdapter.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Nethermind.Core; using Nethermind.Crypto; using Nethermind.Evm; @@ -17,6 +16,11 @@ public TransactionResult Execute(Transaction transaction, ITxTracer txTracer) { // The gas limit per tx go down as the block is processed. if (!simulateRequestState.TxsWithExplicitGas[_currentTxIndex]) + { + transaction.GasLimit = long.Min(simulateRequestState.BlockGasLeft, simulateRequestState.TotalGasLeft); + } + + if (simulateRequestState.TotalGasLeft < transaction.GasLimit) { transaction.GasLimit = simulateRequestState.TotalGasLeft; } @@ -26,6 +30,8 @@ public TransactionResult Execute(Transaction transaction, ITxTracer txTracer) // Keep track of gas left simulateRequestState.TotalGasLeft -= transaction.SpentGas; + simulateRequestState.BlockGasLeft -= transaction.SpentGas; + _currentTxIndex++; return result; } diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateTxMutatorTracer.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateTxTracer.cs similarity index 53% rename from src/Nethermind/Nethermind.Facade/Simulate/SimulateTxMutatorTracer.cs rename to src/Nethermind/Nethermind.Facade/Simulate/SimulateTxTracer.cs index b62dc1d205ae..25f078f9fb11 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateTxMutatorTracer.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateTxTracer.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using Nethermind.Abi; -using Nethermind.Blockchain.Tracing; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Evm; @@ -17,47 +16,68 @@ namespace Nethermind.Facade.Simulate; -public sealed class SimulateTxMutatorTracer : TxTracer, ITxLogsMutator +public sealed class SimulateTxTracer : TxTracer { - private static readonly Hash256 transferSignature = + private static readonly Hash256 TransferSignature = new AbiSignature("Transfer", AbiType.Address, AbiType.Address, AbiType.UInt256).Hash; + private static readonly AbiSignature AbiTransferSignature = new("", AbiType.UInt256); + private static readonly Address Erc20Sender = new("0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"); private readonly Hash256 _currentBlockHash; private readonly ulong _currentBlockNumber; + private readonly ulong _currentBlockTimestamp; private readonly ulong _txIndex; - private ICollection? _logsToMutate; + private readonly List _logs; private readonly Transaction _tx; + private readonly bool _isTracingTransfers; - public SimulateTxMutatorTracer(bool isTracingTransfers, Transaction tx, ulong currentBlockNumber, Hash256 currentBlockHash, - ulong txIndex) + public SimulateTxTracer(bool isTracingTransfers, Transaction tx, ulong currentBlockNumber, Hash256 currentBlockHash, + ulong currentBlockTimestamp, ulong txIndex) { // Note: Tx hash will be mutated as tx is modified while processing block _tx = tx; _currentBlockNumber = currentBlockNumber; _currentBlockHash = currentBlockHash; + _currentBlockTimestamp = currentBlockTimestamp; _txIndex = txIndex; + _isTracingTransfers = isTracingTransfers; IsTracingReceipt = true; - IsTracingActions = IsMutatingLogs = isTracingTransfers; + IsTracingLogs = true; + IsTracingActions = true; + _logs = new(); } public SimulateCallResult? TraceResult { get; set; } - public bool IsMutatingLogs { get; } - - public void SetLogsToMutate(ICollection logsToMutate) => _logsToMutate = logsToMutate; - public override void ReportAction(long gas, UInt256 value, Address from, Address to, ReadOnlyMemory input, ExecutionType callType, bool isPrecompileCall = false) { + if (!_isTracingTransfers) return; base.ReportAction(gas, value, from, to, input, callType, isPrecompileCall); + if (callType == ExecutionType.DELEGATECALL) return; if (value > UInt256.Zero) { - var data = AbiEncoder.Instance.Encode(AbiEncodingStyle.Packed, new AbiSignature("", AbiType.UInt256), - value); - _logsToMutate?.Add(new LogEntry(Erc20Sender, data, [transferSignature, (Hash256)from.ToHash(), (Hash256)to.ToHash()])); + var data = AbiEncoder.Instance.Encode(AbiEncodingStyle.Packed, AbiTransferSignature, value); + _logs.Add(new LogEntry(Erc20Sender, data, [TransferSignature, from.ToHash().ToHash256(), to.ToHash().ToHash256()])); + } + } + + public override void ReportSelfDestruct(Address address, UInt256 balance, Address refundAddress) + { + base.ReportSelfDestruct(address, balance, refundAddress); + if (balance > UInt256.Zero) + { + var data = AbiEncoder.Instance.Encode(AbiEncodingStyle.Packed, AbiTransferSignature, balance); + _logs.Add(new LogEntry(Erc20Sender, data, [TransferSignature, address.ToHash().ToHash256(), refundAddress.ToHash().ToHash256()])); } } + public override void ReportLog(LogEntry log) + { + base.ReportLog(log); + _logs.Add(log); + } + public override void MarkAsSuccess(Address recipient, GasConsumed gasSpent, byte[] output, LogEntry[] logs, Hash256? stateRoot = null) { TraceResult = new SimulateCallResult @@ -65,16 +85,17 @@ public override void MarkAsSuccess(Address recipient, GasConsumed gasSpent, byte GasUsed = (ulong)gasSpent.SpentGas, ReturnData = output, Status = StatusCode.Success, - Logs = logs.Select((entry, i) => new Log + Logs = _logs.Select((entry, i) => new Log { Address = entry.Address, Topics = entry.Topics, Data = entry.Data, - LogIndex = (ulong)i, + LogIndex = _txIndex + (ulong)i, TransactionHash = _tx.Hash!, TransactionIndex = _txIndex, BlockHash = _currentBlockHash, - BlockNumber = _currentBlockNumber + BlockNumber = _currentBlockNumber, + BlockTimestamp = _currentBlockTimestamp }).ToList() }; } @@ -86,10 +107,20 @@ public override void MarkAsFailed(Address recipient, GasConsumed gasSpent, byte[ GasUsed = (ulong)gasSpent.SpentGas, Error = new Error { - Message = error + Message = error is TransactionSubstate.Revert ? "execution reverted" : "execution reverted: " + error, + EvmException = _exceptionType, + Data = output }, - ReturnData = output, + ReturnData = [], Status = StatusCode.Failure }; } + + private EvmExceptionType _exceptionType = EvmExceptionType.None; + + public override void ReportActionError(EvmExceptionType evmExceptionType) + { + base.ReportActionError(evmExceptionType); + _exceptionType = evmExceptionType; + } } diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateVirtualMachine.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateVirtualMachine.cs deleted file mode 100644 index 60761f2d5fc3..000000000000 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateVirtualMachine.cs +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Diagnostics.CodeAnalysis; -using Nethermind.Blockchain.Tracing; -using Nethermind.Core; -using Nethermind.Evm; -using Nethermind.Evm.State; -using Nethermind.Evm.Tracing; - -namespace Nethermind.Facade.Simulate; - -public class SimulateVirtualMachine(IVirtualMachine virtualMachine) : IVirtualMachine -{ - public TransactionSubstate ExecuteTransaction(EvmState state, IWorldState worldState, ITxTracer txTracer) - where TTracingInst : struct, IFlag - { - if (txTracer.IsTracingActions && TryGetLogsMutator(txTracer, out ITxLogsMutator logsMutator)) - { - logsMutator.SetLogsToMutate(state.AccessTracker.Logs); - } - - return virtualMachine.ExecuteTransaction(state, worldState, txTracer); - } - - private static bool TryGetLogsMutator(ITxTracer txTracer, [NotNullWhen(true)] out ITxLogsMutator? txLogsMutator) - { - switch (txTracer) - { - case ITxLogsMutator { IsMutatingLogs: true } logsMutator: - txLogsMutator = logsMutator; - return true; - case ITxTracerWrapper txTracerWrapper: - return TryGetLogsMutator(txTracerWrapper.InnerTracer, out txLogsMutator); - default: - txLogsMutator = null; - return false; - } - } - - public ref readonly BlockExecutionContext BlockExecutionContext => ref virtualMachine.BlockExecutionContext; - - public ref readonly TxExecutionContext TxExecutionContext => ref virtualMachine.TxExecutionContext; - - public void SetBlockExecutionContext(in BlockExecutionContext blockExecutionContext) - => virtualMachine.SetBlockExecutionContext(blockExecutionContext); - - public void SetTxExecutionContext(in TxExecutionContext txExecutionContext) - => virtualMachine.SetTxExecutionContext(txExecutionContext); - public int OpCodeCount => virtualMachine.OpCodeCount; -} diff --git a/src/Nethermind/Nethermind.Facade/TransactionLookupResult.cs b/src/Nethermind/Nethermind.Facade/TransactionLookupResult.cs new file mode 100644 index 000000000000..a8d486ad2440 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/TransactionLookupResult.cs @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Facade.Eth; + +namespace Nethermind.Facade; + +public readonly struct TransactionLookupResult(Transaction? transaction, in TransactionForRpcContext extraData) +{ + public Transaction? Transaction { get; } = transaction; + public TransactionForRpcContext ExtraData { get; } = extraData; +} diff --git a/src/Nethermind/Nethermind.Flashbots.Test/BidTraceSerializationTests.cs b/src/Nethermind/Nethermind.Flashbots.Test/BidTraceSerializationTests.cs new file mode 100644 index 000000000000..c3d0664bce46 --- /dev/null +++ b/src/Nethermind/Nethermind.Flashbots.Test/BidTraceSerializationTests.cs @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +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 7b1690f131fb..e5b74f0a7a7f 100644 --- a/src/Nethermind/Nethermind.Flashbots.Test/FlashbotsModuleTests.cs +++ b/src/Nethermind/Nethermind.Flashbots.Test/FlashbotsModuleTests.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using Autofac; using FluentAssertions; -using Nethermind.Consensus.Processing; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; @@ -57,7 +56,7 @@ public virtual async Task TestValidateBuilderSubmissionV3() ResultWrapper result = await rpc.flashbots_validateBuilderSubmissionV3(BlockRequest); result.Should().NotBeNull(); - Assert.That(result.Result.Error, Is.EqualTo("No proposer payment receipt")); + Assert.That(result.Result.Error, Is.EqualTo("Invalid blob proofs")); Assert.That(result.Data.Status, Is.EqualTo(FlashbotsStatus.Invalid)); string response = await RpcTest.TestSerializedRequest(rpc, "flashbots_validateBuilderSubmissionV3", BlockRequest); @@ -68,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); @@ -78,9 +77,21 @@ private Block CreateBlock(EngineModuleTests.MergeTestBlockchain chain) Build.A.Withdrawal.WithIndex(1).WithValidatorIndex(1).WithAmount(100).WithRecipient(TestKeysAndAddress.TestAddr).TestObject ]; + Transaction[] transactions = [ + Build.A.Transaction.WithShardBlobTxTypeAndFields(1, spec: Prague.Instance).WithMaxFeePerGas(1.GWei()).WithMaxPriorityFeePerGas(1).SignedAndResolved(TestItem.PrivateKeyA).TestObject, + Build.A.Transaction.WithShardBlobTxTypeAndFields(2, spec: Osaka.Instance).WithMaxFeePerGas(1.GWei()).WithMaxPriorityFeePerGas(0).SignedAndResolved(TestItem.PrivateKeyB).TestObject, + Build.A.Transaction + .WithMaxFeePerGas(0) + .WithMaxPriorityFeePerGas(0) + .WithTo(TestKeysAndAddress.TestBuilderAddr) + .WithValue(132912184722469) + .SignedAndResolved(TestItem.PrivateKeyC) + .TestObject, + ]; + Hash256 prevRandao = Keccak.Zero; - Hash256 expectedBlockHash = new("0xed7c356cda6ea2e7d79be86be2b11c24f97b6501bf5519a5826fd384a458a060"); + Hash256 expectedBlockHash = new("0xf96547f16f2d140931e4a026b15a5490538d5479518bbd46338bbada948b403a"); string stateRoot = "0xa272b2f949e4a0e411c9b45542bd5d0ef3c311b5f26c4ed6b7a8d4f605a91154"; return new( @@ -95,7 +106,7 @@ private Block CreateBlock(EngineModuleTests.MergeTestBlockchain chain) Bytes.FromHexString("0x4e65746865726d696e64") // Nethermind ) { - BlobGasUsed = 0, + BlobGasUsed = 3 * Eip4844Constants.GasPerBlob, ExcessBlobGas = 0, BaseFeePerGas = 0, Bloom = Bloom.Empty, @@ -106,7 +117,7 @@ private Block CreateBlock(EngineModuleTests.MergeTestBlockchain chain) ReceiptsRoot = chain.BlockTree.Head!.ReceiptsRoot!, StateRoot = new(stateRoot), }, - [], + transactions, Array.Empty(), withdrawals ); diff --git a/src/Nethermind/Nethermind.Flashbots.Test/Rbuilder/RbuilderRpcModuleTests.cs b/src/Nethermind/Nethermind.Flashbots.Test/Rbuilder/RbuilderRpcModuleTests.cs index b7688fa13d84..013e78afd1df 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 e27ca437a8a9..2af6ac9c37d9 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.Flashbots/Data/RWithdrawal.cs b/src/Nethermind/Nethermind.Flashbots/Data/RWithdrawal.cs index 79d7e42241e6..819663ef9e0e 100644 --- a/src/Nethermind/Nethermind.Flashbots/Data/RWithdrawal.cs +++ b/src/Nethermind/Nethermind.Flashbots/Data/RWithdrawal.cs @@ -1,6 +1,5 @@ using System.Text.Json.Serialization; using Nethermind.Core; -using Nethermind.Int256; public class RWithdrawal { diff --git a/src/Nethermind/Nethermind.Flashbots/Handlers/ValidateBuilderSubmissionHandler.cs b/src/Nethermind/Nethermind.Flashbots/Handlers/ValidateBuilderSubmissionHandler.cs index 883913578826..7729c86745ce 100644 --- a/src/Nethermind/Nethermind.Flashbots/Handlers/ValidateBuilderSubmissionHandler.cs +++ b/src/Nethermind/Nethermind.Flashbots/Handlers/ValidateBuilderSubmissionHandler.cs @@ -1,17 +1,13 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; using Nethermind.Blockchain; using Nethermind.Blockchain.Tracing; using Nethermind.Consensus; using Nethermind.Consensus.Processing; using Nethermind.Consensus.Validators; using Nethermind.Core; -using Nethermind.Core.Crypto; +using Nethermind.Core.Collections; using Nethermind.Core.Specs; using Nethermind.Crypto; using Nethermind.Evm; @@ -22,6 +18,9 @@ using Nethermind.Logging; using Nethermind.Merge.Plugin.Data; using Nethermind.State.OverridableEnv; +using System; +using System.Threading; +using System.Threading.Tasks; namespace Nethermind.Flashbots.Handlers; @@ -70,7 +69,7 @@ public Task> ValidateSubmission(BuilderBlockValid return FlashbotsResult.Invalid("Parent beacon block root must be set in the request"); } - payload.ParentBeaconBlockRoot = new Hash256(request.ParentBeaconBlockRoot); + payload.ParentBeaconBlockRoot = request.ParentBeaconBlockRoot; BlobsBundleV1 blobsBundle = request.BlobsBundle; @@ -87,13 +86,15 @@ public Task> ValidateSubmission(BuilderBlockValid return FlashbotsResult.Invalid($"Block {payload} could not be parsed as a block: {decodingResult.Error}"); } - if (!ValidateBlock(block, request.Message, request.RegisteredGasLimit, out string? error)) + IReleaseSpec releaseSpec = _specProvider.GetSpec(block.Header); + + if (!ValidateBlock(block, request.Message, request.RegisteredGasLimit, releaseSpec, out string? error)) { if (_logger.IsWarn) _logger.Warn($"Invalid block. Result of {payloadStr}. Error: {error}"); return FlashbotsResult.Invalid(error ?? "Block validation failed"); } - if (!ValidateBlobsBundle(block.Transactions, blobsBundle, out string? blobsError)) + if (!ValidateBlobsBundle(block.Transactions, blobsBundle, releaseSpec, out string? blobsError)) { if (_logger.IsWarn) _logger.Warn($"Invalid blobs bundle. Result of {payloadStr}. Error: {blobsError}"); return FlashbotsResult.Invalid(blobsError ?? "Blobs bundle validation failed"); @@ -102,7 +103,7 @@ public Task> ValidateSubmission(BuilderBlockValid return FlashbotsResult.Valid(); } - private bool ValidateBlock(Block block, BidTrace message, long registeredGasLimit, out string? error) + private bool ValidateBlock(Block block, BidTrace message, long registeredGasLimit, IReleaseSpec releaseSpec, out string? error) { error = null; @@ -133,7 +134,7 @@ private bool ValidateBlock(Block block, BidTrace message, long registeredGasLimi Address feeRecipient = message.ProposerFeeRecipient; UInt256 expectedProfit = message.Value; - if (!ValidatePayload(block, feeRecipient, expectedProfit, registeredGasLimit, _flashbotsConfig.UseBalanceDiffProfit, _flashbotsConfig.ExcludeWithdrawals, out error)) + if (!ValidatePayload(block, feeRecipient, expectedProfit, registeredGasLimit, _flashbotsConfig.UseBalanceDiffProfit, _flashbotsConfig.ExcludeWithdrawals, releaseSpec, out error)) { return false; } @@ -143,51 +144,53 @@ private bool ValidateBlock(Block block, BidTrace message, long registeredGasLimi return true; } - private bool ValidateBlobsBundle(Transaction[] transactions, BlobsBundleV1 blobsBundle, out string? error) + private bool ValidateBlobsBundle(Transaction[] transactions, BlobsBundleV1 blobsBundle, IReleaseSpec releaseSpec, out string? error) { - // get sum of length of blobs of each transaction - int totalBlobsLength = 0; + ShardBlobNetworkWrapper blobs = new(blobsBundle.Blobs, blobsBundle.Commitments, blobsBundle.Proofs, releaseSpec.BlobProofVersion); + + IBlobProofsVerifier verifier = IBlobProofsManager.For(releaseSpec.BlobProofVersion); + + using ArrayPoolListRef hashes = new(blobs.Blobs.Length); + foreach (Transaction tx in transactions) { - byte[]?[]? versionedHashes = tx.BlobVersionedHashes; - if (versionedHashes is not null) + if (tx.BlobVersionedHashes is not null) { - totalBlobsLength += versionedHashes.Length; + foreach (byte[]? blobHash in tx.BlobVersionedHashes) + { + if (blobHash is not null) + { + hashes.Add(blobHash); + } + } } } - if (totalBlobsLength != blobsBundle.Blobs.Length) + if (!verifier.ValidateHashes(blobs, hashes.AsSpan())) { - error = $"Total blobs length mismatch. Expected {totalBlobsLength} but got {blobsBundle.Blobs.Length}"; + error = "Invalid blob hashes"; return false; } - if (totalBlobsLength != blobsBundle.Commitments.Length) + if (!verifier.ValidateLengths(blobs)) { - error = $"Total commitments length mismatch. Expected {totalBlobsLength} but got {blobsBundle.Commitments.Length}"; + error = "Invalid blob lengths"; return false; } - if (totalBlobsLength != blobsBundle.Proofs.Length) + if (!verifier.ValidateProofs(blobs)) { - error = $"Total proofs length mismatch. Expected {totalBlobsLength} but got {blobsBundle.Proofs.Length}"; + error = "Invalid blob proofs"; return false; } - if (!IBlobProofsManager.For(ProofVersion.V1).ValidateProofs(new ShardBlobNetworkWrapper(blobsBundle.Blobs, blobsBundle.Commitments, blobsBundle.Proofs, ProofVersion.V1))) - { - error = "Invalid KZG proofs"; - return false; - } + _logger.Info($"Validated blobs bundle with {blobsBundle.Blobs.Length} blobs, commitments: {blobsBundle.Commitments.Length}, proofs: {blobsBundle.Proofs.Length}"); error = null; - - _logger.Info($"Validated blobs bundle with {totalBlobsLength} blobs, commitments: {blobsBundle.Commitments.Length}, proofs: {blobsBundle.Proofs.Length}"); - return true; } - private bool ValidatePayload(Block block, Address feeRecipient, UInt256 expectedProfit, long registerGasLimit, bool useBalanceDiffProfit, bool excludeWithdrawals, out string? error) + private bool ValidatePayload(Block block, Address feeRecipient, UInt256 expectedProfit, long registerGasLimit, bool useBalanceDiffProfit, bool excludeWithdrawals, IReleaseSpec releaseSpec, out string? error) { BlockHeader? parentHeader = _blockTree.FindHeader(block.ParentHash!, BlockTreeLookupOptions.DoNotCreateLevelIfMissing); @@ -197,7 +200,7 @@ private bool ValidatePayload(Block block, Address feeRecipient, UInt256 expected return false; } - if (!ValidateBlockMetadata(block, registerGasLimit, parentHeader, out error)) + if (!ValidateBlockMetadata(block, registerGasLimit, parentHeader, releaseSpec, out error)) { return false; } @@ -206,9 +209,7 @@ private bool ValidatePayload(Block block, Address feeRecipient, UInt256 expected IWorldState worldState = scope.Component.WorldState; IBlockProcessor blockProcessor = scope.Component.BlockProcessor; - IReleaseSpec spec = _specProvider.GetSpec(parentHeader); - - RecoverSenderAddress(block, spec); + RecoverSenderAddress(block, releaseSpec); UInt256 feeRecipientBalanceBefore = worldState.HasStateForBlock(parentHeader) ? (worldState.AccountExists(feeRecipient) ? worldState.GetBalance(feeRecipient) : UInt256.Zero) : UInt256.Zero; BlockReceiptsTracer blockReceiptsTracer = new(); @@ -220,8 +221,7 @@ private bool ValidatePayload(Block block, Address feeRecipient, UInt256 expected ValidateSubmissionProcessingOptions |= ProcessingOptions.NoValidation; } - spec = _specProvider.GetSpec(block.Header); - _ = blockProcessor.ProcessOne(block, ValidateSubmissionProcessingOptions, blockReceiptsTracer, spec, CancellationToken.None); + _ = blockProcessor.ProcessOne(block, ValidateSubmissionProcessingOptions, blockReceiptsTracer, releaseSpec, CancellationToken.None); } catch (Exception e) { @@ -271,11 +271,10 @@ private void RecoverSenderAddress(Block block, IReleaseSpec spec) } } - private bool ValidateBlockMetadata(Block block, long registerGasLimit, BlockHeader parentHeader, out string? error) + private bool ValidateBlockMetadata(Block block, long registerGasLimit, BlockHeader parentHeader, IReleaseSpec releaseSpec, out string? error) { - if (!_headerValidator.Validate(block.Header, parentHeader)) + if (!_headerValidator.Validate(block.Header, parentHeader, false, out error)) { - error = $"Invalid block header hash {block.Header.Hash}"; return false; } @@ -291,7 +290,7 @@ private bool ValidateBlockMetadata(Block block, long registerGasLimit, BlockHead return false; } - long calculatedGasLimit = GetGasLimit(parentHeader, registerGasLimit); + long calculatedGasLimit = GetGasLimit(parentHeader, registerGasLimit, releaseSpec); if (calculatedGasLimit != block.Header.GasLimit) { @@ -302,23 +301,23 @@ private bool ValidateBlockMetadata(Block block, long registerGasLimit, BlockHead return true; } - private long GetGasLimit(BlockHeader parentHeader, long desiredGasLimit) + private long GetGasLimit(BlockHeader parentHeader, long desiredGasLimit, IReleaseSpec releaseSpec) { long parentGasLimit = parentHeader.GasLimit; long gasLimit = parentGasLimit; long? targetGasLimit = desiredGasLimit; long newBlockNumber = parentHeader.Number + 1; - IReleaseSpec spec = _specProvider.GetSpec(newBlockNumber, parentHeader.Timestamp); + if (targetGasLimit is not null) { - long maxGasLimitDifference = Math.Max(0, parentGasLimit / spec.GasLimitBoundDivisor - 1); + long maxGasLimitDifference = Math.Max(0, parentGasLimit / releaseSpec.GasLimitBoundDivisor - 1); gasLimit = targetGasLimit.Value > parentGasLimit ? parentGasLimit + Math.Min(targetGasLimit.Value - parentGasLimit, maxGasLimitDifference) : parentGasLimit - Math.Min(parentGasLimit - targetGasLimit.Value, maxGasLimitDifference); } - gasLimit = Eip1559GasLimitAdjuster.AdjustGasLimit(spec, gasLimit, newBlockNumber); + gasLimit = Eip1559GasLimitAdjuster.AdjustGasLimit(releaseSpec, gasLimit, newBlockNumber); return gasLimit; } @@ -343,9 +342,9 @@ private bool ValidateProposerPayment(UInt256 expectedProfit, bool useBalanceDiff return false; } - private bool ValidateProcessedBlock(Block processedBlock, Address feeRecipient, UInt256 expectedProfit, IReadOnlyList receipts, out string? error) + private bool ValidateProcessedBlock(Block processedBlock, Address feeRecipient, UInt256 expectedProfit, ReadOnlySpan receipts, out string? error) { - if (receipts.Count == 0) + if (receipts.Length == 0) { error = "No proposer payment receipt"; return false; @@ -355,7 +354,7 @@ private bool ValidateProcessedBlock(Block processedBlock, Address feeRecipient, if (lastReceipt.StatusCode != StatusCode.Success) { - error = $"Proposer payment failed "; + error = $"Proposer payment failed"; return false; } @@ -371,7 +370,7 @@ private bool ValidateProcessedBlock(Block processedBlock, Address feeRecipient, if (paymentTx.To != feeRecipient) { - error = $"Proposer payment transaction recipient is not the proposer,received {paymentTx.To} expected {feeRecipient}"; + error = $"Proposer payment transaction recipient is not the proposer, received {paymentTx.To} expected {feeRecipient}"; return false; } diff --git a/src/Nethermind/Nethermind.Flashbots/Modules/Flashbots/FlashbotsRpcModule.cs b/src/Nethermind/Nethermind.Flashbots/Modules/Flashbots/FlashbotsRpcModule.cs index c17b9b347082..4aef28d99d81 100644 --- a/src/Nethermind/Nethermind.Flashbots/Modules/Flashbots/FlashbotsRpcModule.cs +++ b/src/Nethermind/Nethermind.Flashbots/Modules/Flashbots/FlashbotsRpcModule.cs @@ -5,7 +5,6 @@ using Nethermind.Flashbots.Data; using Nethermind.Flashbots.Handlers; using Nethermind.JsonRpc; -using Nethermind.Merge.Plugin.Data; namespace Nethermind.Flashbots.Modules.Flashbots; diff --git a/src/Nethermind/Nethermind.Flashbots/Modules/Flashbots/FlashbotsRpcModuleFactory.cs b/src/Nethermind/Nethermind.Flashbots/Modules/Flashbots/FlashbotsRpcModuleFactory.cs index c27daceb04d1..a32e093feb40 100644 --- a/src/Nethermind/Nethermind.Flashbots/Modules/Flashbots/FlashbotsRpcModuleFactory.cs +++ b/src/Nethermind/Nethermind.Flashbots/Modules/Flashbots/FlashbotsRpcModuleFactory.cs @@ -1,17 +1,14 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Autofac; using Nethermind.Blockchain; using Nethermind.Blockchain.Receipts; -using Nethermind.Consensus.Processing; using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Container; using Nethermind.Core.Specs; using Nethermind.Crypto; -using Nethermind.Evm.TransactionProcessing; using Nethermind.Flashbots.Handlers; using Nethermind.JsonRpc.Modules; using Nethermind.Logging; diff --git a/src/Nethermind/Nethermind.Flashbots/Modules/Rbuilder/RbuilderRpcModule.cs b/src/Nethermind/Nethermind.Flashbots/Modules/Rbuilder/RbuilderRpcModule.cs index ffe182392699..27a12d1922dd 100644 --- a/src/Nethermind/Nethermind.Flashbots/Modules/Rbuilder/RbuilderRpcModule.cs +++ b/src/Nethermind/Nethermind.Flashbots/Modules/Rbuilder/RbuilderRpcModule.cs @@ -10,7 +10,6 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Evm.State; -using Nethermind.Evm.TransactionProcessing; using Nethermind.Int256; using Nethermind.JsonRpc; using Nethermind.State; diff --git a/src/Nethermind/Nethermind.Grpc/Clients/GrpcClient.cs b/src/Nethermind/Nethermind.Grpc/Clients/GrpcClient.cs index 2855a1a2e0ff..2c6fc5e85105 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 a7169e7e4e87..06b086dff91d 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.Test/NodeHealthServiceTests.cs b/src/Nethermind/Nethermind.HealthChecks.Test/NodeHealthServiceTests.cs index 953c0a6e6beb..57fddec89954 100644 --- a/src/Nethermind/Nethermind.HealthChecks.Test/NodeHealthServiceTests.cs +++ b/src/Nethermind/Nethermind.HealthChecks.Test/NodeHealthServiceTests.cs @@ -1,20 +1,16 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using System.IO.Abstractions; using System.Linq; -using Nethermind.Api; using Nethermind.Blockchain; -using Nethermind.Blockchain.Blocks; using Nethermind.Blockchain.Receipts; using Nethermind.Blockchain.Services; using Nethermind.Blockchain.Synchronization; using Nethermind.Consensus; using Nethermind.Consensus.Processing; using Nethermind.Core; -using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Facade.Eth; using Nethermind.Int256; @@ -24,7 +20,6 @@ using NSubstitute; using NUnit.Framework; using Nethermind.Core.Extensions; -using Nethermind.Synchronization.FastBlocks; using Nethermind.Synchronization.ParallelSync; namespace Nethermind.HealthChecks.Test; diff --git a/src/Nethermind/Nethermind.HealthChecks/HealthChecksWebhookInfo.cs b/src/Nethermind/Nethermind.HealthChecks/HealthChecksWebhookInfo.cs index 3314176be139..3b1e4fb9d8cf 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/IClHealthTracker.cs b/src/Nethermind/Nethermind.HealthChecks/IClHealthTracker.cs index 5d053ea3a22d..688e8116080a 100644 --- a/src/Nethermind/Nethermind.HealthChecks/IClHealthTracker.cs +++ b/src/Nethermind/Nethermind.HealthChecks/IClHealthTracker.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; - namespace Nethermind.HealthChecks; public interface IClHealthTracker diff --git a/src/Nethermind/Nethermind.HealthChecks/IHealthChecksConfig.cs b/src/Nethermind/Nethermind.HealthChecks/IHealthChecksConfig.cs index eac6efa88bdf..14733d674f5d 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 3fe6267162e0..7f6f3d606efc 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.HealthChecks/NodeHealthService.cs b/src/Nethermind/Nethermind.HealthChecks/NodeHealthService.cs index 0ca645ecf33d..b19f5b6d33e2 100644 --- a/src/Nethermind/Nethermind.HealthChecks/NodeHealthService.cs +++ b/src/Nethermind/Nethermind.HealthChecks/NodeHealthService.cs @@ -24,19 +24,19 @@ public class CheckHealthResult public IEnumerable Errors { get; set; } } - public class NodeHealthService : INodeHealthService + public class NodeHealthService( + ISyncServer syncServer, + IBlockchainProcessor blockchainProcessor, + IBlockProducerRunner blockProducerRunner, + IHealthChecksConfig healthChecksConfig, + IHealthHintService healthHintService, + IEthSyncingInfo ethSyncingInfo, + IClHealthTracker clHealthTracker, + UInt256? terminalTotalDifficulty, + IDriveInfo[] drives, + bool isMining) + : INodeHealthService { - private readonly ISyncServer _syncServer; - private readonly IBlockchainProcessor _blockchainProcessor; - private readonly IBlockProducerRunner _blockProducerRunner; - private readonly IHealthChecksConfig _healthChecksConfig; - private readonly IHealthHintService _healthHintService; - private readonly IEthSyncingInfo _ethSyncingInfo; - private readonly IClHealthTracker _clHealthTracker; - private readonly UInt256? _terminalTotalDifficulty; - private readonly IDriveInfo[] _drives; - private readonly bool _isMining; - public NodeHealthService( ISyncServer syncServer, IMainProcessingContext mainProcessingContext, @@ -47,7 +47,7 @@ public NodeHealthService( IClHealthTracker clHealthTracker, ISpecProvider specProvider, [KeyFilter(nameof(IInitConfig.BaseDbPath))] IDriveInfo[] drives, - IInitConfig initConfig) : this( + IMiningConfig miningConfig) : this( syncServer, mainProcessingContext.BlockchainProcessor, blockProducerRunner, @@ -57,31 +57,8 @@ public NodeHealthService( clHealthTracker, specProvider.TerminalTotalDifficulty, drives, - initConfig.IsMining) - { - } - - public NodeHealthService(ISyncServer syncServer, - IBlockchainProcessor blockchainProcessor, - IBlockProducerRunner blockProducerRunner, - IHealthChecksConfig healthChecksConfig, - IHealthHintService healthHintService, - IEthSyncingInfo ethSyncingInfo, - IClHealthTracker clHealthTracker, - UInt256? terminalTotalDifficulty, - IDriveInfo[] drives, - bool isMining) + miningConfig.Enabled) { - _syncServer = syncServer; - _isMining = isMining; - _healthChecksConfig = healthChecksConfig; - _healthHintService = healthHintService; - _blockchainProcessor = blockchainProcessor; - _blockProducerRunner = blockProducerRunner; - _ethSyncingInfo = ethSyncingInfo; - _clHealthTracker = clHealthTracker; - _terminalTotalDifficulty = terminalTotalDifficulty; - _drives = drives; } public CheckHealthResult CheckHealth() @@ -89,10 +66,10 @@ public CheckHealthResult CheckHealth() List<(string Message, string LongMessage)> messages = new(); List errors = new(); bool healthy = false; - long netPeerCount = _syncServer.GetPeerCount(); - SyncingResult syncingResult = _ethSyncingInfo.GetFullInfo(); + long netPeerCount = syncServer.GetPeerCount(); + SyncingResult syncingResult = ethSyncingInfo.GetFullInfo(); - if (_terminalTotalDifficulty is not null) + if (terminalTotalDifficulty is not null) { bool syncHealthy = CheckSyncPostMerge(messages, errors, syncingResult); @@ -109,24 +86,24 @@ public CheckHealthResult CheckHealth() } else { - if (!_isMining && syncingResult.IsSyncing) + if (!isMining && syncingResult.IsSyncing) { AddStillSyncingMessage(messages, syncingResult); CheckPeers(messages, errors, netPeerCount); } - else if (!_isMining && !syncingResult.IsSyncing) + else if (!isMining && !syncingResult.IsSyncing) { AddFullySyncMessage(messages); bool peers = CheckPeers(messages, errors, netPeerCount); bool processing = IsProcessingBlocks(messages, errors); healthy = peers && processing; } - else if (_isMining && syncingResult.IsSyncing) + else if (isMining && syncingResult.IsSyncing) { AddStillSyncingMessage(messages, syncingResult); healthy = CheckPeers(messages, errors, netPeerCount); } - else if (_isMining && !syncingResult.IsSyncing) + else if (isMining && !syncingResult.IsSyncing) { AddFullySyncMessage(messages); bool peers = CheckPeers(messages, errors, netPeerCount); @@ -137,11 +114,11 @@ public CheckHealthResult CheckHealth() } bool isLowDiskSpaceErrorAdded = false; - for (int index = 0; index < _drives.Length; index++) + for (int index = 0; index < drives.Length; index++) { - IDriveInfo drive = _drives[index]; + IDriveInfo drive = drives[index]; double freeSpacePercentage = drive.GetFreeSpacePercentage(); - if (freeSpacePercentage < _healthChecksConfig.LowStorageSpaceWarningThreshold) + if (freeSpacePercentage < healthChecksConfig.LowStorageSpaceWarningThreshold) { AddLowDiskSpaceMessage(messages, drive, freeSpacePercentage); if (!isLowDiskSpaceErrorAdded) @@ -156,18 +133,18 @@ public CheckHealthResult CheckHealth() return new CheckHealthResult() { Healthy = healthy, Errors = errors, Messages = messages, IsSyncing = syncingResult.IsSyncing }; } - public bool CheckClAlive() => _clHealthTracker?.CheckClAlive() ?? true; + public bool CheckClAlive() => clHealthTracker?.CheckClAlive() ?? true; private ulong? GetBlockProcessorIntervalHint() { - return _healthChecksConfig.MaxIntervalWithoutProcessedBlock ?? - _healthHintService.MaxSecondsIntervalForProcessingBlocksHint(); + return healthChecksConfig.MaxIntervalWithoutProcessedBlock ?? + healthHintService.MaxSecondsIntervalForProcessingBlocksHint(); } private ulong? GetBlockProducerIntervalHint() { - return _healthChecksConfig.MaxIntervalWithoutProducedBlock ?? - _healthHintService.MaxSecondsIntervalForProducingBlocksHint(); + return healthChecksConfig.MaxIntervalWithoutProducedBlock ?? + healthHintService.MaxSecondsIntervalForProducingBlocksHint(); } private static bool CheckSyncPostMerge(ICollection<(string Description, string LongDescription)> messages, @@ -223,7 +200,7 @@ private static bool CheckPeers(ICollection<(string Description, string LongDescr private bool IsProducingBlocks(ICollection<(string Description, string LongDescription)> messages, ICollection errors) { ulong? maxIntervalHint = GetBlockProducerIntervalHint(); - bool producingBlocks = _blockProducerRunner.IsProducingBlocks(maxIntervalHint); + bool producingBlocks = blockProducerRunner.IsProducingBlocks(maxIntervalHint); if (producingBlocks == false) { errors.Add(ErrorStrings.NotProducingBlocks); @@ -236,7 +213,7 @@ private bool IsProducingBlocks(ICollection<(string Description, string LongDescr private bool IsProcessingBlocks(ICollection<(string Description, string LongDescription)> messages, ICollection errors) { ulong? maxIntervalHint = GetBlockProcessorIntervalHint(); - bool processingBlocks = _blockchainProcessor.IsProcessingBlocks(maxIntervalHint); + bool processingBlocks = blockchainProcessor.IsProcessingBlocks(maxIntervalHint); if (processingBlocks == false) { errors.Add(ErrorStrings.NotProcessingBlocks); diff --git a/src/Nethermind/Nethermind.History.Test/HistoryPrunerTests.cs b/src/Nethermind/Nethermind.History.Test/HistoryPrunerTests.cs index 32732d657ca6..801c62832902 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 }; @@ -53,6 +53,7 @@ public async Task Can_prune_blocks_older_than_specified_epochs() { Pruning = PruningModes.Rolling, RetentionEpochs = 2, + PruningInterval = 0 }; using BasicTestBlockchain testBlockchain = await BasicTestBlockchain.Create(BuildContainer(historyConfig)); @@ -73,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++) @@ -101,6 +102,7 @@ public async Task Can_prune_to_ancient_barriers() { Pruning = PruningModes.UseAncientBarriers, RetentionEpochs = 100, // should have no effect + PruningInterval = 0 }; using BasicTestBlockchain testBlockchain = await BasicTestBlockchain.Create(BuildContainer(historyConfig)); @@ -120,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]); @@ -149,6 +151,7 @@ public async Task Prunes_up_to_sync_pivot() IHistoryConfig historyConfig = new HistoryConfig { Pruning = PruningModes.UseAncientBarriers, + PruningInterval = 0 }; using BasicTestBlockchain testBlockchain = await BasicTestBlockchain.Create(BuildContainer(historyConfig)); @@ -168,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]); @@ -198,6 +201,7 @@ public async Task Can_find_oldest_block() { Pruning = PruningModes.Rolling, RetentionEpochs = 2, + PruningInterval = 0 }; using BasicTestBlockchain testBlockchain = await BasicTestBlockchain.Create(BuildContainer(historyConfig)); @@ -217,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); @@ -231,6 +235,7 @@ public async Task Does_not_prune_when_disabled() IHistoryConfig historyConfig = new HistoryConfig { Pruning = PruningModes.Disabled, + PruningInterval = 0 }; using BasicTestBlockchain testBlockchain = await BasicTestBlockchain.Create(BuildContainer(historyConfig)); @@ -243,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/HistoryConfig.cs b/src/Nethermind/Nethermind.History/HistoryConfig.cs index f1f1d57b607b..e3d1db28f15d 100644 --- a/src/Nethermind/Nethermind.History/HistoryConfig.cs +++ b/src/Nethermind/Nethermind.History/HistoryConfig.cs @@ -6,5 +6,6 @@ namespace Nethermind.History; public class HistoryConfig : IHistoryConfig { public PruningModes Pruning { get; set; } = PruningModes.Disabled; - public long RetentionEpochs { get; set; } = 82125; + public uint RetentionEpochs { get; set; } = 82125; + public uint PruningInterval { get; set; } = 8; } diff --git a/src/Nethermind/Nethermind.History/HistoryPruner.cs b/src/Nethermind/Nethermind.History/HistoryPruner.cs index fecee5f7cd2f..2a8121e65b67 100644 --- a/src/Nethermind/Nethermind.History/HistoryPruner.cs +++ b/src/Nethermind/Nethermind.History/HistoryPruner.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -14,7 +13,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; @@ -29,6 +30,7 @@ public class HistoryPruner : IHistoryPruner { private const int MaxOptimisticSearchAttempts = 3; private const int LockWaitTimeoutMs = 100; + private const int SlotsPerEpoch = 32; // only one pruning and one searching thread at a time private readonly object _pruneLock = new(); @@ -45,6 +47,7 @@ public class HistoryPruner : IHistoryPruner private readonly IHistoryConfig _historyConfig; private readonly bool _enabled; private readonly long _epochLength; + private readonly long _pruningInterval; private readonly long _minHistoryRetentionEpochs; private readonly int _deletionProgressLoggingInterval; private readonly long _ancientBarrier; @@ -54,6 +57,7 @@ public class HistoryPruner : IHistoryPruner private long? _cutoffPointer; private ulong? _cutoffTimestamp; private bool _hasLoadedDeletePointer = false; + private int _currentlyPruning = 0; public event EventHandler? NewOldestBlock; @@ -83,7 +87,8 @@ public HistoryPruner( _backgroundTaskScheduler = backgroundTaskScheduler; _historyConfig = historyConfig; _enabled = historyConfig.Enabled(); - _epochLength = (long)blocksConfig.SecondsPerSlot * 32; // must be changed if slot length changes + _epochLength = (long)blocksConfig.SecondsPerSlot * SlotsPerEpoch; // must be changed if slot length changes + _pruningInterval = historyConfig.PruningInterval * SlotsPerEpoch; _minHistoryRetentionEpochs = specProvider.GenesisSpec.MinHistoryRetentionEpochs; CheckConfig(); @@ -123,25 +128,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) { @@ -155,11 +161,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; @@ -193,7 +199,9 @@ public long? CutoffBlockNumber finally { if (lockTaken) + { Monitor.Exit(_searchLock); + } } return cutoffBlockNumber; @@ -227,7 +235,9 @@ public BlockHeader? OldestBlockHeader finally { if (lockTaken) + { Monitor.Exit(_pruneLock); + } } } @@ -239,38 +249,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); @@ -283,10 +337,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() @@ -336,7 +395,7 @@ private bool ShouldPruneHistory(out ulong? cutoffTimestamp) { cutoffTimestamp = null; - if (!_enabled) + if (!_enabled || !PruningIntervalHasElapsed()) { return false; } @@ -350,15 +409,19 @@ private bool ShouldPruneHistory(out ulong? cutoffTimestamp) return cutoffTimestamp is not null && (_lastPrunedTimestamp is null || cutoffTimestamp > _lastPrunedTimestamp); } + private bool PruningIntervalHasElapsed() + => _pruningInterval == 0 || _blockTree.Head!.Number % _pruningInterval == 0; + 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; @@ -373,7 +436,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; } @@ -390,20 +453,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) @@ -439,7 +503,7 @@ private IEnumerable GetBlocksByNumber(long from, long to, Predicate GetBlocksByNumber(long from, long to, Predicate() - .AddScoped() + .AddScoped() .AddScoped() .AddSingleton() - .AddScoped() + .AddScoped() + .AddScoped() .AddScoped() + .AddSingleton() .AddScoped() .AddScoped() .AddScoped() .AddScoped() .AddScoped() .AddScoped() + .AddScoped() .AddScoped() .AddScoped((rewardSource, txP) => rewardSource.Get(txP)) .AddScoped((specProvider, blocksConfig) => @@ -84,8 +89,6 @@ protected override void Load(ContainerBuilder builder) .AddSingleton(NullSealEngine.Instance) .AddSingleton(NullSealEngine.Instance) .AddSingleton() - - .AddSingleton() .AddSingleton() .AddSingleton((blockTree, specProvider, logManager, blocksConfig) => @@ -104,6 +107,17 @@ protected override void Load(ContainerBuilder builder) .AddScoped() ; + if (blocksConfig.BuildBlocksOnMainState) + { + builder.AddSingleton() + .AddScoped(); + } + else + { + builder.AddSingleton() + .AddScoped(); + } + if (initConfig.ExitOnInvalidBlock) builder.AddStep(typeof(ExitOnInvalidBlock)); } diff --git a/src/Nethermind/Nethermind.Init/Modules/BlockTreeModule.cs b/src/Nethermind/Nethermind.Init/Modules/BlockTreeModule.cs index 76d7b8265175..139dabadad2b 100644 --- a/src/Nethermind/Nethermind.Init/Modules/BlockTreeModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/BlockTreeModule.cs @@ -15,14 +15,16 @@ using Nethermind.Crypto; using Nethermind.Db; using Nethermind.Db.Blooms; +using Nethermind.Db.LogIndex; using Nethermind.Facade.Find; +using Nethermind.Logging; using Nethermind.History; using Nethermind.State.Repositories; using Nethermind.TxPool; namespace Nethermind.Init.Modules; -public class BlockTreeModule(IReceiptConfig receiptConfig) : Autofac.Module +public class BlockTreeModule(IReceiptConfig receiptConfig, ILogIndexConfig logIndexConfig) : Autofac.Module { protected override void Load(ContainerBuilder builder) { @@ -30,22 +32,41 @@ protected override void Load(ContainerBuilder builder) .AddKeyedSingleton(nameof(BloomStorage), CreateBloomStorageFileStoreFactory) .AddSingleton() .AddSingleton() + .AddSingleton(c => c.Resolve()) .AddSingleton() .AddSingleton() .AddSingleton(CreateBadBlockStore) .AddSingleton() .AddSingleton() .AddSingleton((ecdsa, specProvider, receiptConfig) => - new ReceiptsRecovery(ecdsa, specProvider, !receiptConfig.CompactReceiptStore)) + new ReceiptsRecovery(ecdsa, specProvider, !receiptConfig.CompactReceiptStore) + ) .AddSingleton() .AddSingleton() - .AddSingleton() .Bind() - .AddSingleton() - .AddSingleton((bt) => bt.AsReadOnly()) + .AddSingleton((bt) => bt.AsReadOnly()); + + builder.AddSingleton() + .AddDecorator((ctx, config) => + { + IPruningConfig pruningConfig = ctx.Resolve(); + config.MaxReorgDepth ??= pruningConfig.PruningBoundary; + return config; + }); - ; + if (logIndexConfig.Enabled) + { + builder + .AddSingleton() + .AddSingleton(); + } + else + { + builder + .AddSingleton() + .AddSingleton(); + } if (!receiptConfig.StoreReceipts) { diff --git a/src/Nethermind/Nethermind.Init/Modules/BuiltInStepsModule.cs b/src/Nethermind/Nethermind.Init/Modules/BuiltInStepsModule.cs index abde62b70a4e..0b27e6863e92 100644 --- a/src/Nethermind/Nethermind.Init/Modules/BuiltInStepsModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/BuiltInStepsModule.cs @@ -25,7 +25,6 @@ public class BuiltInStepsModule : Module typeof(InitTxTypesAndRlp), typeof(LoadGenesisBlock), typeof(LogHardwareInfo), - typeof(MigrateConfigs), typeof(RegisterPluginRpcModules), typeof(RegisterRpcModules), typeof(ResolveIps), @@ -34,6 +33,7 @@ public class BuiltInStepsModule : Module typeof(StartBlockProcessor), typeof(StartBlockProducer), typeof(StartMonitoring), + typeof(StartLogIndex) ]; protected override void Load(ContainerBuilder builder) diff --git a/src/Nethermind/Nethermind.Init/Modules/DbModule.cs b/src/Nethermind/Nethermind.Init/Modules/DbModule.cs index 14b15978c588..0caa67fe530b 100644 --- a/src/Nethermind/Nethermind.Init/Modules/DbModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/DbModule.cs @@ -50,11 +50,12 @@ protected override void Load(ContainerBuilder builder) return new NoopTunableDb(); }) .AddKeyedAdapter((kv) => kv) - - // 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() + .AddKeyedAdapter((key, kv) => + { + if (kv is not ISortedKeyValueStore sortedKeyValue) + throw new InvalidOperationException($"{nameof(IKeyValueStore)}:{key} is not of type {nameof(ISortedKeyValueStore)}"); + return sortedKeyValue; + }) .AddDatabase(DbNames.State) .AddDatabase(DbNames.Code) @@ -64,13 +65,12 @@ 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) .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 000000000000..74065a806261 --- /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/DiscoveryModule.cs b/src/Nethermind/Nethermind.Init/Modules/DiscoveryModule.cs index 686b85396432..c74af4fec787 100644 --- a/src/Nethermind/Nethermind.Init/Modules/DiscoveryModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/DiscoveryModule.cs @@ -1,13 +1,11 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Linq; using Autofac; using Nethermind.Api; using Nethermind.Core; using Nethermind.Crypto; using Nethermind.Db; -using Nethermind.Init.Steps; using Nethermind.Logging; using Nethermind.Network; using Nethermind.Network.Config; @@ -43,12 +41,13 @@ protected override void Load(ContainerBuilder builder) .AddKeyedSingleton(NodeSourceToDiscV4Feeder.SourceKey, ctx => ctx.Resolve()) // Uses by RPC also. - .AddSingleton((logManager) => new StaticNodesManager(initConfig.StaticNodesPath, logManager)) + .AddSingleton(logManager => + new StaticNodesManager(initConfig.StaticNodesPath.GetApplicationResourcePath(initConfig.DataDir), logManager)) // This load from file. .AddSingleton() - .AddSingleton((logManager) => - new TrustedNodesManager(initConfig.TrustedNodesPath, logManager)) + .AddSingleton(logManager => + new TrustedNodesManager(initConfig.TrustedNodesPath.GetApplicationResourcePath(initConfig.DataDir), logManager)) .Bind() @@ -67,25 +66,14 @@ protected override void Load(ContainerBuilder builder) ChainSpec chainSpec = ctx.Resolve(); IDiscoveryConfig discoveryConfig = ctx.Resolve(); - // Was in `UpdateDiscoveryConfig` step. - if (discoveryConfig.Bootnodes != string.Empty) - { - if (chainSpec.Bootnodes.Length != 0) - { - discoveryConfig.Bootnodes += "," + string.Join(",", chainSpec.Bootnodes.Select(static bn => bn.ToString())); - } - } - else if (chainSpec.Bootnodes is not null) - { - discoveryConfig.Bootnodes = string.Join(",", chainSpec.Bootnodes.Select(static bn => bn.ToString())); - } - if (networkConfig.DiscoveryDns == null) { string chainName = BlockchainIds.GetBlockchainName(chainSpec!.NetworkId).ToLowerInvariant(); networkConfig.DiscoveryDns = $"all.{chainName}.ethdisco.net"; } - networkConfig.Bootnodes = discoveryConfig.Bootnodes; + + networkConfig.Bootnodes = [.. networkConfig.Bootnodes, .. discoveryConfig.Bootnodes, .. chainSpec.Bootnodes]; + return networkConfig; }) @@ -116,6 +104,7 @@ protected override void Load(ContainerBuilder builder) .AddSingleton() .AddNetworkStorage(DbNames.DiscoveryNodes, "discoveryNodes") + .AddNetworkStorage(DbNames.DiscoveryV5Nodes, "discoveryV5Nodes") .AddSingleton() .AddSingleton() diff --git a/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs b/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs index a18287cea127..9133320d1788 100644 --- a/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs +++ b/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs @@ -7,11 +7,9 @@ using Nethermind.Api; using Nethermind.Blockchain; using Nethermind.Blockchain.Receipts; -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.Logging; @@ -24,60 +22,51 @@ 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) { - var 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) - .AddScoped((branchProcessor) => new BlockchainProcessor( - blockTree!, - branchProcessor, - compositeBlockPreprocessorStep, - worldStateManager.GlobalStateReader, - logManager, - new BlockchainProcessor.Options + .AddScoped((branchProcessor, processingStats) => + new BlockchainProcessor( + blockTree, + branchProcessor, + compositeBlockPreprocessorStep, + worldStateManager.GlobalStateReader, + logManager, + new BlockchainProcessor.Options + { + StoreReceiptsByDefault = receiptConfig.StoreReceipts, + DumpOptions = initConfig.AutoDump + }, + processingStats) { - StoreReceiptsByDefault = receiptConfig.StoreReceipts, - DumpOptions = initConfig.AutoDump + IsMainProcessor = true // Manual construction because of this flag }) - { - IsMainProcessor = true // Manual construction because of this flag - }) - + .AddScoped(ctx => ctx.Resolve()) + .AddScoped(ctx => ctx.Resolve()) // 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, preBlockCaches?.PrecompileCache); - }) - ; - } }); _components = innerScope.Resolve(); @@ -90,9 +79,10 @@ public async ValueTask DisposeAsync() await LifetimeScope.DisposeAsync(); } - private Components _components; + private readonly Components _components; public ILifetimeScope LifetimeScope { get; init; } public IBlockchainProcessor BlockchainProcessor => _components.BlockchainProcessor; + public IBlockProcessingQueue BlockProcessingQueue => _components.BlockProcessingQueue; public IWorldState WorldState => _components.WorldState; public IBranchProcessor BranchProcessor => _components.BranchProcessor; public IBlockProcessor BlockProcessor => _components.BlockProcessor; @@ -109,6 +99,7 @@ private record Components( IBranchProcessor BranchProcessor, IBlockProcessor BlockProcessor, IBlockchainProcessor BlockchainProcessor, + IBlockProcessingQueue BlockProcessingQueue, IWorldState WorldState, IGenesisLoader GenesisLoader ); diff --git a/src/Nethermind/Nethermind.Init/Modules/MonitoringModule.cs b/src/Nethermind/Nethermind.Init/Modules/MonitoringModule.cs new file mode 100644 index 000000000000..814ab35de125 --- /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 9bdfb7ee49f7..cd17a2f96dc6 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,11 @@ using Nethermind.Core.Specs; using Nethermind.Core.Timers; using Nethermind.Crypto; -using Nethermind.Db; +using Nethermind.Db.LogIndex; 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 +47,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())) - .AddModule(new BlockTreeModule(configProvider.GetConfig())) + .AddModule(new BlockProcessingModule(configProvider.GetConfig(), configProvider.GetConfig())) + .AddModule(new BlockTreeModule(configProvider.GetConfig(), 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 000000000000..a5131853f84e --- /dev/null +++ b/src/Nethermind/Nethermind.Init/Modules/PrewarmerModule.cs @@ -0,0 +1,67 @@ +// 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.State; +using Nethermind.Trie; + +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); + }); + } + } +} diff --git a/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs b/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs index 420c1fa26b01..3cf04ff24cc0 100644 --- a/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs +++ b/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs @@ -8,12 +8,12 @@ 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; using Nethermind.Facade; using Nethermind.Facade.Eth; +using Nethermind.Facade.Find; using Nethermind.Facade.Simulate; using Nethermind.Init.Steps.Migrations; using Nethermind.JsonRpc; @@ -22,6 +22,7 @@ using Nethermind.JsonRpc.Modules.DebugModule; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.JsonRpc.Modules.Eth.FeeHistory; +using Nethermind.JsonRpc.Modules.LogIndex; using Nethermind.JsonRpc.Modules.Net; using Nethermind.JsonRpc.Modules.Parity; using Nethermind.JsonRpc.Modules.Personal; @@ -31,7 +32,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; @@ -60,6 +60,7 @@ protected override void Load(ContainerBuilder builder) .RegisterSingletonJsonRpcModule() .RegisterSingletonJsonRpcModule() .RegisterSingletonJsonRpcModule() + .RegisterSingletonJsonRpcModule() // Txpool rpc .RegisterSingletonJsonRpcModule() @@ -81,8 +82,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/Modules/WorldStateModule.cs b/src/Nethermind/Nethermind.Init/Modules/WorldStateModule.cs index 0c3b7a310536..957308915c9b 100644 --- a/src/Nethermind/Nethermind.Init/Modules/WorldStateModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/WorldStateModule.cs @@ -6,6 +6,7 @@ using Autofac; using Nethermind.Api; using Nethermind.Api.Steps; +using Nethermind.Blockchain; using Nethermind.Blockchain.Synchronization; using Nethermind.Core; using Nethermind.Db; @@ -15,6 +16,7 @@ using Nethermind.Logging; using Nethermind.State; using Nethermind.Trie; +using Nethermind.Trie.Pruning; namespace Nethermind.Init.Modules; @@ -83,6 +85,8 @@ dbFactory is not MemDbFactory // Prevent multiple concurrent verify trie. .AddSingleton() + + .AddSingleton() ; if (initConfig.DiagnosticMode == DiagnosticMode.VerifyTrie) diff --git a/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs b/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs index 55707205ddb5..5cfab78ac27b 100644 --- a/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs +++ b/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.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; @@ -17,6 +17,7 @@ using Nethermind.Core.Timers; using Nethermind.Db; using Nethermind.Db.FullPruning; +using Nethermind.Db.LogIndex; using Nethermind.Db.Rocks.Config; using Nethermind.Evm.State; using Nethermind.JsonRpc.Modules.Admin; @@ -33,7 +34,6 @@ public class PruningTrieStateFactory( ISyncConfig syncConfig, IInitConfig initConfig, IPruningConfig pruningConfig, - IBlocksConfig blockConfig, IDbProvider dbProvider, IBlockTree blockTree, IFileSystem fileSystem, @@ -45,7 +45,8 @@ public class PruningTrieStateFactory( ChainSpec chainSpec, IDisposableStack disposeStack, Lazy pathRecovery, - ILogManager logManager + ILogManager logManager, + NodeStorageCache? nodeStorageCache = null ) { private readonly ILogger _logger = logManager.GetClassLogger(); @@ -55,39 +56,33 @@ ILogManager logManager CompositePruningTrigger compositePruningTrigger = new CompositePruningTrigger(); 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); IWorldStateManager stateManager = new WorldStateManager( - worldState, + scopeProvider, trieStore, dbProvider, logManager, - new LastNStateRootTracker(blockTree, 128)); + new LastNStateRootTracker(blockTree, syncConfig.SnapServingMaxDepth)); // NOTE: Don't forget this! Very important! TrieStoreBoundaryWatcher trieStoreBoundaryWatcher = new(stateManager, blockTree!, logManager); @@ -102,14 +97,14 @@ ILogManager logManager mainNodeStorage, nodeStorageFactory, trieStore, - compositePruningTrigger, - preBlockCaches + compositePruningTrigger ); - var verifyTrieStarter = new VerifyTrieStarter(stateManager, processExit!, logManager); + VerifyTrieStarter verifyTrieStarter = new(stateManager, processExit!, logManager); ManualPruningTrigger pruningTrigger = new(); compositePruningTrigger.Add(pruningTrigger); - PruningTrieStateAdminRpcModule adminRpcModule = new PruningTrieStateAdminRpcModule( + disposeStack.Push(compositePruningTrigger); + PruningTrieStateAdminRpcModule adminRpcModule = new( pruningTrigger, blockTree, stateManager.GlobalStateReader, @@ -124,8 +119,7 @@ private void InitializeFullPruning(IDb stateDb, INodeStorage mainNodeStorage, INodeStorageFactory nodeStorageFactory, IPruningTrieStore trieStore, - CompositePruningTrigger compositePruningTrigger, - PreBlockCaches? preBlockCaches) + CompositePruningTrigger compositePruningTrigger) { IPruningTrigger? CreateAutomaticTrigger(string dbPath) { @@ -181,7 +175,10 @@ public MainPruningTrieStoreFactory( IPruningConfig pruningConfig, IDbProvider dbProvider, INodeStorageFactory nodeStorageFactory, + IFinalizedStateProvider finalizedStateProvider, + IBlockTree blockTree, IDbConfig dbConfig, + ILogIndexConfig logIndexConfig, IHardwareInfo hardwareInfo, ILogManager logManager ) @@ -190,10 +187,13 @@ ILogManager logManager AdviseConfig(pruningConfig, dbConfig, hardwareInfo); - if (syncConfig.SnapServingEnabled == true && pruningConfig.PruningBoundary < 128) + if (syncConfig.SnapServingEnabled == true && pruningConfig.PruningBoundary < syncConfig.SnapServingMaxDepth) { - if (_logger.IsInfo) _logger.Info($"Snap serving enabled, but {nameof(pruningConfig.PruningBoundary)} is less than 128. Setting to 128."); - pruningConfig.PruningBoundary = 128; + // use PruningBoundary for log-index MaxReorgDepth before it's overwritten + logIndexConfig.MaxReorgDepth ??= pruningConfig.PruningBoundary; + + if (_logger.IsInfo) _logger.Info($"Snap serving enabled, but {nameof(pruningConfig.PruningBoundary)} is less than {syncConfig.SnapServingMaxDepth}. Setting to {syncConfig.SnapServingMaxDepth}."); + pruningConfig.PruningBoundary = syncConfig.SnapServingMaxDepth; } if (pruningConfig.PruningBoundary < 64) @@ -232,10 +232,18 @@ ILogManager logManager INodeStorage mainNodeStorage = nodeStorageFactory.WrapKeyValueStore(stateDb); + if (pruningConfig.SimulateLongFinalizationDepth != 0) + { + // Merge plugin also decorate this, but we want it to be the last decorator for this purpose, so its done + // manually here. + finalizedStateProvider = new DelayedFinalizedStateProvider(finalizedStateProvider, blockTree, pruningConfig.SimulateLongFinalizationDepth); + } + PruningTrieStore = new TrieStore( mainNodeStorage, pruningStrategy, persistenceStrategy, + finalizedStateProvider, pruningConfig, logManager); } @@ -253,7 +261,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. @@ -261,7 +269,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."); } @@ -273,4 +281,38 @@ private void AdviseConfig(IPruningConfig pruningConfig, IDbConfig dbConfig, IHar } public IPruningTrieStore PruningTrieStore { get; } + + // Used to simulate long reorg by delaying `FinalizedBlockNumber` + private class DelayedFinalizedStateProvider( + IFinalizedStateProvider finalizedStateProvider, + IBlockTree blockTree, + int pruningConfigSimulateLongFinalizationDepth + ) : IFinalizedStateProvider + { + private long? _lastFinalizedBlockNumber = null; + + public long FinalizedBlockNumber + { + get + { + long baseFinalizedBlockNumber = finalizedStateProvider.FinalizedBlockNumber; + + // Need to limit by head, otherwise it does not work for forward sync. + long headNumber = blockTree.Head?.Number ?? 0; + baseFinalizedBlockNumber = Math.Min(baseFinalizedBlockNumber, headNumber + pruningConfigSimulateLongFinalizationDepth / 2); + + if (_lastFinalizedBlockNumber is null || baseFinalizedBlockNumber - _lastFinalizedBlockNumber > pruningConfigSimulateLongFinalizationDepth) + { + _lastFinalizedBlockNumber = baseFinalizedBlockNumber; + } + + return _lastFinalizedBlockNumber.Value; + } + } + + public Hash256? GetFinalizedStateRootAt(long blockNumber) + { + return finalizedStateProvider.GetFinalizedStateRootAt(blockNumber); + } + } } diff --git a/src/Nethermind/Nethermind.Init/Steps/ApplyMemoryHint.cs b/src/Nethermind/Nethermind.Init/Steps/ApplyMemoryHint.cs index 6b5fe9025de4..d6f22a89747c 100644 --- a/src/Nethermind/Nethermind.Init/Steps/ApplyMemoryHint.cs +++ b/src/Nethermind/Nethermind.Init/Steps/ApplyMemoryHint.cs @@ -14,7 +14,6 @@ namespace Nethermind.Init.Steps { - [RunnerStepDependencies(typeof(MigrateConfigs))] public sealed class ApplyMemoryHint( IInitConfig initConfig, IDbConfig dbConfig, diff --git a/src/Nethermind/Nethermind.Init/Steps/EthereumStepsLoader.cs b/src/Nethermind/Nethermind.Init/Steps/EthereumStepsLoader.cs index 2ad87b8c327b..56bac33fd579 100644 --- a/src/Nethermind/Nethermind.Init/Steps/EthereumStepsLoader.cs +++ b/src/Nethermind/Nethermind.Init/Steps/EthereumStepsLoader.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using Nethermind.Api; using Nethermind.Api.Extensions; using Nethermind.Api.Steps; diff --git a/src/Nethermind/Nethermind.Init/Steps/EthereumStepsManager.cs b/src/Nethermind/Nethermind.Init/Steps/EthereumStepsManager.cs index a96786ba0943..2a12b6221096 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 41518e7914b0..1256f090c0e6 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/IEthereumStepsLoader.cs b/src/Nethermind/Nethermind.Init/Steps/IEthereumStepsLoader.cs index c456250ea3c3..bf57bfb63d54 100644 --- a/src/Nethermind/Nethermind.Init/Steps/IEthereumStepsLoader.cs +++ b/src/Nethermind/Nethermind.Init/Steps/IEthereumStepsLoader.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using Nethermind.Api.Steps; diff --git a/src/Nethermind/Nethermind.Init/Steps/InitTxTypesAndRlp.cs b/src/Nethermind/Nethermind.Init/Steps/InitTxTypesAndRlp.cs index d2ac52412ff8..e064c6820b69 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitTxTypesAndRlp.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitTxTypesAndRlp.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Reflection; using System.Threading; using System.Threading.Tasks; diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockProducer.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockProducer.cs index 9d571bfef5c7..0125f9a5deea 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockProducer.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockProducer.cs @@ -9,7 +9,6 @@ using Nethermind.Api.Extensions; using Nethermind.Api.Steps; using Nethermind.Consensus; -using Nethermind.Consensus.Transactions; using Nethermind.Core.ServiceStopper; namespace Nethermind.Init.Steps diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockTree.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockTree.cs index bcacdc03ca22..7851c1edb9cf 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockTree.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockTree.cs @@ -1,27 +1,16 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.IO; using System.Threading; using System.Threading.Tasks; using Nethermind.Api; using Nethermind.Api.Steps; using Nethermind.Blockchain; -using Nethermind.Blockchain.Blocks; -using Nethermind.Blockchain.Headers; -using Nethermind.Blockchain.Receipts; -using Nethermind.Blockchain.Synchronization; using Nethermind.Consensus; -using Nethermind.Core; -using Nethermind.Db; -using Nethermind.Db.Blooms; -using Nethermind.Facade.Find; -using Nethermind.Serialization.Rlp; -using Nethermind.State.Repositories; namespace Nethermind.Init.Steps { - [RunnerStepDependencies(typeof(InitTxTypesAndRlp), typeof(InitDatabase), typeof(MigrateConfigs), typeof(SetupKeyStore))] + [RunnerStepDependencies(typeof(InitTxTypesAndRlp), typeof(InitDatabase), typeof(SetupKeyStore))] public class InitializeBlockTree : IStep { private readonly IBasicApi _get; diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs index 0b97300a5e13..8e866c3ac38a 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,7 +17,7 @@ using Nethermind.Consensus.Scheduler; using Nethermind.Core; using Nethermind.Core.Attributes; -using Nethermind.State; +using Nethermind.Logging; using Nethermind.TxPool; using Nethermind.Wallet; @@ -53,8 +52,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/InitializeNetwork.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs index 191592ca38bc..32f65f11c8f7 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.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; @@ -16,7 +16,6 @@ using Nethermind.Network.Config; using Nethermind.Network.Contract.P2P; using Nethermind.Network.Discovery; -using Nethermind.Network.P2P.Subprotocols.Eth; using Nethermind.Stats; using Nethermind.Stats.Model; using Nethermind.Synchronization; @@ -275,14 +274,12 @@ private async Task InitPeer() _api.PeerManager!, _networkConfig, _api.LogManager); - PooledTxsRequestor pooledTxsRequestor = new(_api.TxPool!, _api.Config(), _api.SpecProvider); _api.ProtocolsManager = new ProtocolsManager( _api.SyncPeerPool!, syncServer, _api.BackgroundTaskScheduler, _api.TxPool, - pooledTxsRequestor, _discoveryApp, _api.MessageSerializationService, _api.RlpxPeer, @@ -293,6 +290,8 @@ private async Task InitPeer() _api.GossipPolicy, _api.WorldStateManager!, _api.LogManager, + _api.Config(), + _api.SpecProvider, _api.TxGossipPolicy); if (_syncConfig.SnapServingEnabled == true) diff --git a/src/Nethermind/Nethermind.Init/Steps/MigrateConfigs.cs b/src/Nethermind/Nethermind.Init/Steps/MigrateConfigs.cs deleted file mode 100644 index 5ed082d948db..000000000000 --- a/src/Nethermind/Nethermind.Init/Steps/MigrateConfigs.cs +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Nethermind.Api; -using Nethermind.Api.Steps; -using Nethermind.Blockchain.Receipts; -using Nethermind.Config; -using Nethermind.Consensus; -using Nethermind.Core.Exceptions; - -namespace Nethermind.Init.Steps -{ - public class MigrateConfigs : IStep - { - private readonly INethermindApi _api; - - public MigrateConfigs(INethermindApi api) - { - _api = api; - } - - public Task Execute(CancellationToken cancellationToken) - { - IMiningConfig miningConfig = _api.Config(); - IReceiptConfig receiptConfig = _api.Config(); - - MigrateInitConfig(miningConfig, receiptConfig); - - var blocksConfig = miningConfig.BlocksConfig; - var value = _api.Config(); - MigrateBlocksConfig(blocksConfig, value); - - return Task.CompletedTask; - } - - //This function is marked publick and static for use in tests - public static void MigrateBlocksConfig(IBlocksConfig? blocksConfig, IBlocksConfig? value) - { - PropertyInfo[]? propertyInfos = blocksConfig?.GetType().GetInterface($"{nameof(IBlocksConfig)}")?.GetProperties(); - - //Loop over config properties checking mismaches and changing defaults - //So that on given and current inner configs we would only have same values - if (propertyInfos is null) return; - - foreach (PropertyInfo? propertyInfo in propertyInfos) - { - ConfigItemAttribute? attribute = propertyInfo.GetCustomAttribute(); - string expectedDefaultValue = attribute?.DefaultValue.Trim('"') ?? ""; - if (propertyInfo.Name == nameof(IBlocksConfig.ExtraData)) - { - expectedDefaultValue = BlocksConfig.DefaultExtraData; - } - - object? valA = propertyInfo.GetValue(blocksConfig); - object? valB = propertyInfo.GetValue(value); - - string valAasStr = (valA?.ToString() ?? "null"); - string valBasStr = (valB?.ToString() ?? "null"); - - if (valBasStr != valAasStr) - { - if (valAasStr == expectedDefaultValue) - { - propertyInfo.SetValue(blocksConfig, valB); - } - else if (valBasStr == expectedDefaultValue) - { - propertyInfo.SetValue(value, valA); - } - else - { - throw new InvalidConfigurationException($"Configuration mismatch at {propertyInfo.Name} " + - $"with conflicting values {valA} and {valB}", - ExitCodes.ConflictingConfigurations); - } - } - } - } - - private void MigrateInitConfig(IMiningConfig miningConfig, IReceiptConfig receiptConfig) - { - if (_api.Config().IsMining) - { - miningConfig.Enabled = true; - } - if (!_api.Config().StoreReceipts) - { - receiptConfig.StoreReceipts = false; - } - if (_api.Config().ReceiptsMigration) - { - receiptConfig.ReceiptsMigration = true; - } - } - } -} diff --git a/src/Nethermind/Nethermind.Init/Steps/Migrations/ReceiptMigration.cs b/src/Nethermind/Nethermind.Init/Steps/Migrations/ReceiptMigration.cs index 169c2f2daa52..e33cbbff524a 100644 --- a/src/Nethermind/Nethermind.Init/Steps/Migrations/ReceiptMigration.cs +++ b/src/Nethermind/Nethermind.Init/Steps/Migrations/ReceiptMigration.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; diff --git a/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs b/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs index ed451c7272a9..bb808aad08dd 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/SetupKeyStore.cs b/src/Nethermind/Nethermind.Init/Steps/SetupKeyStore.cs index 243cdd5fde9c..7b83a3eb77f9 100644 --- a/src/Nethermind/Nethermind.Init/Steps/SetupKeyStore.cs +++ b/src/Nethermind/Nethermind.Init/Steps/SetupKeyStore.cs @@ -1,7 +1,6 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; @@ -14,69 +13,64 @@ using Nethermind.KeyStore.Config; using Nethermind.Network.Config; using Nethermind.Wallet; +using System.Linq; namespace Nethermind.Init.Steps { [RunnerStepDependencies(typeof(ResolveIps))] - public class SetupKeyStore : IStep + public class SetupKeyStore(INethermindApi api) : IStep { - private readonly IApiWithBlockchain _api; - - public SetupKeyStore(INethermindApi api) + public Task Execute(CancellationToken cancellationToken) { - _api = api; - } + cancellationToken.ThrowIfCancellationRequested(); - public async Task Execute(CancellationToken cancellationToken) - { - (IApiWithStores get, IApiWithBlockchain set) = _api.ForInit; - // why is the await Task.Run here? - await Task.Run(() => - { - IKeyStoreConfig keyStoreConfig = get.Config(); - INetworkConfig networkConfig = get.Config(); + (IApiWithStores get, IApiWithBlockchain set) = api.ForInit; + + IKeyStoreConfig keyStoreConfig = get.Config(); + INetworkConfig networkConfig = get.Config(); - AesEncrypter encrypter = new(keyStoreConfig, get.LogManager); + AesEncrypter encrypter = new(keyStoreConfig, get.LogManager); - IKeyStore? keyStore = set.KeyStore = new FileKeyStore( - keyStoreConfig, - get.EthereumJsonSerializer, - encrypter, - get.CryptoRandom, - get.LogManager, - new PrivateKeyStoreIOSettingsProvider(keyStoreConfig)); + IKeyStore? keyStore = set.KeyStore = new FileKeyStore( + keyStoreConfig, + get.EthereumJsonSerializer, + encrypter, + get.CryptoRandom, + get.LogManager, + new PrivateKeyStoreIOSettingsProvider(keyStoreConfig)); - set.Wallet = get.Config() switch - { - { EnableUnsecuredDevWallet: true, KeepDevWalletInMemory: true } => new DevWallet(get.Config(), get.LogManager), - { EnableUnsecuredDevWallet: true, KeepDevWalletInMemory: false } => new DevKeyStoreWallet(get.KeyStore, get.LogManager), - _ => new ProtectedKeyStoreWallet(keyStore, new ProtectedPrivateKeyFactory(get.CryptoRandom, get.Timestamper, keyStoreConfig.KeyStoreDirectory), - get.Timestamper, get.LogManager), - }; + set.Wallet = get.Config() switch + { + { EnableUnsecuredDevWallet: true, KeepDevWalletInMemory: true } => new DevWallet(get.Config(), get.LogManager), + { EnableUnsecuredDevWallet: true, KeepDevWalletInMemory: false } => new DevKeyStoreWallet(get.KeyStore, get.LogManager), + _ => new ProtectedKeyStoreWallet(keyStore, new ProtectedPrivateKeyFactory(get.CryptoRandom, get.Timestamper, keyStoreConfig.KeyStoreDirectory), + get.Timestamper, get.LogManager), + }; - new AccountUnlocker(keyStoreConfig, get.Wallet, get.LogManager, new KeyStorePasswordProvider(keyStoreConfig)) - .UnlockAccounts(); + new AccountUnlocker(keyStoreConfig, get.Wallet, get.LogManager, new KeyStorePasswordProvider(keyStoreConfig)) + .UnlockAccounts(); - BasePasswordProvider passwordProvider = new KeyStorePasswordProvider(keyStoreConfig) - .OrReadFromConsole($"Provide password for validator account {keyStoreConfig.BlockAuthorAccount}"); + BasePasswordProvider passwordProvider = new KeyStorePasswordProvider(keyStoreConfig) + .OrReadFromConsole($"Provide password for validator account {keyStoreConfig.BlockAuthorAccount}"); - INodeKeyManager nodeKeyManager = new NodeKeyManager(get.CryptoRandom, get.KeyStore, keyStoreConfig, get.LogManager, passwordProvider, get.FileSystem); - IProtectedPrivateKey? nodeKey = set.NodeKey = nodeKeyManager.LoadNodeKey(); + INodeKeyManager nodeKeyManager = new NodeKeyManager(get.CryptoRandom, get.KeyStore, keyStoreConfig, get.LogManager, passwordProvider, get.FileSystem); + IProtectedPrivateKey? nodeKey = set.NodeKey = nodeKeyManager.LoadNodeKey(); + + IMiningConfig miningConfig = get.Config(); + //Don't load the local key if an external signer is configured + if (string.IsNullOrEmpty(miningConfig.Signer)) + { + set.OriginalSignerKey = nodeKeyManager.LoadSignerKey(); + } - IMiningConfig miningConfig = get.Config(); - //Don't load the local key if an external signer is configured - if (string.IsNullOrEmpty(miningConfig.Signer)) - { - set.OriginalSignerKey = nodeKeyManager.LoadSignerKey(); - } + IPAddress ipAddress = networkConfig.ExternalIp is not null ? IPAddress.Parse(networkConfig.ExternalIp) : IPAddress.Loopback; + IEnode enode = set.Enode = new Enode(nodeKey.PublicKey, ipAddress, networkConfig.P2PPort); - IPAddress ipAddress = networkConfig.ExternalIp is not null ? IPAddress.Parse(networkConfig.ExternalIp) : IPAddress.Loopback; - IEnode enode = set.Enode = new Enode(nodeKey.PublicKey, ipAddress, networkConfig.P2PPort); + get.LogManager.SetGlobalVariable("enode", enode.ToString()); - get.LogManager.SetGlobalVariable("enode", enode.ToString()); + networkConfig.Bootnodes = [.. networkConfig.Bootnodes.Where(bn => bn.NodeId != nodeKey.PublicKey)]; - _api.ChainSpec.Bootnodes = _api.ChainSpec.Bootnodes?.Where(n => !n.NodeId?.Equals(nodeKey.PublicKey) ?? false).ToArray() ?? []; - }, cancellationToken); + return Task.CompletedTask; } } } diff --git a/src/Nethermind/Nethermind.Init/Steps/StartBlockProcessor.cs b/src/Nethermind/Nethermind.Init/Steps/StartBlockProcessor.cs index ddfafbf1b797..399c21676f6b 100644 --- a/src/Nethermind/Nethermind.Init/Steps/StartBlockProcessor.cs +++ b/src/Nethermind/Nethermind.Init/Steps/StartBlockProcessor.cs @@ -3,7 +3,6 @@ using System.Threading; using System.Threading.Tasks; -using Nethermind.Api; using Nethermind.Api.Steps; using Nethermind.Consensus.Processing; diff --git a/src/Nethermind/Nethermind.Init/Steps/StartBlockProducer.cs b/src/Nethermind/Nethermind.Init/Steps/StartBlockProducer.cs index 4dfbe0dd5aa6..b07ed09a9244 100644 --- a/src/Nethermind/Nethermind.Init/Steps/StartBlockProducer.cs +++ b/src/Nethermind/Nethermind.Init/Steps/StartBlockProducer.cs @@ -1,12 +1,13 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Threading; -using System.Threading.Tasks; +using Autofac; using Nethermind.Api; using Nethermind.Api.Steps; using Nethermind.Consensus.Producers; using Nethermind.Logging; +using System.Threading; +using System.Threading.Tasks; namespace Nethermind.Init.Steps { @@ -30,8 +31,7 @@ public Task Execute(CancellationToken _) ILogger logger = _api.LogManager.GetClassLogger(); if (logger.IsInfo) logger.Info($"Starting {_api.SealEngineType} block producer & sealer"); - ProducedBlockSuggester suggester = new(_api.BlockTree, _api.BlockProducerRunner); - _api.DisposeStack.Push(suggester); + _api.Context.ResolveOptional(); _api.BlockProducerRunner.Start(); } diff --git a/src/Nethermind/Nethermind.Init/Steps/StartLogIndex.cs b/src/Nethermind/Nethermind.Init/Steps/StartLogIndex.cs new file mode 100644 index 000000000000..ef329611cf29 --- /dev/null +++ b/src/Nethermind/Nethermind.Init/Steps/StartLogIndex.cs @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Api; +using Nethermind.Api.Steps; +using Nethermind.Core.ServiceStopper; +using Nethermind.Db; +using Nethermind.Db.LogIndex; +using Nethermind.Facade.Find; + +namespace Nethermind.Init.Steps +{ + [RunnerStepDependencies(typeof(InitDatabase), typeof(StartBlockProcessor))] + public class StartLogIndex(IBasicApi api, ILogIndexBuilder logIndexBuilder) : IStep + { + public Task Execute(CancellationToken cancellationToken) + { + if (api.Config().Enabled) + _ = logIndexBuilder.StartAsync(); + + return Task.CompletedTask; + } + + public bool MustInitialize => false; + } +} diff --git a/src/Nethermind/Nethermind.Init/Steps/StartMonitoring.cs b/src/Nethermind/Nethermind.Init/Steps/StartMonitoring.cs index 9f4026090f61..a3ac17f3ea7a 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 31e5bf34e9af..7544c8a272bf 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs @@ -3,7 +3,6 @@ using Autofac; using BenchmarkDotNet.Attributes; -using Nethermind.Api; using Nethermind.Blockchain; using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; @@ -26,6 +25,7 @@ using Nethermind.Wallet; using Nethermind.Config; using Nethermind.Core.Test.Modules; +using Nethermind.Db.LogIndex; using Nethermind.Network; namespace Nethermind.JsonRpc.Benchmark @@ -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); @@ -81,6 +81,7 @@ public void GlobalSetup() feeHistoryOracle, _container.Resolve(), _container.Resolve(), + new LogIndexConfig(), new BlocksConfig().SecondsPerSlot); } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/ByteCodeBuilderExtensions.cs b/src/Nethermind/Nethermind.JsonRpc.Test/ByteCodeBuilderExtensions.cs new file mode 100644 index 000000000000..1fa702fb19b4 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc.Test/ByteCodeBuilderExtensions.cs @@ -0,0 +1,75 @@ +using System.Text; +using Nethermind.Core.Extensions; +using Nethermind.Evm; + +namespace Nethermind.JsonRpc.Test; + +public static class ByteCodeBuilderExtensions +{ + public static Prepare RevertWithError(this Prepare prepare, string errorMessage) + { + byte[] errorBytes = Encoding.UTF8.GetBytes(errorMessage); + + // Mimic the hive callme.eas %revert macro: + // https://github.com/ethereum/hive/blob/master/cmd/hivechain/contracts/callme.eas + // It stores the string at memory 0, then reverts with offset=(32-length), length=length + int length = errorBytes.Length; + int offset = 32 - length; + + prepare + .PushData(errorBytes) // Push the string + .PushData(0) // Memory offset 0 + .Op(Instruction.MSTORE) // Store in memory + .PushData(length) // Length of string + .PushData(offset) // Offset = 32 - length (right-aligned) + .Op(Instruction.REVERT); // Revert + + return prepare; + } + + public static Prepare RevertWithSolidityErrorEncoding(this Prepare prepare, string errorMessage) + { + // based on https://docs.soliditylang.org/en/latest/control-structures.html#revert + // 0x08c379a0 // Function selector for Error(string) + // 0x0000000000000000000000000000000000000000000000000000000000000020 // Data offset + // 0x000000000000000000000000000000000000000000000000000000000000001a // String length + // 0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // String data + + byte[] errorMessageBytes = Encoding.UTF8.GetBytes(errorMessage); + byte[] errorSelector = Bytes.FromHexString("0x08c379a0"); // Error(string) selector + int paddedStringLength = ((errorMessageBytes.Length + 31) / 32) * 32; + int totalLength = 4 + 32 + 32 + paddedStringLength; + + prepare + // Build the first 32 bytes: selector (4 bytes) + start of offset (28 bytes) + // We want: 0x08c379a0 followed by 28 zero bytes + // Need to shift left: 0x08c379a0 << 224 bits = 0x08c379a0000...000 + .PushData(errorSelector) + .PushData(224) // 28 bytes * 8 bits = 224 bits + .Op(Instruction.SHL) // Shift left to position selector at bytes 0-3 + .PushData(0) + .Op(Instruction.MSTORE) + + // Store offset to string data (32) at memory offset 4 + // This will occupy bytes 4-35 + .PushData(32) + .PushData(4) + .Op(Instruction.MSTORE) + + // Store string length at memory offset 36 + // This will occupy bytes 36-67 + .PushData(errorMessageBytes.Length) + .PushData(36) + .Op(Instruction.MSTORE) + + // Store actual string data at memory offset 68 + .StoreDataInMemory(68, errorMessageBytes) + + // REVERT(offset=0, length=totalLength) + .PushData(totalLength) + .PushData(0) + .Op(Instruction.REVERT); + + return prepare; + } +} diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.ReceiptsJsonRpcDataSource.cs b/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.ReceiptsJsonRpcDataSource.cs index 107c7b4f2f62..46318f1ac3cd 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 5c166c2c636f..e72256dada9c 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/BlockParameterConverterTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Data/BlockParameterConverterTests.cs index 7629c2faa71b..a64706ada04f 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Data/BlockParameterConverterTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Data/BlockParameterConverterTests.cs @@ -1,10 +1,12 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using FluentAssertions; using Nethermind.Blockchain.Find; +using Nethermind.Core.Crypto; using Nethermind.Core.Test.Builders; using Nethermind.Serialization.Json; - using NUnit.Framework; namespace Nethermind.JsonRpc.Test.Data @@ -20,6 +22,7 @@ public class BlockParameterConverterTests : SerializationTestBase [TestCase("\"0xa\"", 10)] [TestCase("\"0\"", 0)] [TestCase("\"100\"", 100)] + [TestCase("{ \"blockNumber\": \"0xa\" }", 10)] public void Can_read_block_number(string input, long output) { IJsonSerializer serializer = new EthereumJsonSerializer(); @@ -29,6 +32,37 @@ public void Can_read_block_number(string input, long output) Assert.That(blockParameter.BlockNumber, Is.EqualTo(output)); } + [TestCase("0", true)] + [TestCase("100", true)] + [TestCase("\"0x\"", false)] + [TestCase("\"0x0\"", false)] + [TestCase("\"0xA\"", false)] + [TestCase("\"0xa\"", false)] + [TestCase("\"0\"", true)] + [TestCase("\"100\"", true)] + [TestCase("{ \"blockNumber\": \"0xa\" }", false)] + [TestCase("{ \"blockNumber\": \"100\" }", true)] + public void Cant_read_block_number_when_strict_hex_format_is_enabled(string input, bool throws) + { + bool original = EthereumJsonSerializer.StrictHexFormat; + try + { + EthereumJsonSerializer.StrictHexFormat = true; + IJsonSerializer serializer = new EthereumJsonSerializer(); + + Func action = () => serializer.Deserialize(input); + + if (throws) + action.Should().Throw(); + else + action.Should().NotThrow(); + } + finally + { + EthereumJsonSerializer.StrictHexFormat = original; + } + } + [TestCase("null", BlockParameterType.Latest)] [TestCase("\"\"", BlockParameterType.Latest)] [TestCase("\"latest\"", BlockParameterType.Latest)] @@ -41,6 +75,17 @@ public void Can_read_block_number(string input, long output) [TestCase("\"Finalized\"", BlockParameterType.Finalized)] [TestCase("\"safe\"", BlockParameterType.Safe)] [TestCase("\"Safe\"", BlockParameterType.Safe)] + [TestCase("{ \"blockNumber\": \"\" }", BlockParameterType.Latest)] + [TestCase("{ \"blockNumber\": \"latest\" }", BlockParameterType.Latest)] + [TestCase("{ \"blockNumber\": \"LATEst\" }", BlockParameterType.Latest)] + [TestCase("{ \"blockNumber\": \"earliest\" }", BlockParameterType.Earliest)] + [TestCase("{ \"blockNumber\": \"EaRlIEST\" }", BlockParameterType.Earliest)] + [TestCase("{ \"blockNumber\": \"pending\" }", BlockParameterType.Pending)] + [TestCase("{ \"blockNumber\": \"PeNdInG\" }", BlockParameterType.Pending)] + [TestCase("{ \"blockNumber\": \"finalized\" }", BlockParameterType.Finalized)] + [TestCase("{ \"blockNumber\": \"Finalized\" }", BlockParameterType.Finalized)] + [TestCase("{ \"blockNumber\": \"safe\" }", BlockParameterType.Safe)] + [TestCase("{ \"blockNumber\": \"Safe\" }", BlockParameterType.Safe)] public void Can_read_type(string input, BlockParameterType output) { IJsonSerializer serializer = new EthereumJsonSerializer(); @@ -50,6 +95,19 @@ public void Can_read_type(string input, BlockParameterType output) Assert.That(blockParameter.Type, Is.EqualTo(output)); } + [TestCase("\"0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3\"", "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", false)] + [TestCase("{ \"blockHash\": \"0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3\" }", "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", false)] + [TestCase("{ \"blockHash\": \"0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3\", \"requireCanonical\": true }", "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", true)] + public void Can_read_block_hash(string input, string output, bool requireCanonical) + { + IJsonSerializer serializer = new EthereumJsonSerializer(); + + BlockParameter blockParameter = serializer.Deserialize(input)!; + + Assert.That(blockParameter.BlockHash, Is.EqualTo(new Hash256(output))); + Assert.That(blockParameter.RequireCanonical, Is.EqualTo(requireCanonical)); + } + [TestCase("\"latest\"", BlockParameterType.Latest)] [TestCase("\"earliest\"", BlockParameterType.Earliest)] [TestCase("\"pending\"", BlockParameterType.Pending)] diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Data/Eip2930Tests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Data/Eip2930Tests.cs index dc248851c81e..17de50234f88 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Data/Eip2930Tests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Data/Eip2930Tests.cs @@ -98,7 +98,7 @@ public void can_serialize_null_accessList_to_empty_array(TxType txType) [Test] public void can_deserialize_null_accessList() { - string json = """{"nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":null,"accessList":null}"""; + string json = """{"nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"to":null,"value":"0x0","gas":"0x0","input":null,"accessList":null}"""; var transactionForRpc = _serializer.Deserialize(json); @@ -109,11 +109,13 @@ public void can_deserialize_null_accessList() [Test] public void can_deserialize_no_accessList() { - string json = """{"nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":null}"""; + string json = """{"nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x00"}"""; TransactionForRpc transactionForRpc = _serializer.Deserialize(json); - var transaction = transactionForRpc.ToTransaction(); + Result txResult = transactionForRpc.ToTransaction(); + txResult.ResultType.Should().Be(ResultType.Success); + Transaction transaction = (Transaction)txResult; transaction.Type.Should().Be(TxType.Legacy); transaction.AccessList.Should().BeNull(); } @@ -138,7 +140,7 @@ public void can_serialize_empty_accessList(TxType txType, string txJson) [Test] public void can_deserialize_empty_accessList() { - string json = """{"nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":null,"accessList":[]}"""; + string json = """{"nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"to":null,"value":"0x0","gas":"0x0","input":null,"accessList":[]}"""; TransactionForRpc transactionForRpc = _serializer.Deserialize(json); transactionForRpc.Type.Should().Be(TxType.AccessList); @@ -172,7 +174,7 @@ public void can_serialize_accessList_with_empty_storageKeys(TxType txType, strin [Test] public void can_deserialize_accessList_with_empty_storageKeys() { - string json = """{"nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":null,"accessList":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","storageKeys":[]}]}"""; + string json = """{"nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"to":null,"value":"0x0","gas":"0x0","input":null,"accessList":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","storageKeys":[]}]}"""; TransactionForRpc transactionForRpc = _serializer.Deserialize(json); transactionForRpc.Type.Should().Be(TxType.AccessList); @@ -185,7 +187,7 @@ public void can_deserialize_accessList_with_empty_storageKeys() [Test] public void can_deserialize_accessList_with_null_storageKeys() { - string json = """{"nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":null,"accessList":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099"}]}"""; + string json = """{"nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"to":null,"value":"0x0","gas":"0x0","input":null,"accessList":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099"}]}"""; TransactionForRpc transactionForRpc = _serializer.Deserialize(json); transactionForRpc.Type.Should().Be(TxType.AccessList); @@ -229,11 +231,14 @@ public void can_convert_fromTransaction_toTransactionForRpc_and_back(TxType txTy AccessList = GetTestAccessList(), ChainId = BlockchainIds.Mainnet, SenderAddress = Address.SystemUser, + To = TestItem.AddressA, Data = Memory.Empty, }; TransactionForRpc transactionForRpc = TransactionForRpc.FromTransaction(transaction); - Transaction afterConversion = transactionForRpc.ToTransaction(); + Result txResult = transactionForRpc.ToTransaction(); + txResult.ResultType.Should().Be(ResultType.Success); + Transaction afterConversion = (Transaction)txResult; afterConversion.Should().BeEquivalentTo(transaction, static option => option.ComparingByMembers().Excluding(static tx => tx.Data)); afterConversion.Data.AsArray().Should().BeEquivalentTo(transaction.Data.AsArray()); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Data/IdConverterTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Data/IdConverterTests.cs index f447c6cd77b1..6f5a8238378a 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 1b49f26d5e44..6e4cb9e72968 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Data/TransactionForRpcDeserializationTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Data/TransactionForRpcDeserializationTests.cs @@ -18,7 +18,8 @@ public class TransactionForRpcDeserializationTests public TxType Test_TxTypeIsDetected_ForDifferentFieldSet(string txJson) { TransactionForRpc transactionForRpc = _serializer.Deserialize(txJson); - return transactionForRpc.ToTransaction().Type; + Result result = transactionForRpc.ToTransaction(); + return result.Data?.Type ?? transactionForRpc.Type ?? TxType.Legacy; } [Test] @@ -31,15 +32,18 @@ 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}"""); yield return Make(TxType.Legacy, """{"nonce":"0x0","to":null,"gasPrice":"0x0","gas":"0x0","input":null}"""); - yield return Make(TxType.Legacy, """{"nonce":"0x0","input":null}"""); - yield return Make(TxType.Legacy, """{}"""); - yield return Make(TxType.Legacy, """{"type":null}"""); - yield return Make(TxType.Legacy, """{"additionalField":""}"""); + yield return Make(TxType.EIP1559, """{"nonce":"0x0","input":null}"""); + yield return Make(TxType.EIP1559, """{}"""); + yield return Make(TxType.EIP1559, """{"type":null}"""); + yield return Make(TxType.EIP1559, """{"additionalField":""}"""); + yield return Make(TxType.EIP1559, """{"MaxFeePerBlobGas":"0x0"}"""); + yield return Make(TxType.Legacy, + """{"nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"to":null,"value":"0x0","gasPrice":"0x1","gas":"0x0","input":null,"maxPriorityFeePerGas":"0x1"}"""); yield return Make(TxType.AccessList, """{"type":null,"accessList":[]}"""); yield return Make(TxType.AccessList, """{"nonce":"0x0","to":null,"value":"0x0","accessList":[]}"""); @@ -55,11 +59,10 @@ public static IEnumerable TxJsonTestCases yield return Make(TxType.EIP1559, """{"maxFeePerGas":"0x0"}"""); yield return Make(TxType.EIP1559, """{"maxPriorityFeePerGas":"0x0"}"""); yield return Make(TxType.EIP1559, """{"MaxPriorityFeePerGas":"0x0"}"""); + yield return Make(TxType.EIP1559, """{"nonce":"0x0","to":null,"value":"0x0","maxPriorityFeePerGas":"0x0", "maxFeePerGas":"0x0","maxFeePerBlobGas":"0x0"}"""); - yield return Make(TxType.Blob, """{"nonce":"0x0","to":null,"value":"0x0","accessList":[],"maxFeePerBlobGas":"0x0"}"""); - yield return Make(TxType.Blob, """{"nonce":"0x0","to":null,"value":"0x0","maxPriorityFeePerGas":"0x0", "maxFeePerGas":"0x0","maxFeePerBlobGas":"0x0"}"""); + yield return Make(TxType.Blob, """{"nonce":"0x0","to":null,"value":"0x0","accessList":[],"blobVersionedHashes":[]}"""); yield return Make(TxType.Blob, """{"maxFeePerBlobGas":"0x0", "blobVersionedHashes":[]}"""); - yield return Make(TxType.Blob, """{"MaxFeePerBlobGas":"0x0"}"""); yield return Make(TxType.Blob, """{"blobVersionedHashes":[]}"""); yield return Make(TxType.Blob, """{"BlobVersionedHashes":null}"""); yield return Make(TxType.Blob, """{"blobVersionedHashes":["0x01f1872d656b7a820d763e6001728b9b883f829b922089ec6ad7f5f1665470dc"]}"""); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Eip1186/ProofConverterTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Eip1186/ProofConverterTests.cs index b720c8ab7bbd..770b55b4462e 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Eip1186/ProofConverterTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Eip1186/ProofConverterTests.cs @@ -50,7 +50,7 @@ public void Storage_proofs_have_values_set_complex_3_setup() tree.Accept(accountProofCollector, tree.RootHash); AccountProof proof = accountProofCollector.BuildResult(); - TestToJson(proof, "{\"accountProof\":[\"0xe215a08c9a7cdf08d4425c138ef5ba3d1f6c2ae18786fe88d9a56230a00b3e83367b25\",\"0xf8518080808080a017934c1c90ce30ca48f32b4f449dab308cfba803fd6d142cab01eb1fad1b70038080808080808080a02352504a0cd6095829b18bae394d0c882d84eead7be5b6ad0a87daaff9d2fb4a8080\",\"0xf869a020227dead52ea912e013e7641ccd6b3b174498e55066b0c174a09c8c3cc4bf5eb846f8448001a0b2375a34ff2c8037d9ff04ebc16367b51d156d9a905ca54cef50bfad1a4c0711a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470\"],\"address\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"balance\":\"0x1\",\"codeHash\":\"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470\",\"nonce\":\"0x0\",\"storageHash\":\"0xb2375a34ff2c8037d9ff04ebc16367b51d156d9a905ca54cef50bfad1a4c0711\",\"storageProof\":[{\"key\":\"0x000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaa\",\"proof\":[\"0xf8918080a02474007b0486d5e951fe3fbcdae3e63cadf9c85cb8f178d3c7ca972e2d77705a808080808080a059c4fa21a1e4c40dc3c87e925befbb78a5b6f729865a12f6f0490d9801bcbf22a06b3576bbd6c91ca7128f69f728f3e30bf8980c6381430a5e80186d0dfec89d4e8080a098cfc3bf071c19a2e230165f4152bb98a5d1ab0fee47c952de65da85fcbdfdb2808080\",\"0xf85180a0aec9a5fc3ba2ebedf137fbcf6987b303c9d8718f75253e6e2444b81a4049e5b980808080808080808080a0397f22cb0ad24543caffaad031e3a0a538e5d8ac106d8f6858a703d442c0e4d380808080\",\"0xf84ca02046d62176084b9d1eace1c8bcc2353228d10569a500ccfd1bdbd8c093f4b4e9aaa9ab12000000000000000000000000000000000000000000000000000000000000000000000000000000\"],\"value\":\"0xab12000000000000000000000000000000000000000000000000000000000000000000000000000000\"},{\"key\":\"0x0000000000000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"proof\":[\"0xf8918080a02474007b0486d5e951fe3fbcdae3e63cadf9c85cb8f178d3c7ca972e2d77705a808080808080a059c4fa21a1e4c40dc3c87e925befbb78a5b6f729865a12f6f0490d9801bcbf22a06b3576bbd6c91ca7128f69f728f3e30bf8980c6381430a5e80186d0dfec89d4e8080a098cfc3bf071c19a2e230165f4152bb98a5d1ab0fee47c952de65da85fcbdfdb2808080\",\"0xf85180a0aec9a5fc3ba2ebedf137fbcf6987b303c9d8718f75253e6e2444b81a4049e5b980808080808080808080a0397f22cb0ad24543caffaad031e3a0a538e5d8ac106d8f6858a703d442c0e4d380808080\",\"0xf84ca020cc5921315fe8a051acdea77b782ecb469470d4e94248cb4ff49a1ff26fe982aaa9ab34000000000000000000000000000000000000000000000000000000000000000000000000000000\"],\"value\":\"0xab34000000000000000000000000000000000000000000000000000000000000000000000000000000\"},{\"key\":\"0x00000000001ccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"proof\":[\"0xf8918080a02474007b0486d5e951fe3fbcdae3e63cadf9c85cb8f178d3c7ca972e2d77705a808080808080a059c4fa21a1e4c40dc3c87e925befbb78a5b6f729865a12f6f0490d9801bcbf22a06b3576bbd6c91ca7128f69f728f3e30bf8980c6381430a5e80186d0dfec89d4e8080a098cfc3bf071c19a2e230165f4152bb98a5d1ab0fee47c952de65da85fcbdfdb2808080\",\"0xf84ca0383dacaf928fae678bb3ccca17c746816e9bcfc5628853ae37c67b2d3bbaad32aaa9ab56000000000000000000000000000000000000000000000000000000000000000000000000000000\"],\"value\":\"0xab56000000000000000000000000000000000000000000000000000000000000000000000000000000\"},{\"key\":\"0x00000000001ddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\"proof\":[\"0xf8918080a02474007b0486d5e951fe3fbcdae3e63cadf9c85cb8f178d3c7ca972e2d77705a808080808080a059c4fa21a1e4c40dc3c87e925befbb78a5b6f729865a12f6f0490d9801bcbf22a06b3576bbd6c91ca7128f69f728f3e30bf8980c6381430a5e80186d0dfec89d4e8080a098cfc3bf071c19a2e230165f4152bb98a5d1ab0fee47c952de65da85fcbdfdb2808080\",\"0xf84ca031c208aebac39ba1b4503fa46a491619019fc1a910b3bfe3c78dc3da4abdb097aaa9ab78000000000000000000000000000000000000000000000000000000000000000000000000000000\"],\"value\":\"0xab78000000000000000000000000000000000000000000000000000000000000000000000000000000\"},{\"key\":\"0x00000000001eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\",\"proof\":[\"0xf8918080a02474007b0486d5e951fe3fbcdae3e63cadf9c85cb8f178d3c7ca972e2d77705a808080808080a059c4fa21a1e4c40dc3c87e925befbb78a5b6f729865a12f6f0490d9801bcbf22a06b3576bbd6c91ca7128f69f728f3e30bf8980c6381430a5e80186d0dfec89d4e8080a098cfc3bf071c19a2e230165f4152bb98a5d1ab0fee47c952de65da85fcbdfdb2808080\",\"0xf84ca03ca9062cf1266ae3ad77045eb67b33d4c9a4f5e2be44c79cad975ace0bc6ed22aaa9ab9a000000000000000000000000000000000000000000000000000000000000000000000000000000\"],\"value\":\"0xab9a000000000000000000000000000000000000000000000000000000000000000000000000000000\"}]}"); + TestToJson(proof, "{\"accountProof\":[\"0xe215a08c9a7cdf08d4425c138ef5ba3d1f6c2ae18786fe88d9a56230a00b3e83367b25\",\"0xf8518080808080a017934c1c90ce30ca48f32b4f449dab308cfba803fd6d142cab01eb1fad1b70038080808080808080a02352504a0cd6095829b18bae394d0c882d84eead7be5b6ad0a87daaff9d2fb4a8080\",\"0xf869a020227dead52ea912e013e7641ccd6b3b174498e55066b0c174a09c8c3cc4bf5eb846f8448001a0b2375a34ff2c8037d9ff04ebc16367b51d156d9a905ca54cef50bfad1a4c0711a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470\"],\"address\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"balance\":\"0x1\",\"codeHash\":\"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470\",\"nonce\":\"0x0\",\"storageHash\":\"0xb2375a34ff2c8037d9ff04ebc16367b51d156d9a905ca54cef50bfad1a4c0711\",\"storageProof\":[{\"key\":\"0xaaaaaaaaaaaaaaaaaaaaaa\",\"proof\":[\"0xf8918080a02474007b0486d5e951fe3fbcdae3e63cadf9c85cb8f178d3c7ca972e2d77705a808080808080a059c4fa21a1e4c40dc3c87e925befbb78a5b6f729865a12f6f0490d9801bcbf22a06b3576bbd6c91ca7128f69f728f3e30bf8980c6381430a5e80186d0dfec89d4e8080a098cfc3bf071c19a2e230165f4152bb98a5d1ab0fee47c952de65da85fcbdfdb2808080\",\"0xf85180a0aec9a5fc3ba2ebedf137fbcf6987b303c9d8718f75253e6e2444b81a4049e5b980808080808080808080a0397f22cb0ad24543caffaad031e3a0a538e5d8ac106d8f6858a703d442c0e4d380808080\",\"0xf84ca02046d62176084b9d1eace1c8bcc2353228d10569a500ccfd1bdbd8c093f4b4e9aaa9ab12000000000000000000000000000000000000000000000000000000000000000000000000000000\"],\"value\":\"0xab12000000000000000000000000000000000000000000000000000000000000000000000000000000\"},{\"key\":\"0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"proof\":[\"0xf8918080a02474007b0486d5e951fe3fbcdae3e63cadf9c85cb8f178d3c7ca972e2d77705a808080808080a059c4fa21a1e4c40dc3c87e925befbb78a5b6f729865a12f6f0490d9801bcbf22a06b3576bbd6c91ca7128f69f728f3e30bf8980c6381430a5e80186d0dfec89d4e8080a098cfc3bf071c19a2e230165f4152bb98a5d1ab0fee47c952de65da85fcbdfdb2808080\",\"0xf85180a0aec9a5fc3ba2ebedf137fbcf6987b303c9d8718f75253e6e2444b81a4049e5b980808080808080808080a0397f22cb0ad24543caffaad031e3a0a538e5d8ac106d8f6858a703d442c0e4d380808080\",\"0xf84ca020cc5921315fe8a051acdea77b782ecb469470d4e94248cb4ff49a1ff26fe982aaa9ab34000000000000000000000000000000000000000000000000000000000000000000000000000000\"],\"value\":\"0xab34000000000000000000000000000000000000000000000000000000000000000000000000000000\"},{\"key\":\"0x1ccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"proof\":[\"0xf8918080a02474007b0486d5e951fe3fbcdae3e63cadf9c85cb8f178d3c7ca972e2d77705a808080808080a059c4fa21a1e4c40dc3c87e925befbb78a5b6f729865a12f6f0490d9801bcbf22a06b3576bbd6c91ca7128f69f728f3e30bf8980c6381430a5e80186d0dfec89d4e8080a098cfc3bf071c19a2e230165f4152bb98a5d1ab0fee47c952de65da85fcbdfdb2808080\",\"0xf84ca0383dacaf928fae678bb3ccca17c746816e9bcfc5628853ae37c67b2d3bbaad32aaa9ab56000000000000000000000000000000000000000000000000000000000000000000000000000000\"],\"value\":\"0xab56000000000000000000000000000000000000000000000000000000000000000000000000000000\"},{\"key\":\"0x1ddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\"proof\":[\"0xf8918080a02474007b0486d5e951fe3fbcdae3e63cadf9c85cb8f178d3c7ca972e2d77705a808080808080a059c4fa21a1e4c40dc3c87e925befbb78a5b6f729865a12f6f0490d9801bcbf22a06b3576bbd6c91ca7128f69f728f3e30bf8980c6381430a5e80186d0dfec89d4e8080a098cfc3bf071c19a2e230165f4152bb98a5d1ab0fee47c952de65da85fcbdfdb2808080\",\"0xf84ca031c208aebac39ba1b4503fa46a491619019fc1a910b3bfe3c78dc3da4abdb097aaa9ab78000000000000000000000000000000000000000000000000000000000000000000000000000000\"],\"value\":\"0xab78000000000000000000000000000000000000000000000000000000000000000000000000000000\"},{\"key\":\"0x1eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\",\"proof\":[\"0xf8918080a02474007b0486d5e951fe3fbcdae3e63cadf9c85cb8f178d3c7ca972e2d77705a808080808080a059c4fa21a1e4c40dc3c87e925befbb78a5b6f729865a12f6f0490d9801bcbf22a06b3576bbd6c91ca7128f69f728f3e30bf8980c6381430a5e80186d0dfec89d4e8080a098cfc3bf071c19a2e230165f4152bb98a5d1ab0fee47c952de65da85fcbdfdb2808080\",\"0xf84ca03ca9062cf1266ae3ad77045eb67b33d4c9a4f5e2be44c79cad975ace0bc6ed22aaa9ab9a000000000000000000000000000000000000000000000000000000000000000000000000000000\"],\"value\":\"0xab9a000000000000000000000000000000000000000000000000000000000000000000000000000000\"}]}"); } [Test] @@ -83,7 +83,7 @@ public void Does_not_fail_when_proofs_are_longer_than_number_of_proofs_regressio tree.Accept(accountProofCollector, tree.RootHash); AccountProof proof = accountProofCollector.BuildResult(); - TestToJson(proof, "{\"accountProof\":[\"0xe215a08c9a7cdf08d4425c138ef5ba3d1f6c2ae18786fe88d9a56230a00b3e83367b25\",\"0xf8518080808080a017934c1c90ce30ca48f32b4f449dab308cfba803fd6d142cab01eb1fad1b70038080808080808080a02352504a0cd6095829b18bae394d0c882d84eead7be5b6ad0a87daaff9d2fb4a8080\",\"0xf869a020227dead52ea912e013e7641ccd6b3b174498e55066b0c174a09c8c3cc4bf5eb846f8448001a0b2375a34ff2c8037d9ff04ebc16367b51d156d9a905ca54cef50bfad1a4c0711a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470\"],\"address\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"balance\":\"0x1\",\"codeHash\":\"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470\",\"nonce\":\"0x0\",\"storageHash\":\"0xb2375a34ff2c8037d9ff04ebc16367b51d156d9a905ca54cef50bfad1a4c0711\",\"storageProof\":[{\"key\":\"0x000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaa\",\"proof\":[\"0xf8918080a02474007b0486d5e951fe3fbcdae3e63cadf9c85cb8f178d3c7ca972e2d77705a808080808080a059c4fa21a1e4c40dc3c87e925befbb78a5b6f729865a12f6f0490d9801bcbf22a06b3576bbd6c91ca7128f69f728f3e30bf8980c6381430a5e80186d0dfec89d4e8080a098cfc3bf071c19a2e230165f4152bb98a5d1ab0fee47c952de65da85fcbdfdb2808080\",\"0xf85180a0aec9a5fc3ba2ebedf137fbcf6987b303c9d8718f75253e6e2444b81a4049e5b980808080808080808080a0397f22cb0ad24543caffaad031e3a0a538e5d8ac106d8f6858a703d442c0e4d380808080\",\"0xf84ca02046d62176084b9d1eace1c8bcc2353228d10569a500ccfd1bdbd8c093f4b4e9aaa9ab12000000000000000000000000000000000000000000000000000000000000000000000000000000\"],\"value\":\"0xab12000000000000000000000000000000000000000000000000000000000000000000000000000000\"}]}"); + TestToJson(proof, "{\"accountProof\":[\"0xe215a08c9a7cdf08d4425c138ef5ba3d1f6c2ae18786fe88d9a56230a00b3e83367b25\",\"0xf8518080808080a017934c1c90ce30ca48f32b4f449dab308cfba803fd6d142cab01eb1fad1b70038080808080808080a02352504a0cd6095829b18bae394d0c882d84eead7be5b6ad0a87daaff9d2fb4a8080\",\"0xf869a020227dead52ea912e013e7641ccd6b3b174498e55066b0c174a09c8c3cc4bf5eb846f8448001a0b2375a34ff2c8037d9ff04ebc16367b51d156d9a905ca54cef50bfad1a4c0711a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470\"],\"address\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"balance\":\"0x1\",\"codeHash\":\"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470\",\"nonce\":\"0x0\",\"storageHash\":\"0xb2375a34ff2c8037d9ff04ebc16367b51d156d9a905ca54cef50bfad1a4c0711\",\"storageProof\":[{\"key\":\"0xaaaaaaaaaaaaaaaaaaaaaa\",\"proof\":[\"0xf8918080a02474007b0486d5e951fe3fbcdae3e63cadf9c85cb8f178d3c7ca972e2d77705a808080808080a059c4fa21a1e4c40dc3c87e925befbb78a5b6f729865a12f6f0490d9801bcbf22a06b3576bbd6c91ca7128f69f728f3e30bf8980c6381430a5e80186d0dfec89d4e8080a098cfc3bf071c19a2e230165f4152bb98a5d1ab0fee47c952de65da85fcbdfdb2808080\",\"0xf85180a0aec9a5fc3ba2ebedf137fbcf6987b303c9d8718f75253e6e2444b81a4049e5b980808080808080808080a0397f22cb0ad24543caffaad031e3a0a538e5d8ac106d8f6858a703d442c0e4d380808080\",\"0xf84ca02046d62176084b9d1eace1c8bcc2353228d10569a500ccfd1bdbd8c093f4b4e9aaa9ab12000000000000000000000000000000000000000000000000000000000000000000000000000000\"],\"value\":\"0xab12000000000000000000000000000000000000000000000000000000000000000000000000000000\"}]}"); } } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs index d95e38b84888..3ecf250af76c 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs @@ -4,12 +4,14 @@ using System; using System.Collections.Generic; using System.IO.Abstractions; +using System.IO.Pipelines; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Nethermind.Core.Extensions; +using Nethermind.Config; using Nethermind.Logging; using Nethermind.JsonRpc.Modules; using NSubstitute; @@ -24,7 +26,7 @@ public class JsonRpcProcessorTests(bool returnErrors) { private readonly JsonRpcErrorResponse _errorResponse = new(); - private JsonRpcProcessor Initialize(JsonRpcConfig? config = null) + private JsonRpcProcessor Initialize(JsonRpcConfig? config = null, RpcRecorderState recorderState = RpcRecorderState.All) { IJsonRpcService service = Substitute.For(); service.SendRequestAsync(Arg.Any(), Arg.Any()).Returns(ci => returnErrors ? new JsonRpcErrorResponse { Id = ci.Arg().Id } : new JsonRpcSuccessResponse { Id = ci.Arg().Id }); @@ -33,11 +35,9 @@ private JsonRpcProcessor Initialize(JsonRpcConfig? config = null) IFileSystem fileSystem = Substitute.For(); - /* we enable recorder always to have an easy smoke test for recording - * and this is fine because recorder is non-critical component - */ + // we enable recorder always to have an easy smoke test for recording and this is fine because recorder is a non-critical component config ??= new JsonRpcConfig(); - config.RpcRecorderState = RpcRecorderState.All; + config.RpcRecorderState = recorderState; return new JsonRpcProcessor(service, config, fileSystem, LimboLogs.Instance); } @@ -402,6 +402,65 @@ public async Task Can_handle_null_request() result.DisposeItems(); } + [Test] + public async Task Should_stop_processing_when_shutdown_requested() + { + IJsonRpcService service = Substitute.For(); + service.GetErrorResponse(Arg.Any(), Arg.Any()) + .Returns(new JsonRpcErrorResponse { Error = new Error { Code = ErrorCodes.ResourceUnavailable, Message = "Shutting down" } }); + + IProcessExitSource processExitSource = Substitute.For(); + processExitSource.Token.Returns(new CancellationToken(canceled: true)); + + JsonRpcProcessor processor = new( + service, + new JsonRpcConfig(), + Substitute.For(), + LimboLogs.Instance, + processExitSource); + + string request = "{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"; + List results = await processor.ProcessAsync(request, new JsonRpcContext(RpcEndpoint.Http)).ToListAsync(); + + results.Should().HaveCount(1); + results[0].Response.Should().BeOfType(); + ((JsonRpcErrorResponse)results[0].Response!).Error!.Code.Should().Be(ErrorCodes.ResourceUnavailable); + await service.DidNotReceive().SendRequestAsync(Arg.Any(), Arg.Any()); + results.DisposeItems(); + } + + [Test] + public async Task Should_complete_pipe_reader_when_shutdown_requested() + { + IJsonRpcService service = Substitute.For(); + service.GetErrorResponse(Arg.Any(), Arg.Any()) + .Returns(new JsonRpcErrorResponse { Error = new Error { Code = ErrorCodes.ResourceUnavailable, Message = "Shutting down" } }); + + IProcessExitSource processExitSource = Substitute.For(); + processExitSource.Token.Returns(new CancellationToken(canceled: true)); + + JsonRpcProcessor processor = new( + service, + new JsonRpcConfig(), + Substitute.For(), + LimboLogs.Instance, + processExitSource); + + Pipe pipe = new(); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\",\"params\":[]}")); + + List results = await processor.ProcessAsync(pipe.Reader, new JsonRpcContext(RpcEndpoint.Http)).ToListAsync(); + + results.Should().HaveCount(1); + results[0].Response.Should().BeOfType(); + + // Verify PipeReader was completed by the processor (reading again should throw) + await FluentActions.Invoking(async () => await pipe.Reader.ReadAsync()) + .Should().ThrowAsync(); + + results.DisposeItems(); + } + [Test] public void Cannot_accept_null_file_system() { @@ -409,4 +468,65 @@ public void Cannot_accept_null_file_system() Substitute.For(), null!, LimboLogs.Instance)); } + + [Test] + public async Task Can_process_multiple_large_requests_arriving_in_chunks() + { + Pipe pipe = new(); + JsonRpcProcessor processor = Initialize(recorderState: RpcRecorderState.None); + JsonRpcContext context = new(RpcEndpoint.Ws); + + // Create 5 large JSON-RPC requests (~10KB each) + List requests = Enumerable.Range(0, 5) + .Select(i => CreateLargeRequest(i, targetSize: 10_000)) + .ToList(); + + string allRequestsJson = string.Join("\n", requests); + byte[] bytes = Encoding.UTF8.GetBytes(allRequestsJson); + + // Start processing task (reads from pipe.Reader) + ValueTask> processTask = processor + .ProcessAsync(pipe.Reader, context) + .ToListAsync(); + + // Write data in 1KB chunks with small delays to simulate network + const int chunkSize = 1024; + for (int i = 0; i < bytes.Length; i += chunkSize) + { + int size = Math.Min(chunkSize, bytes.Length - i); + await pipe.Writer.WriteAsync(new ReadOnlyMemory(bytes, i, size)); + await Task.Yield(); + } + await pipe.Writer.CompleteAsync(); + + // Verify all 5 requests processed + List results = await processTask; + results.Should().HaveCount(5); + for (int i = 0; i < 5; i++) + { + results[i].Response.Should().NotBeNull(); + } + results.DisposeItems(); + } + + private static string CreateLargeRequest(int id, int targetSize) + { + StringBuilder sb = new(); + sb.Append($"{{\"jsonrpc\":\"2.0\",\"id\":{id},\"method\":\"test_method\",\"params\":["); + + int currentSize = sb.Length + 2; // account for closing ]} + bool first = true; + int paramIndex = 0; + while (currentSize < targetSize) + { + string param = $"\"param_{paramIndex++}_padding\""; + if (!first) sb.Append(','); + sb.Append(param); + currentSize += param.Length + (first ? 0 : 1); + first = false; + } + + sb.Append("]}"); + return sb.ToString(); + } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcUrlCollectionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcUrlCollectionTests.cs index 7ace8f6392f6..714a083672e6 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 cb64343d7e7c..7ae6ebfb56bd 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/BoundedModulePoolTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs index 11e1e43e21e4..82e655f5f597 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs @@ -6,6 +6,7 @@ using Nethermind.Config; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; +using Nethermind.Db.LogIndex; using Nethermind.JsonRpc.Modules; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Logging; @@ -56,7 +57,8 @@ public Task Initialize() Substitute.For(), Substitute.For(), new BlocksConfig(), - Substitute.For()), + Substitute.For(), + Substitute.For()), 1, 1000); return Task.CompletedTask; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs index f341a7f90932..b39db42f80a1 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs @@ -125,7 +125,7 @@ public async Task Get_raw_Header() debugBridge.GetBlock(new BlockParameter((long)0)).Returns(blk); DebugRpcModule rpcModule = CreateDebugRpcModule(debugBridge); - using var response = await RpcTest.TestRequest(rpcModule, "debug_getRawHeader", 0) as JsonRpcSuccessResponse; + using var response = await RpcTest.TestRequest(rpcModule, "debug_getRawHeader", "0x") as JsonRpcSuccessResponse; Assert.That((byte[]?)response?.Result, Is.EqualTo(rlp.Bytes)); } @@ -152,7 +152,7 @@ public async Task Get_rawblock() localDebugBridge.GetBlockRlp(new BlockParameter(1)).Returns(rlp.Bytes); DebugRpcModule rpcModule = CreateDebugRpcModule(localDebugBridge); - using var response = await RpcTest.TestRequest(rpcModule, "debug_getRawBlock", 1) as JsonRpcSuccessResponse; + using var response = await RpcTest.TestRequest(rpcModule, "debug_getRawBlock", "0x1") as JsonRpcSuccessResponse; Assert.That((byte[]?)response?.Result, Is.EqualTo(rlp.Bytes)); } @@ -179,7 +179,7 @@ public async Task Get_block_rlp_when_missing() DebugRpcModule rpcModule = CreateDebugRpcModule(debugBridge); using var response = await RpcTest.TestRequest(rpcModule, "debug_getBlockRlp", 1) as JsonRpcErrorResponse; - Assert.That(response?.Error?.Code, Is.EqualTo(-32001)); + Assert.That(response?.Error?.Code, Is.EqualTo(ErrorCodes.ResourceNotFound)); } [Test] @@ -188,9 +188,9 @@ public async Task Get_rawblock_when_missing() debugBridge.GetBlockRlp(new BlockParameter(1)).ReturnsNull(); DebugRpcModule rpcModule = CreateDebugRpcModule(debugBridge); - using var response = await RpcTest.TestRequest(rpcModule, "debug_getRawBlock", 1) as JsonRpcErrorResponse; + using var response = await RpcTest.TestRequest(rpcModule, "debug_getRawBlock", "0x1") as JsonRpcErrorResponse; - Assert.That(response?.Error?.Code, Is.EqualTo(-32001)); + Assert.That(response?.Error?.Code, Is.EqualTo(ErrorCodes.ResourceNotFound)); } [Test] @@ -203,7 +203,7 @@ public async Task Get_block_rlp_by_hash_when_missing() DebugRpcModule rpcModule = CreateDebugRpcModule(debugBridge); using var response = await RpcTest.TestRequest(rpcModule, "debug_getBlockRlpByHash", Keccak.Zero) as JsonRpcErrorResponse; - Assert.That(response?.Error?.Code, Is.EqualTo(-32001)); + Assert.That(response?.Error?.Code, Is.EqualTo(ErrorCodes.ResourceNotFound)); } private BlockTree BuildBlockTree(Func? builderOptions = null) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.TraceCallMany.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.TraceCallMany.cs index 10b0df5a0799..2f6e84862cc0 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.TraceCallMany.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.TraceCallMany.cs @@ -170,7 +170,7 @@ public async Task Debug_traceCallMany_block_override_gaslimit_applies() public async Task Debug_traceCallMany_mixed_bundles_preserves_order() { using Context ctx = await CreateContext(); - TransactionBundle simple = CreateBundle(CreateTransaction(gas: 25_000_000)); + TransactionBundle simple = CreateBundle(CreateTransaction(gas: 4_000_000)); TransactionBundle withOverride = CreateBundle(CreateTransaction(gas: 25_000_000)); withOverride.BlockOverride = new BlockOverride { GasLimit = 30_000_000 }; var result = ctx.DebugRpcModule.debug_traceCallMany([simple, withOverride], BlockParameter.Latest); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.TraceTransaction.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.TraceTransaction.cs index a8ce6cfcc906..df22e364ff87 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.TraceTransaction.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.TraceTransaction.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Text.Json; using System.Threading.Tasks; using FluentAssertions; using Nethermind.Core; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.cs index a0471307c087..aad3ae9a1709 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.cs @@ -4,7 +4,6 @@ using System; using System.Text.Json; using System.Threading.Tasks; -using Autofac; using FluentAssertions; using FluentAssertions.Execution; using FluentAssertions.Json; @@ -14,7 +13,6 @@ using Nethermind.Core.Test.Builders; using Nethermind.Blockchain.Tracing.GethStyle; using Nethermind.Int256; -using Nethermind.JsonRpc.Modules; using Nethermind.JsonRpc.Modules.DebugModule; using Newtonsoft.Json.Linq; using NUnit.Framework; @@ -60,7 +58,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 +99,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 +124,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 c3e6c11fdcf6..b468009e9f12 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); @@ -55,7 +55,7 @@ public async Task Test_debug_simulate_eth_moved() TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain(); UInt256 nonceA = chain.ReadOnlyState.GetNonce(TestItem.AddressA); - Transaction txMainnetAtoB = EthSimulateTestsBlocksAndTransactions.GetTransferTxData(nonceA, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1); + Transaction txMainnetAtoB = EthSimulateTestsBlocksAndTransactions.GetTransferTxData(nonceA, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1, type: TxType.Legacy); SimulatePayload payload = EthSimulateTestsBlocksAndTransactions.CreateEthMovedPayload(chain, nonceA); @@ -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!); @@ -93,7 +93,7 @@ public async Task Test_debug_simulate_transactions_forced_fail() UInt256 nonceA = chain.ReadOnlyState.GetNonce(TestItem.AddressA); Transaction txMainnetAtoB = - EthSimulateTestsBlocksAndTransactions.GetTransferTxData(nonceA, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1); + EthSimulateTestsBlocksAndTransactions.GetTransferTxData(nonceA, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1, type: TxType.Legacy); SimulatePayload payload = EthSimulateTestsBlocksAndTransactions.CreateTransactionsForcedFail(chain, nonceA); @@ -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 bc3c1d557e4b..05bc13e3eb87 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EstimateGas.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EstimateGas.cs @@ -8,9 +8,7 @@ using FluentAssertions.Execution; using FluentAssertions.Json; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; -using Nethermind.Core.Test.Blockchain; using Nethermind.Core.Test.Builders; using Nethermind.Core.Test.Container; using Nethermind.Evm; @@ -21,6 +19,8 @@ using Nethermind.Evm.State; using Newtonsoft.Json.Linq; using NUnit.Framework; +using System.Text; +using Nethermind.Abi; namespace Nethermind.JsonRpc.Test.Modules.Eth; @@ -37,7 +37,7 @@ public async Task Eth_estimateGas_web3_should_return_insufficient_balance_error( string serialized = await ctx.Test.TestEthRpc("eth_estimateGas", transaction); Assert.That( - serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"insufficient funds for transfer: address 0x0001020304050607080910111213141516171819\"},\"id\":67}")); + serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"insufficient sender balance\"},\"id\":67}")); ctx.Test.ReadOnlyState.AccountExists(someAccount).Should().BeFalse(); } @@ -48,7 +48,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}")); @@ -62,7 +62,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}")); @@ -76,7 +76,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\":\"0x100000000\",\"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}")); @@ -124,7 +124,7 @@ public async Task Eth_estimate_gas_with_accessList(bool senderAccessList, long g AccessListTransactionForRpc transaction = test.JsonSerializer.Deserialize( - $"{{\"type\":\"0x1\", \"data\": \"{code.ToHexString(true)}\"}}"); + $"{{\"type\":\"0x1\", \"from\": \"{Address.SystemUser}\", \"data\": \"{code.ToHexString(true)}\"}}"); string serialized = await test.TestEthRpc("eth_estimateGas", transaction, "0x0"); Assert.That( serialized, Is.EqualTo($"{{\"jsonrpc\":\"2.0\",\"result\":\"{gasPriceWithoutAccessList.ToHexString(true)}\",\"id\":67}}")); @@ -173,7 +173,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}")); } @@ -183,7 +183,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}")); } @@ -230,18 +230,49 @@ public async Task Estimate_gas_with_revert() { using Context ctx = await Context.CreateWithLondonEnabled(); + string errorMessage = "wrong-calldatasize"; + string hexEncodedErrorMessage = Encoding.UTF8.GetBytes(errorMessage).ToHexString(true); + byte[] code = Prepare.EvmCode - .PushData(0) - .PushData(0) - .Op(Instruction.REVERT) + .RevertWithError(errorMessage) + .Done; + + string dataStr = code.ToHexString(); + TransactionForRpc transaction = ctx.Test.JsonSerializer.Deserialize( + $$"""{"from": "0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24", "type": "0x2", "data": "{{dataStr}}", "gas": 1000000}"""); + string serialized = await ctx.Test.TestEthRpc("eth_estimateGas", transaction); + Assert.That( + serialized, Is.EqualTo($$"""{"jsonrpc":"2.0","error":{"code":3,"message":"execution reverted: {{errorMessage}}","data":"{{hexEncodedErrorMessage}}"},"id":67}""")); + } + + [Test] + public async Task Estimate_gas_with_abi_encoded_revert() + { + using Context ctx = await Context.CreateWithLondonEnabled(); + + var abiEncoder = new AbiEncoder(); + var errorSignature = new AbiSignature( + "Error", + AbiType.String + ); + string errorMessage = "wrong-parameters"; + byte[] encodedError = abiEncoder.Encode( + AbiEncodingStyle.IncludeSignature, // Include the 0x08c379a0 selector + errorSignature, + errorMessage + ); + string abiEncodedErrorMessage = encodedError.ToHexString(true); + + byte[] code = Prepare.EvmCode + .RevertWithSolidityErrorEncoding(errorMessage) .Done; string dataStr = code.ToHexString(); TransactionForRpc transaction = ctx.Test.JsonSerializer.Deserialize( - $"{{\"from\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"type\": \"0x2\", \"data\": \"{dataStr}\", \"gas\": 100000000}}"); + $$"""{"from": "0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24", "type": "0x2", "data": "{{dataStr}}", "gas": 1000000}"""); string serialized = await ctx.Test.TestEthRpc("eth_estimateGas", transaction); Assert.That( - serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"execution reverted\"},\"id\":67}")); + serialized, Is.EqualTo($$"""{"jsonrpc":"2.0","error":{"code":3,"message":"execution reverted: {{errorMessage}}","data":"{{abiEncodedErrorMessage}}"},"id":67}""")); } [Test] @@ -256,9 +287,12 @@ 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) + LegacyTransactionForRpc transaction = new LegacyTransactionForRpc( + tx, + new(tx.ChainId ?? BlockchainIds.Mainnet)) { - To = TestItem.AddressB + To = TestItem.AddressB, + GasPrice = 0 }; string serialized = @@ -285,7 +319,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 @@ -304,12 +338,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"}}""" )] @@ -348,14 +382,14 @@ public async Task Estimate_gas_uses_block_gas_limit_when_not_specified() string blockResponse = await ctx.Test.TestEthRpc("eth_getBlockByNumber", blockNumber, false); long blockGasLimit = Convert.ToInt64(JToken.Parse(blockResponse).SelectToken("result.gasLimit")!.Value(), 16); - await TestEstimateGasOutOfGas(ctx, null, blockGasLimit); + await TestEstimateGasOutOfGas(ctx, null, blockGasLimit, "out of gas"); } [Test] public async Task Estimate_gas_uses_specified_gas_limit() { using Context ctx = await Context.Create(); - await TestEstimateGasOutOfGas(ctx, 30000000, 30000000); + await TestEstimateGasOutOfGas(ctx, 30000000, 30000000, "Block gas limit exceeded"); } [Test] @@ -363,7 +397,7 @@ public async Task Estimate_gas_cannot_exceed_gas_cap() { using Context ctx = await Context.Create(); ctx.Test.RpcConfig.GasCap = 50000000; - await TestEstimateGasOutOfGas(ctx, 300000000, 50000000); + await TestEstimateGasOutOfGas(ctx, 300000000, 50000000, "Block gas limit exceeded"); } [Test] @@ -373,12 +407,13 @@ public async Task Eth_estimateGas_ignores_invalid_nonce() byte[] code = Prepare.EvmCode .Op(Instruction.STOP) .Done; - EIP1559TransactionForRpc transaction = new(Build.A.Transaction + Transaction tx = Build.A.Transaction .WithNonce(123) .WithGasLimit(100000) .WithData(code) .SignedAndResolved(TestItem.PrivateKeyA) - .TestObject); + .TestObject; + EIP1559TransactionForRpc transaction = new(tx, new(tx.ChainId ?? BlockchainIds.Mainnet)); transaction.GasPrice = null; string serialized = await ctx.Test.TestEthRpc("eth_estimateGas", transaction); @@ -393,12 +428,13 @@ public async Task Eth_estimateGas_simple_transfer() { using Context ctx = await Context.Create(); byte[] code = []; - EIP1559TransactionForRpc transaction = new(Build.A.Transaction + Transaction tx = Build.A.Transaction .WithTo(TestItem.AddressB) .WithGasLimit(100000) .WithData(code) .SignedAndResolved(TestItem.PrivateKeyA) - .TestObject); + .TestObject; + EIP1559TransactionForRpc transaction = new(tx, new(tx.ChainId ?? BlockchainIds.Mainnet)); transaction.GasPrice = null; @@ -408,7 +444,7 @@ public async Task Eth_estimateGas_simple_transfer() serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":\"0x5208\",\"id\":67}")); } - private static async Task TestEstimateGasOutOfGas(Context ctx, long? specifiedGasLimit, long expectedGasLimit) + private static async Task TestEstimateGasOutOfGas(Context ctx, long? specifiedGasLimit, long expectedGasLimit, string message) { string gasParam = specifiedGasLimit.HasValue ? $", \"gas\": \"0x{specifiedGasLimit.Value:X}\"" : ""; TransactionForRpc transaction = ctx.Test.JsonSerializer.Deserialize( @@ -416,7 +452,7 @@ private static async Task TestEstimateGasOutOfGas(Context ctx, long? specifiedGa string serialized = await ctx.Test.TestEthRpc("eth_estimateGas", transaction); JToken.Parse(serialized).Should().BeEquivalentTo( - $"{{\"jsonrpc\":\"2.0\",\"error\":{{\"code\":-32000,\"message\":\"out of gas\"}},\"id\":67}}"); + $"{{\"jsonrpc\":\"2.0\",\"error\":{{\"code\":-32000,\"message\":\"{message}\"}},\"id\":67}}"); } } 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 f1b637a85ea1..93e87fceb856 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EthCall.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EthCall.cs @@ -10,7 +10,6 @@ using FluentAssertions.Execution; using FluentAssertions.Json; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; using Nethermind.Core.Test.Container; @@ -24,6 +23,7 @@ using Nethermind.Int256; using Newtonsoft.Json.Linq; using NUnit.Framework; +using Nethermind.Abi; namespace Nethermind.JsonRpc.Test.Modules.Eth; @@ -52,7 +52,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}")); @@ -66,10 +66,10 @@ public async Task Eth_call_web3_should_return_insufficient_balance_error() 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\", \"value\": 500, \"gas\": 100000000}"); + "{\"from\":\"0x0001020304050607080910111213141516171819\",\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\", \"value\": 500, \"gas\": 1000000}"); string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); Assert.That( - serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"insufficient funds for transfer: address 0x0001020304050607080910111213141516171819\"},\"id\":67}")); + serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"insufficient sender balance\"},\"id\":67}")); ctx.Test.ReadOnlyState.AccountExists(someAccount).Should().BeFalse(); } @@ -80,7 +80,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}")); @@ -91,9 +91,10 @@ public async Task Eth_call_web3_sample_not_enough_gas_other_account() public async Task Eth_call_no_sender() { using Context ctx = await Context.Create(); - LegacyTransactionForRpc transaction = new(new Transaction(), 1, Keccak.Zero, 1L) + LegacyTransactionForRpc transaction = new(new Transaction(), new(BlockchainIds.Mainnet)) { - To = TestItem.AddressB + To = TestItem.AddressB, + Gas = 1000000 }; string serialized = @@ -105,11 +106,11 @@ public async Task Eth_call_no_sender() public async Task Eth_call_no_recipient_should_work_as_init() { using Context ctx = await Context.Create(); - LegacyTransactionForRpc transaction = new(new Transaction(), 1, Keccak.Zero, 1L) + LegacyTransactionForRpc transaction = new(new Transaction(), new(BlockchainIds.Mainnet)) { From = TestItem.AddressA, Input = [1, 2, 3], - Gas = 100000000 + Gas = 1000000 }; string serialized = @@ -131,9 +132,10 @@ 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) + LegacyTransactionForRpc transaction = new(tx, new(tx.ChainId ?? BlockchainIds.Mainnet)) { - To = TestItem.AddressB + To = TestItem.AddressB, + GasPrice = 0 }; string serialized = @@ -155,10 +157,11 @@ public async Task Eth_call_ethereum_recipient() public async Task Eth_call_ok() { using Context ctx = await Context.Create(); - LegacyTransactionForRpc transaction = new(new Transaction(), 1, Keccak.Zero, 1L) + LegacyTransactionForRpc transaction = new(new Transaction(), new(BlockchainIds.Mainnet)) { From = TestItem.AddressA, - To = TestItem.AddressB + To = TestItem.AddressB, + Gas = 1000000 }; string serialized = @@ -170,7 +173,7 @@ public async Task Eth_call_ok() public async Task Eth_call_with_blockhash_ok() { using Context ctx = await Context.Create(); - LegacyTransactionForRpc transaction = new(new Transaction(), 1, Keccak.Zero, 1L) + LegacyTransactionForRpc transaction = new(new Transaction(), new(BlockchainIds.Mainnet)) { From = TestItem.AddressA, To = TestItem.AddressB @@ -178,14 +181,14 @@ public async Task Eth_call_with_blockhash_ok() string serialized = await ctx.Test.TestEthRpc("eth_call", transaction, "{\"blockHash\":\"0xf0b3f69cbd4e1e8d9b0ef02ff5d1384d18e19d251a4052f5f90bab190c5e8937\"}"); - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32001,\"message\":\"0xf0b3f69cbd4e1e8d9b0ef02ff5d1384d18e19d251a4052f5f90bab190c5e8937 could not be found\"},\"id\":67}")); + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"0xf0b3f69cbd4e1e8d9b0ef02ff5d1384d18e19d251a4052f5f90bab190c5e8937 could not be found\"},\"id\":67}")); } [Test] public async Task Eth_call_create_tx_with_empty_data() { using Context ctx = await Context.Create(); - LegacyTransactionForRpc transaction = new(new Transaction(), 1, Keccak.Zero, 1L) + LegacyTransactionForRpc transaction = new(new Transaction(), new(BlockchainIds.Mainnet)) { From = TestItem.AddressA }; @@ -198,7 +201,7 @@ public async Task Eth_call_create_tx_with_empty_data() public async Task Eth_call_missing_state_after_fast_sync() { using Context ctx = await Context.Create(); - LegacyTransactionForRpc transaction = new(new Transaction(), 1, Keccak.Zero, 1L) + LegacyTransactionForRpc transaction = new(new Transaction(), new(BlockchainIds.Mainnet)) { From = TestItem.AddressA, To = TestItem.AddressB @@ -244,7 +247,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}")); } @@ -254,7 +257,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}")); } @@ -297,22 +300,78 @@ public async Task Eth_call_with_base_fee_opcode_should_return_0() } [Test] - public async Task Eth_call_with_revert() + public async Task Eth_call_with_base_fee_opcode_without_from_address_should_return_0() { using Context ctx = await Context.CreateWithLondonEnabled(); byte[] code = Prepare.EvmCode + .Op(Instruction.BASEFEE) .PushData(0) + .Op(Instruction.MSTORE) + .PushData("0x20") + .PushData("0x0") + .Op(Instruction.RETURN) + .Done; + + string dataStr = code.ToHexString(); + TransactionForRpc transaction = ctx.Test.JsonSerializer.Deserialize( + $"{{\"type\": \"0x2\", \"data\": \"{dataStr}\"}}"); + string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); + Assert.That( + serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"id\":67}")); + } + + [Test] + public async Task Eth_call_with_value_transfer_without_from_address_should_throw() + { + using Context ctx = await Context.CreateWithLondonEnabled(); + + byte[] code = Prepare.EvmCode + .Op(Instruction.BASEFEE) .PushData(0) - .Op(Instruction.REVERT) + .Op(Instruction.MSTORE) + .PushData("0x20") + .PushData("0x0") + .Op(Instruction.RETURN) .Done; string dataStr = code.ToHexString(); TransactionForRpc transaction = ctx.Test.JsonSerializer.Deserialize( - $"{{\"from\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"type\": \"0x2\", \"data\": \"{dataStr}\", \"gas\": 100000000}}"); + $"{{\"type\": \"0x2\", \"value\":\"{1.Ether()}\", \"data\": \"{dataStr}\"}}"); string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); + Console.WriteLine(serialized); Assert.That( - serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"execution reverted\",\"data\":\"0x\"},\"id\":67}")); + serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"insufficient sender balance\"},\"id\":67}")); + } + + [Test] + public async Task Eth_call_with_revert() + { + using Context ctx = await Context.CreateWithLondonEnabled(); + + var abiEncoder = new AbiEncoder(); + var errorSignature = new AbiSignature( + "Error", + AbiType.String + ); + string errorMessage = "wrong-parameters"; + byte[] encodedError = abiEncoder.Encode( + AbiEncodingStyle.IncludeSignature, // Include the 0x08c379a0 selector + errorSignature, + errorMessage + ); + string abiEncodedErrorMessage = encodedError.ToHexString(true); + + byte[] code = Prepare.EvmCode + .RevertWithSolidityErrorEncoding(errorMessage) + .Done; + + string dataStr = code.ToHexString(); + TransactionForRpc transaction = ctx.Test.JsonSerializer.Deserialize( + $$"""{"from": "0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24", "type": "0x2", "data": "{{dataStr}}", "gas": 1000000}"""); + string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); + Assert.That( + serialized, Is.EqualTo($$"""{"jsonrpc":"2.0","error":{"code":3,"message":"execution reverted: {{errorMessage}}","data":"{{abiEncodedErrorMessage}}"},"id":67}""")); } [TestCase( @@ -334,7 +393,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}""" @@ -352,12 +411,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"}}""" )] @@ -397,7 +456,12 @@ public async Task Eth_call_uses_block_gas_limit_when_not_specified() string blockResponse = await ctx.Test.TestEthRpc("eth_getBlockByNumber", blockNumber, false); long blockGasLimit = Convert.ToInt64(JToken.Parse(blockResponse).SelectToken("result.gasLimit")!.Value(), 16); - await TestEthCallOutOfGas(ctx, null, blockGasLimit); + TransactionForRpc transaction = ctx.Test.JsonSerializer.Deserialize( + $"{{\"from\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"data\": \"{InfiniteLoopCode.ToHexString()}\"}}"); + + string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); + JToken.Parse(serialized).Should().BeEquivalentTo( + $"{{\"jsonrpc\":\"2.0\",\"error\":{{\"code\":-32000,\"message\":\"out of gas\",\"data\":\"0x\"}},\"id\":67}}"); } [Test] @@ -422,12 +486,13 @@ public async Task Eth_call_ignores_invalid_nonce() byte[] code = Prepare.EvmCode .Op(Instruction.STOP) .Done; - EIP1559TransactionForRpc transaction = new(Build.A.Transaction + Transaction tx = Build.A.Transaction .WithNonce(123) .WithGasLimit(100000) .WithData(code) .SignedAndResolved(TestItem.PrivateKeyA) - .TestObject); + .TestObject; + EIP1559TransactionForRpc transaction = new(tx, new(tx.ChainId ?? BlockchainIds.Mainnet)); transaction.GasPrice = null; string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); @@ -443,11 +508,12 @@ public async Task Eth_call_contract_creation() byte[] code = Prepare.EvmCode .Op(Instruction.STOP) .Done; - LegacyTransactionForRpc transaction = new(Build.A.Transaction + Transaction tx = Build.A.Transaction .WithData(code) .WithGasLimit(100000) .SignedAndResolved(TestItem.PrivateKeyA) - .TestObject); + .TestObject; + LegacyTransactionForRpc transaction = new(tx, new(tx.ChainId ?? BlockchainIds.Mainnet)); transaction.To = null; string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); @@ -460,10 +526,11 @@ public async Task Eth_call_contract_creation() public async Task Eth_call_to_is_null_and_not_contract_creation(byte[]? data) { using Context ctx = await Context.Create(); - LegacyTransactionForRpc transaction = new(Build.A.Transaction + Transaction tx = Build.A.Transaction .WithGasLimit(100000) .SignedAndResolved(TestItem.PrivateKeyA) - .TestObject); + .TestObject; + LegacyTransactionForRpc transaction = new(tx, new(tx.ChainId ?? BlockchainIds.Mainnet)); transaction.To = null; transaction.Data = data; string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); @@ -476,11 +543,12 @@ public async Task Eth_call_to_is_null_and_not_contract_creation(byte[]? data) public async Task Eth_call_gas_price_in_eip1559_tx() { using Context ctx = await Context.Create(); - EIP1559TransactionForRpc transaction = new(Build.A.Transaction + Transaction tx = Build.A.Transaction .WithGasLimit(100000) .To(TestItem.AddressA) .SignedAndResolved(TestItem.PrivateKeyA) - .TestObject); + .TestObject; + EIP1559TransactionForRpc transaction = new(tx, new(tx.ChainId ?? BlockchainIds.Mainnet)); transaction.GasPrice = new UInt256(1); string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); @@ -493,12 +561,13 @@ public async Task Eth_call_gas_price_in_eip1559_tx() public async Task Eth_call_no_blobs_in_blob_tx(bool isNull) { using Context ctx = await Context.Create(); - BlobTransactionForRpc transaction = new(Build.A.Transaction + Transaction tx = Build.A.Transaction .WithGasLimit(100000) .WithBlobVersionedHashes(isNull ? null : []) .To(TestItem.AddressA) .SignedAndResolved(TestItem.PrivateKeyA) - .TestObject); + .TestObject; + BlobTransactionForRpc transaction = new(tx, new(tx.ChainId ?? BlockchainIds.Mainnet)); transaction.GasPrice = null; string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); @@ -510,11 +579,15 @@ public async Task Eth_call_no_blobs_in_blob_tx(bool isNull) public async Task Eth_call_maxFeePerBlobGas_is_zero() { using Context ctx = await Context.Create(); - BlobTransactionForRpc transaction = new(Build.A.Transaction + byte[] validHash = new byte[32]; + validHash[0] = 0x01; // KZG version + Transaction tx = Build.A.Transaction .WithGasLimit(100000) - .WithBlobVersionedHashes([[]]) + .WithBlobVersionedHashes([validHash]) + .To(TestItem.AddressA) .SignedAndResolved(TestItem.PrivateKeyA) - .TestObject); + .TestObject; + BlobTransactionForRpc transaction = new(tx, new(tx.ChainId ?? BlockchainIds.Mainnet)); transaction.MaxFeePerBlobGas = 0; transaction.GasPrice = null; string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); @@ -530,13 +603,16 @@ public async Task Eth_call_missing_to_in_blob_tx() byte[] code = Prepare.EvmCode .Op(Instruction.STOP) .Done; - BlobTransactionForRpc transaction = new(Build.A.Transaction + byte[] validHash = new byte[32]; + validHash[0] = 0x01; // KZG version + Transaction tx = Build.A.Transaction .WithData(code) .WithGasLimit(100000) .WithMaxFeePerBlobGas(1) - .WithBlobVersionedHashes([[]]) + .WithBlobVersionedHashes([validHash]) .SignedAndResolved(TestItem.PrivateKeyA) - .TestObject); + .TestObject; + BlobTransactionForRpc transaction = new(tx, new(tx.ChainId ?? BlockchainIds.Mainnet)); transaction.To = null; transaction.GasPrice = null; string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); @@ -549,10 +625,11 @@ public async Task Eth_call_missing_to_in_blob_tx() public async Task Eth_call_maxFeePerGas_is_zero() { using Context ctx = await Context.Create(); - EIP1559TransactionForRpc transaction = new(Build.A.Transaction + Transaction tx = Build.A.Transaction .WithGasLimit(100000) .SignedAndResolved(TestItem.PrivateKeyA) - .TestObject); + .TestObject; + EIP1559TransactionForRpc transaction = new(tx, new(tx.ChainId ?? BlockchainIds.Mainnet)); transaction.MaxFeePerGas = 0; transaction.GasPrice = null; string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); @@ -565,19 +642,66 @@ public async Task Eth_call_maxFeePerGas_is_zero() public async Task Eth_call_maxFeePerGas_smaller_then_maxPriorityFeePerGas() { using Context ctx = await Context.Create(); - EIP1559TransactionForRpc transaction = new(Build.A.Transaction + Transaction tx = Build.A.Transaction .WithGasLimit(100000) .SignedAndResolved(TestItem.PrivateKeyA) - .TestObject); - transaction.MaxFeePerGas = 1; - transaction.MaxPriorityFeePerGas = 2; - transaction.GasPrice = null; + .TestObject; + + EIP1559TransactionForRpc transaction = new(tx, new(tx.ChainId ?? BlockchainIds.Mainnet)) + { + MaxFeePerGas = 1, + MaxPriorityFeePerGas = 2, + GasPrice = null + }; + string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); Assert.That( serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"maxFeePerGas (1) < maxPriorityFeePerGas (2)\"},\"id\":67}")); } + [TestCase(null, RpcTransactionErrors.InvalidBlobVersionedHashSize, TestName = "BlobVersionedHash null")] + [TestCase(new byte[] { 0x01 }, RpcTransactionErrors.InvalidBlobVersionedHashSize, TestName = "BlobVersionedHash too short")] + [TestCase(new byte[] { 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, RpcTransactionErrors.InvalidBlobVersionedHashSize, TestName = "BlobVersionedHash too long")] + [TestCase(new byte[] { 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, RpcTransactionErrors.InvalidBlobVersionedHashVersion, TestName = "BlobVersionedHash invalid version 0x00")] + [TestCase(new byte[] { 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, RpcTransactionErrors.InvalidBlobVersionedHashVersion, TestName = "BlobVersionedHash invalid version 0x02")] + public async Task Eth_call_invalid_blob_versioned_hash(byte[]? hash, string expectedError) + { + using Context ctx = await Context.Create(); + Transaction tx = Build.A.Transaction + .WithGasLimit(100000) + .WithMaxFeePerBlobGas(1) + .WithBlobVersionedHashes([hash!]) + .To(TestItem.AddressA) + .SignedAndResolved(TestItem.PrivateKeyA) + .TestObject; + + BlobTransactionForRpc transaction = new(tx, new(tx.ChainId ?? BlockchainIds.Mainnet)) + { + GasPrice = null + }; + string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); + + Assert.That(serialized, Is.EqualTo($"{{\"jsonrpc\":\"2.0\",\"error\":{{\"code\":-32000,\"message\":\"{expectedError}\"}},\"id\":67}}")); + } + + [Test] + public async Task Eth_call_bubbles_up_precompile_errors() + { + using Context ctx = await Context.Create(new SingleReleaseSpecProvider(Osaka.Instance, BlockchainIds.Mainnet, BlockchainIds.Mainnet)); + Transaction tx = Build.A.Transaction + .WithData(Bytes.FromHexString("0x00000000000000000000000000000000000000000000000000000000000004010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000101")) + .WithTo(new Address("0x0000000000000000000000000000000000000005")) + .WithGasLimit(1000000) + .SignedAndResolved(TestItem.PrivateKeyA) + .TestObject; + LegacyTransactionForRpc transaction = new(tx, new(tx.ChainId ?? BlockchainIds.Mainnet)); + + string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); + + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"Precompile MODEXP failed with error: one or more of base/exponent/modulus length exceeded 1024 bytes\",\"data\":\"0x\"},\"id\":67}")); + } + private static async Task TestEthCallOutOfGas(Context ctx, long? specifiedGasLimit, long expectedGasLimit) { string gasParam = specifiedGasLimit.HasValue ? $", \"gas\": \"0x{specifiedGasLimit.Value:X}\"" : ""; @@ -586,6 +710,6 @@ private static async Task TestEthCallOutOfGas(Context ctx, long? specifiedGasLim string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); JToken.Parse(serialized).Should().BeEquivalentTo( - $"{{\"jsonrpc\":\"2.0\",\"error\":{{\"code\":-32000,\"message\":\"out of gas\",\"data\":\"0x\"}},\"id\":67}}"); + $"{{\"jsonrpc\":\"2.0\",\"error\":{{\"code\":-32000,\"message\":\"Block gas limit exceeded\"}},\"id\":67}}"); } } 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 0109a2beec6a..aa61625cdf48 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.GasPrice.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.GasPrice.cs index 5c56e07b3eea..de698303e04e 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.GasPrice.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.GasPrice.cs @@ -16,7 +16,6 @@ using Nethermind.Logging; using Nethermind.Specs; using Nethermind.Specs.Forks; -using Nethermind.Specs.Test; using NUnit.Framework; using static Nethermind.JsonRpc.Test.Modules.GasPriceOracleTests; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs index 484cfe255e80..a7647dc34534 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; @@ -81,7 +82,7 @@ public async Task Eth_get_transaction_by_block_hash_and_index() { using Context ctx = await Context.Create(); string serialized = await ctx.Test.TestEthRpc("eth_getTransactionByBlockHashAndIndex", ctx.Test.BlockTree.FindHeadBlock()!.Hash!.ToString(), "1"); - JToken.Parse(serialized).Should().BeEquivalentTo("""{"jsonrpc":"2.0","result":{"hash":"0x7126cf20a0ad8bd51634837d9049615c34c1bff5e1a54e5663f7e23109bff48b","nonce":"0x2","blockHash":"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3","blockNumber":"0x3","transactionIndex":"0x1","from":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","to":"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358","value":"0x1","gasPrice":"0x1","gas":"0x5208","input":"0x","type":"0x0","v":"0x25","s":"0x575361bb330bf38b9a89dd8279d42a20d34edeaeede9739a7c2bdcbe3242d7bb","r":"0xe7c5ff3cba254c4fe8f9f12c3f202150bb9a0aebeee349ff2f4acb23585f56bd"},"id":67}"""); + JToken.Parse(serialized).Should().BeEquivalentTo("""{"jsonrpc":"2.0","result":{"hash":"0x7126cf20a0ad8bd51634837d9049615c34c1bff5e1a54e5663f7e23109bff48b","nonce":"0x2","blockHash":"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3","blockNumber":"0x3","blockTimestamp":"0x5e47e919","transactionIndex":"0x1","from":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","to":"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358","value":"0x1","gasPrice":"0x1","gas":"0x5208","input":"0x","chainId":"0x1","type":"0x0","v":"0x25","s":"0x575361bb330bf38b9a89dd8279d42a20d34edeaeede9739a7c2bdcbe3242d7bb","r":"0xe7c5ff3cba254c4fe8f9f12c3f202150bb9a0aebeee349ff2f4acb23585f56bd"},"id":67}"""); } [Test] @@ -89,7 +90,7 @@ public async Task Eth_get_transaction_by_hash() { using Context ctx = await Context.Create(); string serialized = await ctx.Test.TestEthRpc("eth_getTransactionByHash", ctx.Test.BlockTree.FindHeadBlock()!.Transactions.Last().Hash!.ToString()); - JToken.Parse(serialized).Should().BeEquivalentTo("""{"jsonrpc":"2.0","result":{"hash":"0x7126cf20a0ad8bd51634837d9049615c34c1bff5e1a54e5663f7e23109bff48b","nonce":"0x2","blockHash":"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3","blockNumber":"0x3","transactionIndex":"0x1","from":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","to":"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358","value":"0x1","gasPrice":"0x1","gas":"0x5208","input":"0x","type":"0x0","v":"0x25","s":"0x575361bb330bf38b9a89dd8279d42a20d34edeaeede9739a7c2bdcbe3242d7bb","r":"0xe7c5ff3cba254c4fe8f9f12c3f202150bb9a0aebeee349ff2f4acb23585f56bd"},"id":67}"""); + JToken.Parse(serialized).Should().BeEquivalentTo("""{"jsonrpc":"2.0","result":{"hash":"0x7126cf20a0ad8bd51634837d9049615c34c1bff5e1a54e5663f7e23109bff48b","nonce":"0x2","blockHash":"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3","blockNumber":"0x3","blockTimestamp":"0x5e47e919","transactionIndex":"0x1","from":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","to":"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358","value":"0x1","gasPrice":"0x1","gas":"0x5208","input":"0x","chainId":"0x1","type":"0x0","v":"0x25","s":"0x575361bb330bf38b9a89dd8279d42a20d34edeaeede9739a7c2bdcbe3242d7bb","r":"0xe7c5ff3cba254c4fe8f9f12c3f202150bb9a0aebeee349ff2f4acb23585f56bd"},"id":67}"""); } [Test] @@ -140,7 +141,7 @@ public async Task Eth_pending_transactions() using Context ctx = await Context.Create(); ctx.Test.AddTransactions(Build.A.Transaction.SignedAndResolved(TestItem.PrivateKeyD).TestObject); string serialized = await ctx.Test.TestEthRpc("eth_pendingTransactions"); - JToken.Parse(serialized).Should().BeEquivalentTo("""{"jsonrpc":"2.0","result":[{"hash":"0x190d9a78dbc61b1856162ab909976a1b28ba4a41ee041341576ea69686cd3b29","nonce":"0x0","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":null,"transactionIndex":null,"from":"0x475674cb523a0a2736b7f7534390288fce16982c","to":"0x0000000000000000000000000000000000000000","value":"0x1","gasPrice":"0x1","gas":"0x5208","input":"0x","type":"0x0","v":"0x26","s":"0x2d04e55699fa32e6b65a22189f7571f5030d636d7d44a8b53fe016a2c3ecde24","r":"0xda3978c3a1430bd902cf5bbca73c5a1eca019b3f003c95ee16657fd0bb89534c"}],"id":67}"""); + JToken.Parse(serialized).Should().BeEquivalentTo("""{"jsonrpc":"2.0","result":[{"hash":"0x190d9a78dbc61b1856162ab909976a1b28ba4a41ee041341576ea69686cd3b29","nonce":"0x0","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":null,"blockTimestamp":null,"transactionIndex":null,"from":"0x475674cb523a0a2736b7f7534390288fce16982c","to":"0x0000000000000000000000000000000000000000","value":"0x1","gasPrice":"0x1","gas":"0x5208","input":"0x","chainId":"0x1","type":"0x0","v":"0x26","s":"0x2d04e55699fa32e6b65a22189f7571f5030d636d7d44a8b53fe016a2c3ecde24","r":"0xda3978c3a1430bd902cf5bbca73c5a1eca019b3f003c95ee16657fd0bb89534c"}],"id":67}"""); } [Test] @@ -174,15 +175,15 @@ public async Task Eth_getBlockReceipts() public async Task Eth_get_transaction_by_block_number_and_index() { using Context ctx = await Context.Create(); - string serialized = await ctx.Test.TestEthRpc("eth_getTransactionByBlockNumberAndIndex", ctx.Test.BlockTree.FindHeadBlock()!.Number.ToString(), "1"); - JToken.Parse(serialized).Should().BeEquivalentTo("""{"jsonrpc":"2.0","result":{"hash":"0x7126cf20a0ad8bd51634837d9049615c34c1bff5e1a54e5663f7e23109bff48b","nonce":"0x2","blockHash":"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3","blockNumber":"0x3","transactionIndex":"0x1","from":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","to":"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358","value":"0x1","gasPrice":"0x1","gas":"0x5208","input":"0x","type":"0x0","v":"0x25","s":"0x575361bb330bf38b9a89dd8279d42a20d34edeaeede9739a7c2bdcbe3242d7bb","r":"0xe7c5ff3cba254c4fe8f9f12c3f202150bb9a0aebeee349ff2f4acb23585f56bd"},"id":67}"""); + string serialized = await ctx.Test.TestEthRpc("eth_getTransactionByBlockNumberAndIndex", ctx.Test.BlockTree.FindHeadBlock()!.Number.ToHexString(true), "1"); + JToken.Parse(serialized).Should().BeEquivalentTo("""{"jsonrpc":"2.0","result":{"hash":"0x7126cf20a0ad8bd51634837d9049615c34c1bff5e1a54e5663f7e23109bff48b","nonce":"0x2","blockHash":"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3","blockNumber":"0x3","blockTimestamp":"0x5e47e919","transactionIndex":"0x1","from":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","to":"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358","value":"0x1","gasPrice":"0x1","gas":"0x5208","input":"0x","chainId":"0x1","type":"0x0","v":"0x25","s":"0x575361bb330bf38b9a89dd8279d42a20d34edeaeede9739a7c2bdcbe3242d7bb","r":"0xe7c5ff3cba254c4fe8f9f12c3f202150bb9a0aebeee349ff2f4acb23585f56bd"},"id":67}"""); } [Test] public async Task Eth_get_transaction_by_block_number_and_index_out_of_bounds() { using Context ctx = await Context.Create(); - string serialized = await ctx.Test.TestEthRpc("eth_getTransactionByBlockNumberAndIndex", ctx.Test.BlockTree.FindHeadBlock()!.Number.ToString(), "100"); + string serialized = await ctx.Test.TestEthRpc("eth_getTransactionByBlockNumberAndIndex", ctx.Test.BlockTree.FindHeadBlock()!.Number.ToHexString(true), "100"); Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":null,\"id\":67}")); } @@ -202,7 +203,7 @@ public async Task Eth_get_uncle_by_block_number_and_index(bool eip1559, string e IBlockTree blockTree = Substitute.For(); blockTree.FindBlock((BlockParameter?)null).ReturnsForAnyArgs(block); ctx.Test = await TestRpcBlockchain.ForTest(SealEngineType.NethDev).WithBlockFinder(blockTree).Build(specProvider); - string serialized = await ctx.Test!.TestEthRpc("eth_getUncleByBlockNumberAndIndex", ctx.Test.BlockTree.FindHeadBlock()!.Number.ToString(), "1"); + string serialized = await ctx.Test!.TestEthRpc("eth_getUncleByBlockNumberAndIndex", ctx.Test.BlockTree.FindHeadBlock()!.Number.ToHexString(true), "1"); Assert.That(serialized, Is.EqualTo(expectedJson), serialized?.Replace("\"", "\\\"")); } @@ -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); } @@ -239,7 +240,7 @@ public async Task Eth_get_uncle_count_by_block_hash() public async Task Eth_get_uncle_count_by_block_number() { using Context ctx = await Context.Create(); - string serialized = await ctx.Test.TestEthRpc("eth_getUncleCountByBlockNumber", ctx.Test.BlockTree.FindHeadBlock()!.Number.ToString()); + string serialized = await ctx.Test.TestEthRpc("eth_getUncleCountByBlockNumber", ctx.Test.BlockTree.FindHeadBlock()!.Number.ToHexString(true)); Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":\"0x0\",\"id\":67}"), serialized.Replace("\"", "\\\"")); } @@ -309,6 +310,14 @@ public async Task Eth_get_filter_changes_missing() Assert.That(serialized2, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"Filter not found\"},\"id\":67}")); } + [Test] + public async Task Eth_get_filter_changes_too_big() + { + using Context ctx = await Context.Create(); + string serialized2 = await ctx.Test.TestEthRpc("eth_getFilterChanges", ((UInt256)uint.MaxValue + 1).ToHexString(true)); + Assert.That(serialized2, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"Filter not found\"},\"id\":67}")); + } + [Test] public async Task Eth_uninstall_filter() { @@ -571,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); @@ -583,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(); @@ -630,7 +639,7 @@ static IEnumerable GetLogs(CancellationToken ct) serialized.Should().Be("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32016,\"message\":\"eth_getLogs request was canceled due to enabled timeout.\"},\"id\":67}"); } - [TestCase("{\"fromBlock\":\"earliest\",\"toBlock\":\"latest\"}", "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32001,\"message\":\"resource not found message\"},\"id\":67}")] + [TestCase("{\"fromBlock\":\"earliest\",\"toBlock\":\"latest\"}", "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"resource not found message\"},\"id\":67}")] public async Task Eth_get_logs_with_resourceNotFound(string parameter, string expected) { using Context ctx = await Context.Create(); @@ -706,7 +715,7 @@ public async Task Eth_get_block_by_hash_null() } [TestCase("0x71eac5e72c3b64431c246173352a8c625c8434d944eb5f3f58204fec3ec36b54", false, "{\"jsonrpc\":\"2.0\",\"result\":{\"difficulty\":\"0x1\",\"extraData\":\"0x4e65746865726d696e64\",\"gasLimit\":\"0x3d0900\",\"gasUsed\":\"0xa410\",\"hash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"miner\":\"0x475674cb523a0a2736b7f7534390288fce16982c\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"nonce\":\"0x0000000000000000\",\"number\":\"0x3\",\"parentHash\":\"0x49e7d7466be0927347ff2f654c014a768b5a5fcd8c483635210466dd0d6d204c\",\"receiptsRoot\":\"0xd95b673818fa493deec414e01e610d97ee287c9421c8eff4102b1647c1a184e4\",\"sha3Uncles\":\"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347\",\"size\":\"0x2cb\",\"stateRoot\":\"0x4e786afc8bed76b7299973ca70022b367cbb94c14ec30e9e7273b31b6b968de9\",\"totalDifficulty\":\"0xf4243\",\"timestamp\":\"0x5e47e919\",\"transactions\":[\"0x681c2b6f99e37fd6fe6046db8b51ec3460d699cacd6a376143fd5842ac50621f\",\"0x7126cf20a0ad8bd51634837d9049615c34c1bff5e1a54e5663f7e23109bff48b\"],\"transactionsRoot\":\"0x2e6e6deb19d24bd48eda6071ab38b1bae64c15ef1998c96f0d153711d3a3efc7\",\"uncles\":[]},\"id\":67}")] - [TestCase("0x71eac5e72c3b64431c246173352a8c625c8434d944eb5f3f58204fec3ec36b54", true, "{\"jsonrpc\":\"2.0\",\"result\":{\"difficulty\":\"0x1\",\"extraData\":\"0x4e65746865726d696e64\",\"gasLimit\":\"0x3d0900\",\"gasUsed\":\"0xa410\",\"hash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"miner\":\"0x475674cb523a0a2736b7f7534390288fce16982c\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"nonce\":\"0x0000000000000000\",\"number\":\"0x3\",\"parentHash\":\"0x49e7d7466be0927347ff2f654c014a768b5a5fcd8c483635210466dd0d6d204c\",\"receiptsRoot\":\"0xd95b673818fa493deec414e01e610d97ee287c9421c8eff4102b1647c1a184e4\",\"sha3Uncles\":\"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347\",\"size\":\"0x2cb\",\"stateRoot\":\"0x4e786afc8bed76b7299973ca70022b367cbb94c14ec30e9e7273b31b6b968de9\",\"totalDifficulty\":\"0xf4243\",\"timestamp\":\"0x5e47e919\",\"transactions\":[{\"hash\":\"0x681c2b6f99e37fd6fe6046db8b51ec3460d699cacd6a376143fd5842ac50621f\",\"nonce\":\"0x1\",\"blockHash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"blockNumber\":\"0x3\",\"transactionIndex\":\"0x0\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"value\":\"0x1\",\"gasPrice\":\"0x1\",\"gas\":\"0x5208\",\"input\":\"0x\",\"type\":\"0x0\",\"v\":\"0x25\",\"s\":\"0x575361bb330bf38b9a89dd8279d42a20d34edeaeede9739a7c2bdcbe3242d7bb\",\"r\":\"0xe7c5ff3cba254c4fe8f9f12c3f202150bb9a0aebeee349ff2f4acb23585f56bd\"},{\"hash\":\"0x7126cf20a0ad8bd51634837d9049615c34c1bff5e1a54e5663f7e23109bff48b\",\"nonce\":\"0x2\",\"blockHash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"blockNumber\":\"0x3\",\"transactionIndex\":\"0x1\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"value\":\"0x1\",\"gasPrice\":\"0x1\",\"gas\":\"0x5208\",\"input\":\"0x\",\"type\":\"0x0\",\"v\":\"0x25\",\"s\":\"0x575361bb330bf38b9a89dd8279d42a20d34edeaeede9739a7c2bdcbe3242d7bb\",\"r\":\"0xe7c5ff3cba254c4fe8f9f12c3f202150bb9a0aebeee349ff2f4acb23585f56bd\"}],\"transactionsRoot\":\"0x2e6e6deb19d24bd48eda6071ab38b1bae64c15ef1998c96f0d153711d3a3efc7\",\"uncles\":[]},\"id\":67}")] + [TestCase("0x71eac5e72c3b64431c246173352a8c625c8434d944eb5f3f58204fec3ec36b54", true, "{\"jsonrpc\":\"2.0\",\"result\":{\"difficulty\":\"0x1\",\"extraData\":\"0x4e65746865726d696e64\",\"gasLimit\":\"0x3d0900\",\"gasUsed\":\"0xa410\",\"hash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"miner\":\"0x475674cb523a0a2736b7f7534390288fce16982c\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"nonce\":\"0x0000000000000000\",\"number\":\"0x3\",\"parentHash\":\"0x49e7d7466be0927347ff2f654c014a768b5a5fcd8c483635210466dd0d6d204c\",\"receiptsRoot\":\"0xd95b673818fa493deec414e01e610d97ee287c9421c8eff4102b1647c1a184e4\",\"sha3Uncles\":\"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347\",\"size\":\"0x2cb\",\"stateRoot\":\"0x4e786afc8bed76b7299973ca70022b367cbb94c14ec30e9e7273b31b6b968de9\",\"totalDifficulty\":\"0xf4243\",\"timestamp\":\"0x5e47e919\",\"transactions\":[{\"hash\":\"0x681c2b6f99e37fd6fe6046db8b51ec3460d699cacd6a376143fd5842ac50621f\",\"nonce\":\"0x1\",\"blockHash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"blockNumber\":\"0x3\",\"blockTimestamp\":\"0x5e47e919\",\"transactionIndex\":\"0x0\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"value\":\"0x1\",\"gasPrice\":\"0x1\",\"gas\":\"0x5208\",\"input\":\"0x\",\"chainId\":\"0x1\",\"type\":\"0x0\",\"v\":\"0x25\",\"s\":\"0x575361bb330bf38b9a89dd8279d42a20d34edeaeede9739a7c2bdcbe3242d7bb\",\"r\":\"0xe7c5ff3cba254c4fe8f9f12c3f202150bb9a0aebeee349ff2f4acb23585f56bd\"},{\"hash\":\"0x7126cf20a0ad8bd51634837d9049615c34c1bff5e1a54e5663f7e23109bff48b\",\"nonce\":\"0x2\",\"blockHash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"blockNumber\":\"0x3\",\"blockTimestamp\":\"0x5e47e919\",\"transactionIndex\":\"0x1\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"value\":\"0x1\",\"gasPrice\":\"0x1\",\"gas\":\"0x5208\",\"input\":\"0x\",\"chainId\":\"0x1\",\"type\":\"0x0\",\"v\":\"0x25\",\"s\":\"0x575361bb330bf38b9a89dd8279d42a20d34edeaeede9739a7c2bdcbe3242d7bb\",\"r\":\"0xe7c5ff3cba254c4fe8f9f12c3f202150bb9a0aebeee349ff2f4acb23585f56bd\"}],\"transactionsRoot\":\"0x2e6e6deb19d24bd48eda6071ab38b1bae64c15ef1998c96f0d153711d3a3efc7\",\"uncles\":[]},\"id\":67}")] public async Task Eth_get_block_by_hash_with_tx(string blockParameter, bool withTxData, string expectedResult) { using Context ctx = await Context.Create(); @@ -715,8 +724,8 @@ public async Task Eth_get_block_by_hash_with_tx(string blockParameter, bool with } [TestCase(false, "earliest", "{\"jsonrpc\":\"2.0\",\"result\":{\"difficulty\":\"0xf4240\",\"extraData\":\"0x010203\",\"gasLimit\":\"0x3d0900\",\"gasUsed\":\"0x0\",\"hash\":\"0x2167088a0f0de66028d2b728235af6d467108c1750c3e11a8f6e6cd60fddb0e4\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"miner\":\"0x0000000000000000000000000000000000000000\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"nonce\":\"0x00000000000003e8\",\"number\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"receiptsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"sha3Uncles\":\"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347\",\"size\":\"0x201\",\"stateRoot\":\"0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f\",\"totalDifficulty\":\"0xf4240\",\"timestamp\":\"0xf4240\",\"transactions\":[],\"transactionsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"uncles\":[]},\"id\":67}")] - [TestCase(false, "latest", "{\"jsonrpc\":\"2.0\",\"result\":{\"difficulty\":\"0x1\",\"extraData\":\"0x4e65746865726d696e64\",\"gasLimit\":\"0x3d0900\",\"gasUsed\":\"0xa410\",\"hash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"miner\":\"0x475674cb523a0a2736b7f7534390288fce16982c\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"nonce\":\"0x0000000000000000\",\"number\":\"0x3\",\"parentHash\":\"0x49e7d7466be0927347ff2f654c014a768b5a5fcd8c483635210466dd0d6d204c\",\"receiptsRoot\":\"0xd95b673818fa493deec414e01e610d97ee287c9421c8eff4102b1647c1a184e4\",\"sha3Uncles\":\"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347\",\"size\":\"0x2cb\",\"stateRoot\":\"0x4e786afc8bed76b7299973ca70022b367cbb94c14ec30e9e7273b31b6b968de9\",\"totalDifficulty\":\"0xf4243\",\"timestamp\":\"0x5e47e919\",\"transactions\":[{\"hash\":\"0x681c2b6f99e37fd6fe6046db8b51ec3460d699cacd6a376143fd5842ac50621f\",\"nonce\":\"0x1\",\"blockHash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"blockNumber\":\"0x3\",\"transactionIndex\":\"0x0\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"value\":\"0x1\",\"gasPrice\":\"0x1\",\"gas\":\"0x5208\",\"input\":\"0x\",\"type\":\"0x0\",\"v\":\"0x25\",\"s\":\"0x575361bb330bf38b9a89dd8279d42a20d34edeaeede9739a7c2bdcbe3242d7bb\",\"r\":\"0xe7c5ff3cba254c4fe8f9f12c3f202150bb9a0aebeee349ff2f4acb23585f56bd\"},{\"hash\":\"0x7126cf20a0ad8bd51634837d9049615c34c1bff5e1a54e5663f7e23109bff48b\",\"nonce\":\"0x2\",\"blockHash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"blockNumber\":\"0x3\",\"transactionIndex\":\"0x1\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"value\":\"0x1\",\"gasPrice\":\"0x1\",\"gas\":\"0x5208\",\"input\":\"0x\",\"type\":\"0x0\",\"v\":\"0x25\",\"s\":\"0x575361bb330bf38b9a89dd8279d42a20d34edeaeede9739a7c2bdcbe3242d7bb\",\"r\":\"0xe7c5ff3cba254c4fe8f9f12c3f202150bb9a0aebeee349ff2f4acb23585f56bd\"}],\"transactionsRoot\":\"0x2e6e6deb19d24bd48eda6071ab38b1bae64c15ef1998c96f0d153711d3a3efc7\",\"uncles\":[]},\"id\":67}")] - [TestCase(false, "pending", "{\"jsonrpc\":\"2.0\",\"result\":{\"difficulty\":\"0x1\",\"extraData\":\"0x4e65746865726d696e64\",\"gasLimit\":\"0x3d0900\",\"gasUsed\":\"0xa410\",\"hash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"miner\":\"0x475674cb523a0a2736b7f7534390288fce16982c\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"nonce\":\"0x0000000000000000\",\"number\":\"0x3\",\"parentHash\":\"0x49e7d7466be0927347ff2f654c014a768b5a5fcd8c483635210466dd0d6d204c\",\"receiptsRoot\":\"0xd95b673818fa493deec414e01e610d97ee287c9421c8eff4102b1647c1a184e4\",\"sha3Uncles\":\"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347\",\"size\":\"0x2cb\",\"stateRoot\":\"0x4e786afc8bed76b7299973ca70022b367cbb94c14ec30e9e7273b31b6b968de9\",\"totalDifficulty\":\"0xf4243\",\"timestamp\":\"0x5e47e919\",\"transactions\":[{\"hash\":\"0x681c2b6f99e37fd6fe6046db8b51ec3460d699cacd6a376143fd5842ac50621f\",\"nonce\":\"0x1\",\"blockHash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"blockNumber\":\"0x3\",\"transactionIndex\":\"0x0\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"value\":\"0x1\",\"gasPrice\":\"0x1\",\"gas\":\"0x5208\",\"input\":\"0x\",\"type\":\"0x0\",\"v\":\"0x25\",\"s\":\"0x575361bb330bf38b9a89dd8279d42a20d34edeaeede9739a7c2bdcbe3242d7bb\",\"r\":\"0xe7c5ff3cba254c4fe8f9f12c3f202150bb9a0aebeee349ff2f4acb23585f56bd\"},{\"hash\":\"0x7126cf20a0ad8bd51634837d9049615c34c1bff5e1a54e5663f7e23109bff48b\",\"nonce\":\"0x2\",\"blockHash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"blockNumber\":\"0x3\",\"transactionIndex\":\"0x1\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"value\":\"0x1\",\"gasPrice\":\"0x1\",\"gas\":\"0x5208\",\"input\":\"0x\",\"type\":\"0x0\",\"v\":\"0x25\",\"s\":\"0x575361bb330bf38b9a89dd8279d42a20d34edeaeede9739a7c2bdcbe3242d7bb\",\"r\":\"0xe7c5ff3cba254c4fe8f9f12c3f202150bb9a0aebeee349ff2f4acb23585f56bd\"}],\"transactionsRoot\":\"0x2e6e6deb19d24bd48eda6071ab38b1bae64c15ef1998c96f0d153711d3a3efc7\",\"uncles\":[]},\"id\":67}")] + [TestCase(false, "latest", "{\"jsonrpc\":\"2.0\",\"result\":{\"difficulty\":\"0x1\",\"extraData\":\"0x4e65746865726d696e64\",\"gasLimit\":\"0x3d0900\",\"gasUsed\":\"0xa410\",\"hash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"miner\":\"0x475674cb523a0a2736b7f7534390288fce16982c\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"nonce\":\"0x0000000000000000\",\"number\":\"0x3\",\"parentHash\":\"0x49e7d7466be0927347ff2f654c014a768b5a5fcd8c483635210466dd0d6d204c\",\"receiptsRoot\":\"0xd95b673818fa493deec414e01e610d97ee287c9421c8eff4102b1647c1a184e4\",\"sha3Uncles\":\"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347\",\"size\":\"0x2cb\",\"stateRoot\":\"0x4e786afc8bed76b7299973ca70022b367cbb94c14ec30e9e7273b31b6b968de9\",\"totalDifficulty\":\"0xf4243\",\"timestamp\":\"0x5e47e919\",\"transactions\":[{\"hash\":\"0x681c2b6f99e37fd6fe6046db8b51ec3460d699cacd6a376143fd5842ac50621f\",\"nonce\":\"0x1\",\"blockHash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"blockNumber\":\"0x3\",\"blockTimestamp\":\"0x5e47e919\",\"transactionIndex\":\"0x0\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"value\":\"0x1\",\"gasPrice\":\"0x1\",\"gas\":\"0x5208\",\"input\":\"0x\",\"chainId\":\"0x1\",\"type\":\"0x0\",\"v\":\"0x25\",\"s\":\"0x575361bb330bf38b9a89dd8279d42a20d34edeaeede9739a7c2bdcbe3242d7bb\",\"r\":\"0xe7c5ff3cba254c4fe8f9f12c3f202150bb9a0aebeee349ff2f4acb23585f56bd\"},{\"hash\":\"0x7126cf20a0ad8bd51634837d9049615c34c1bff5e1a54e5663f7e23109bff48b\",\"nonce\":\"0x2\",\"blockHash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"blockNumber\":\"0x3\",\"blockTimestamp\":\"0x5e47e919\",\"transactionIndex\":\"0x1\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"value\":\"0x1\",\"gasPrice\":\"0x1\",\"gas\":\"0x5208\",\"input\":\"0x\",\"chainId\":\"0x1\",\"type\":\"0x0\",\"v\":\"0x25\",\"s\":\"0x575361bb330bf38b9a89dd8279d42a20d34edeaeede9739a7c2bdcbe3242d7bb\",\"r\":\"0xe7c5ff3cba254c4fe8f9f12c3f202150bb9a0aebeee349ff2f4acb23585f56bd\"}],\"transactionsRoot\":\"0x2e6e6deb19d24bd48eda6071ab38b1bae64c15ef1998c96f0d153711d3a3efc7\",\"uncles\":[]},\"id\":67}")] + [TestCase(false, "pending", "{\"jsonrpc\":\"2.0\",\"result\":{\"difficulty\":\"0x1\",\"extraData\":\"0x4e65746865726d696e64\",\"gasLimit\":\"0x3d0900\",\"gasUsed\":\"0xa410\",\"hash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"miner\":\"0x475674cb523a0a2736b7f7534390288fce16982c\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"nonce\":\"0x0000000000000000\",\"number\":\"0x3\",\"parentHash\":\"0x49e7d7466be0927347ff2f654c014a768b5a5fcd8c483635210466dd0d6d204c\",\"receiptsRoot\":\"0xd95b673818fa493deec414e01e610d97ee287c9421c8eff4102b1647c1a184e4\",\"sha3Uncles\":\"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347\",\"size\":\"0x2cb\",\"stateRoot\":\"0x4e786afc8bed76b7299973ca70022b367cbb94c14ec30e9e7273b31b6b968de9\",\"totalDifficulty\":\"0xf4243\",\"timestamp\":\"0x5e47e919\",\"transactions\":[{\"hash\":\"0x681c2b6f99e37fd6fe6046db8b51ec3460d699cacd6a376143fd5842ac50621f\",\"nonce\":\"0x1\",\"blockHash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"blockNumber\":\"0x3\",\"blockTimestamp\":\"0x5e47e919\",\"transactionIndex\":\"0x0\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"value\":\"0x1\",\"gasPrice\":\"0x1\",\"gas\":\"0x5208\",\"input\":\"0x\",\"chainId\":\"0x1\",\"type\":\"0x0\",\"v\":\"0x25\",\"s\":\"0x575361bb330bf38b9a89dd8279d42a20d34edeaeede9739a7c2bdcbe3242d7bb\",\"r\":\"0xe7c5ff3cba254c4fe8f9f12c3f202150bb9a0aebeee349ff2f4acb23585f56bd\"},{\"hash\":\"0x7126cf20a0ad8bd51634837d9049615c34c1bff5e1a54e5663f7e23109bff48b\",\"nonce\":\"0x2\",\"blockHash\":\"0x29f141925d2d8e357ae5b6040c97aa12d7ac6dfcbe2b20e7b616d8907ac8e1f3\",\"blockNumber\":\"0x3\",\"blockTimestamp\":\"0x5e47e919\",\"transactionIndex\":\"0x1\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"value\":\"0x1\",\"gasPrice\":\"0x1\",\"gas\":\"0x5208\",\"input\":\"0x\",\"chainId\":\"0x1\",\"type\":\"0x0\",\"v\":\"0x25\",\"s\":\"0x575361bb330bf38b9a89dd8279d42a20d34edeaeede9739a7c2bdcbe3242d7bb\",\"r\":\"0xe7c5ff3cba254c4fe8f9f12c3f202150bb9a0aebeee349ff2f4acb23585f56bd\"}],\"transactionsRoot\":\"0x2e6e6deb19d24bd48eda6071ab38b1bae64c15ef1998c96f0d153711d3a3efc7\",\"uncles\":[]},\"id\":67}")] [TestCase(false, "0x0", "{\"jsonrpc\":\"2.0\",\"result\":{\"difficulty\":\"0xf4240\",\"extraData\":\"0x010203\",\"gasLimit\":\"0x3d0900\",\"gasUsed\":\"0x0\",\"hash\":\"0x2167088a0f0de66028d2b728235af6d467108c1750c3e11a8f6e6cd60fddb0e4\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"miner\":\"0x0000000000000000000000000000000000000000\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"nonce\":\"0x00000000000003e8\",\"number\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"receiptsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"sha3Uncles\":\"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347\",\"size\":\"0x201\",\"stateRoot\":\"0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f\",\"totalDifficulty\":\"0xf4240\",\"timestamp\":\"0xf4240\",\"transactions\":[],\"transactionsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"uncles\":[]},\"id\":67}")] [TestCase(true, "earliest", "{\"jsonrpc\":\"2.0\",\"result\":{\"difficulty\":\"0xf4240\",\"extraData\":\"0x010203\",\"gasLimit\":\"0x3d0900\",\"gasUsed\":\"0x0\",\"hash\":\"0x2167088a0f0de66028d2b728235af6d467108c1750c3e11a8f6e6cd60fddb0e4\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"miner\":\"0x0000000000000000000000000000000000000000\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"nonce\":\"0x00000000000003e8\",\"number\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"receiptsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"sha3Uncles\":\"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347\",\"size\":\"0x201\",\"stateRoot\":\"0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f\",\"totalDifficulty\":\"0xf4240\",\"timestamp\":\"0xf4240\",\"baseFeePerGas\":\"0x0\",\"transactions\":[],\"transactionsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"uncles\":[]},\"id\":67}")] [TestCase(true, "latest", "{\"jsonrpc\":\"2.0\",\"result\":{\"difficulty\":\"0x1\",\"extraData\":\"0x4e65746865726d696e64\",\"gasLimit\":\"0x7a1200\",\"gasUsed\":\"0x0\",\"hash\":\"0x16b111d85efa64c1c8e27f1e59c8ccd6bb6643b1999628ac37294c31158e2245\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"miner\":\"0x475674cb523a0a2736b7f7534390288fce16982c\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"nonce\":\"0x0000000000000000\",\"number\":\"0x3\",\"parentHash\":\"0x761cfe357802c8a2a68e37ad8325607920e72ce19b5b0d3e1ba01840f7e905ec\",\"receiptsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"sha3Uncles\":\"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347\",\"size\":\"0x20b\",\"stateRoot\":\"0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f\",\"totalDifficulty\":\"0xf4243\",\"timestamp\":\"0x5e47e919\",\"baseFeePerGas\":\"0x2da282a8\",\"transactions\":[],\"transactionsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"uncles\":[]},\"id\":67}")] @@ -758,14 +767,13 @@ public async Task Eth_get_block_by_number_should_not_recover_tx_senders_for_requ public async Task Eth_get_block_by_number_null() { using Context ctx = await Context.Create(); - string serialized = await ctx.Test.TestEthRpc("eth_getBlockByNumber", "1000000", "false"); + string serialized = await ctx.Test.TestEthRpc("eth_getBlockByNumber", 1000000.ToHexString(), "false"); Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":null,\"id\":67}")); } [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}")); @@ -812,11 +820,19 @@ 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\":\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"proof\":[\"0xe7a120b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf68483abcdef\"],\"value\":\"0xabcdef\"}]},\"id\":67}"), serialized.Replace("\"", "\\\"")); + 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] @@ -856,7 +872,7 @@ public async Task Eth_get_account_incorrect_block() string serialized = await ctx.Test.TestEthRpc("eth_getAccount", account_address, "0xffff"); - serialized.Should().Be("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32001,\"message\":\"65535 could not be found\"},\"id\":67}"); + serialized.Should().Be("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"65535 could not be found\"},\"id\":67}"); } [Test] @@ -899,7 +915,7 @@ public async Task Eth_get_account_info_incorrect_block() string serialized = await ctx.Test.TestEthRpc("eth_getAccountInfo", account_address, "0xffff"); - serialized.Should().Be("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32001,\"message\":\"65535 could not be found\"},\"id\":67}"); + serialized.Should().Be("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"65535 could not be found\"},\"id\":67}"); } [Test] @@ -942,7 +958,7 @@ public async Task Eth_get_block_by_number_with_recovering_sender_from_receipts() ctx.Test = await TestRpcBlockchain.ForTest(SealEngineType.NethDev).WithBlockFinder(blockFinder).WithReceiptFinder(receiptFinder).Build(); string serialized = await ctx.Test.TestEthRpc("eth_getBlockByNumber", TestItem.KeccakA.ToString(), "true"); - JToken.Parse(serialized).Should().BeEquivalentTo("""{"jsonrpc":"2.0","result":{"difficulty":"0xf4240","extraData":"0x010203","gasLimit":"0x3d0900","gasUsed":"0x0","hash":"0xe3026a6708b90d5cb25557ac38ddc3f5ef550af10f31e1cf771524da8553fa1c","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x2ba5557a4c62a513c7e56d1bf13373e0da6bec016755483e91589fe1c6d212e2","nonce":"0x00000000000003e8","number":"0x1","parentHash":"0xff483e972a04a9a62bb4b7d04ae403c615604e4090521ecc5bb7af67f71be09c","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x221","stateRoot":"0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f","totalDifficulty":"0x0","timestamp":"0xf4240","transactions":[{"nonce":"0x0","blockHash":"0xe3026a6708b90d5cb25557ac38ddc3f5ef550af10f31e1cf771524da8553fa1c","blockNumber":"0x1","transactionIndex":"0x0","from":"0x2d36e6c27c34ea22620e7b7c45de774599406cf3","to":"0x0000000000000000000000000000000000000000","value":"0x1","gasPrice":"0x1","gas":"0x5208","input":"0x","type":"0x0","v":"0x0","r":"0x0","s":"0x0","hash":null}],"transactionsRoot":"0x29cc403075ed3d1d6af940d577125cc378ee5a26f7746cbaf87f1cf4a38258b5","uncles":[]},"id":67}"""); + JToken.Parse(serialized).Should().BeEquivalentTo("""{"jsonrpc":"2.0","result":{"difficulty":"0xf4240","extraData":"0x010203","gasLimit":"0x3d0900","gasUsed":"0x0","hash":"0xe3026a6708b90d5cb25557ac38ddc3f5ef550af10f31e1cf771524da8553fa1c","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x2ba5557a4c62a513c7e56d1bf13373e0da6bec016755483e91589fe1c6d212e2","nonce":"0x00000000000003e8","number":"0x1","parentHash":"0xff483e972a04a9a62bb4b7d04ae403c615604e4090521ecc5bb7af67f71be09c","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x221","stateRoot":"0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f","totalDifficulty":"0x0","timestamp":"0xf4240","transactions":[{"nonce":"0x0","blockHash":"0xe3026a6708b90d5cb25557ac38ddc3f5ef550af10f31e1cf771524da8553fa1c","blockNumber":"0x1","blockTimestamp":"0xf4240","transactionIndex":"0x0","from":"0x2d36e6c27c34ea22620e7b7c45de774599406cf3","to":"0x0000000000000000000000000000000000000000","value":"0x1","gasPrice":"0x1","gas":"0x5208","input":"0x","type":"0x0","v":"0x0","r":"0x0","s":"0x0","hash":null}],"transactionsRoot":"0x29cc403075ed3d1d6af940d577125cc378ee5a26f7746cbaf87f1cf4a38258b5","uncles":[]},"id":67}"""); } [TestCase(false)] @@ -980,9 +996,9 @@ public async Task Eth_get_transaction_receipt(bool postEip4844) string serialized = await ctx.Test.TestEthRpc("eth_getTransactionReceipt", TestItem.KeccakA.ToString()); if (postEip4844) - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":{\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"transactionIndex\":\"0x2\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"cumulativeGasUsed\":\"0x3e8\",\"gasUsed\":\"0x64\",\"blobGasUsed\":\"0x3\",\"blobGasPrice\":\"0x2\",\"effectiveGasPrice\":\"0x1\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"contractAddress\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"logs\":[{\"removed\":false,\"logIndex\":\"0x0\",\"transactionIndex\":\"0x2\",\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"blockTimestamp\":\"0xa\",\"address\":\"0x0000000000000000000000000000000000000000\",\"data\":\"0x\",\"topics\":[\"0x0000000000000000000000000000000000000000000000000000000000000000\"]},{\"removed\":false,\"logIndex\":\"0x1\",\"transactionIndex\":\"0x2\",\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"blockTimestamp\":\"0xa\",\"address\":\"0x0000000000000000000000000000000000000000\",\"data\":\"0x\",\"topics\":[\"0x0000000000000000000000000000000000000000000000000000000000000000\"]}],\"logsBloom\":\"0x00000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000\",\"root\":\"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111\",\"status\":\"0x1\",\"error\":\"error\",\"type\":\"0x0\"},\"id\":67}")); + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":{\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"transactionIndex\":\"0x2\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"cumulativeGasUsed\":\"0x3e8\",\"gasUsed\":\"0x64\",\"blobGasUsed\":\"0x3\",\"blobGasPrice\":\"0x2\",\"effectiveGasPrice\":\"0x1\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"contractAddress\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"logs\":[{\"removed\":false,\"logIndex\":\"0x0\",\"transactionIndex\":\"0x2\",\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"blockTimestamp\":\"0xa\",\"address\":\"0x0000000000000000000000000000000000000000\",\"data\":\"0x\",\"topics\":[\"0x0000000000000000000000000000000000000000000000000000000000000000\"]},{\"removed\":false,\"logIndex\":\"0x1\",\"transactionIndex\":\"0x2\",\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"blockTimestamp\":\"0xa\",\"address\":\"0x0000000000000000000000000000000000000000\",\"data\":\"0x\",\"topics\":[\"0x0000000000000000000000000000000000000000000000000000000000000000\"]}],\"logsBloom\":\"0x00000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000\",\"root\":\"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111\",\"error\":\"error\",\"type\":\"0x0\"},\"id\":67}")); else - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":{\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"transactionIndex\":\"0x2\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"cumulativeGasUsed\":\"0x3e8\",\"gasUsed\":\"0x64\",\"effectiveGasPrice\":\"0x1\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"contractAddress\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"logs\":[{\"removed\":false,\"logIndex\":\"0x0\",\"transactionIndex\":\"0x2\",\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"blockTimestamp\":\"0xa\",\"address\":\"0x0000000000000000000000000000000000000000\",\"data\":\"0x\",\"topics\":[\"0x0000000000000000000000000000000000000000000000000000000000000000\"]},{\"removed\":false,\"logIndex\":\"0x1\",\"transactionIndex\":\"0x2\",\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"blockTimestamp\":\"0xa\",\"address\":\"0x0000000000000000000000000000000000000000\",\"data\":\"0x\",\"topics\":[\"0x0000000000000000000000000000000000000000000000000000000000000000\"]}],\"logsBloom\":\"0x00000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000\",\"root\":\"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111\",\"status\":\"0x1\",\"error\":\"error\",\"type\":\"0x0\"},\"id\":67}")); + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":{\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"transactionIndex\":\"0x2\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"cumulativeGasUsed\":\"0x3e8\",\"gasUsed\":\"0x64\",\"effectiveGasPrice\":\"0x1\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"contractAddress\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"logs\":[{\"removed\":false,\"logIndex\":\"0x0\",\"transactionIndex\":\"0x2\",\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"blockTimestamp\":\"0xa\",\"address\":\"0x0000000000000000000000000000000000000000\",\"data\":\"0x\",\"topics\":[\"0x0000000000000000000000000000000000000000000000000000000000000000\"]},{\"removed\":false,\"logIndex\":\"0x1\",\"transactionIndex\":\"0x2\",\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"blockTimestamp\":\"0xa\",\"address\":\"0x0000000000000000000000000000000000000000\",\"data\":\"0x\",\"topics\":[\"0x0000000000000000000000000000000000000000000000000000000000000000\"]}],\"logsBloom\":\"0x00000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000\",\"root\":\"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111\",\"error\":\"error\",\"type\":\"0x0\"},\"id\":67}")); } @@ -1091,7 +1107,7 @@ public async Task Eth_getTransactionReceipt_return_info_about_mined_tx() ctx.Test = await TestRpcBlockchain.ForTest(SealEngineType.NethDev).WithBlockFinder(blockFinder).WithReceiptFinder(receiptFinder).WithBlockchainBridge(blockchainBridge).Build(); string serialized = await ctx.Test.TestEthRpc("eth_getTransactionReceipt", tx.Hash!.ToString()); - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":{\"transactionHash\":\"0xda6b4df2595675cbee0d4889f41c3d0790204e8ed1b8ad4cadaa45a7d50dace5\",\"transactionIndex\":\"0x2\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"cumulativeGasUsed\":\"0x3e8\",\"gasUsed\":\"0x64\",\"effectiveGasPrice\":\"0x1\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"contractAddress\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"logs\":[{\"removed\":false,\"logIndex\":\"0x0\",\"transactionIndex\":\"0x2\",\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"blockTimestamp\":\"0xa\",\"address\":\"0x0000000000000000000000000000000000000000\",\"data\":\"0x\",\"topics\":[\"0x0000000000000000000000000000000000000000000000000000000000000000\"]}],\"logsBloom\":\"0x00000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000\",\"root\":\"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111\",\"status\":\"0x1\",\"error\":\"error\",\"type\":\"0x0\"},\"id\":67}")); + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":{\"transactionHash\":\"0xda6b4df2595675cbee0d4889f41c3d0790204e8ed1b8ad4cadaa45a7d50dace5\",\"transactionIndex\":\"0x2\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"cumulativeGasUsed\":\"0x3e8\",\"gasUsed\":\"0x64\",\"effectiveGasPrice\":\"0x1\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"contractAddress\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"logs\":[{\"removed\":false,\"logIndex\":\"0x0\",\"transactionIndex\":\"0x2\",\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"blockTimestamp\":\"0xa\",\"address\":\"0x0000000000000000000000000000000000000000\",\"data\":\"0x\",\"topics\":[\"0x0000000000000000000000000000000000000000000000000000000000000000\"]}],\"logsBloom\":\"0x00000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000\",\"root\":\"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111\",\"error\":\"error\",\"type\":\"0x0\"},\"id\":67}")); } [Test] @@ -1157,7 +1173,7 @@ public async Task Send_raw_transaction_will_send_transaction(string rawTransacti string serialized = await ctx.Test.TestEthRpc("eth_sendRawTransaction", rawTransaction); Transaction tx = Rlp.Decode(Bytes.FromHexString(rawTransaction)); await txSender.Received().SendTransaction(tx, TxHandlingOptions.PersistentBroadcast); - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32010,\"message\":\"Invalid, InvalidTxSignature: Signature is invalid.\"},\"id\":67}")); + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"Invalid, InvalidTxSignature: Signature is invalid.\"},\"id\":67}")); } [Test] @@ -1208,7 +1224,7 @@ public async Task Send_transaction_should_return_ErrorCode_if_tx_not_added() string serialized = await ctx.Test.TestEthRpc("eth_sendTransaction", txForRpc); - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32010,\"message\":\"InsufficientFunds, Balance is zero, cannot pay gas\"},\"id\":67}")); + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"InsufficientFunds, Balance is zero, cannot pay gas\"},\"id\":67}")); } public enum AccessListProvided @@ -1218,21 +1234,21 @@ public enum AccessListProvided Full } - [TestCase(AccessListProvided.None, false, 2, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0xfffffffffffffffffffffffffffffffffffffffe\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\"]},{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]}],\"gasUsed\":\"0xf71b\"},\"id\":67}")] - [TestCase(AccessListProvided.Full, false, 2, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0xfffffffffffffffffffffffffffffffffffffffe\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\"]},{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]}],\"gasUsed\":\"0xf71b\"},\"id\":67}")] - [TestCase(AccessListProvided.Partial, false, 2, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]},{\"address\":\"0xfffffffffffffffffffffffffffffffffffffffe\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\"]}],\"gasUsed\":\"0xf71b\"},\"id\":67}")] + [TestCase(AccessListProvided.None, false, 2, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0xbd770416a3345f91e4b34576cb804a576fa48eb1\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\"]},{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]}],\"gasUsed\":\"0xf71b\"},\"id\":67}")] + [TestCase(AccessListProvided.Full, false, 2, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0xfffffffffffffffffffffffffffffffffffffffe\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\"]},{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]},{\"address\":\"0xbd770416a3345f91e4b34576cb804a576fa48eb1\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\"]}],\"gasUsed\":\"0x10f53\"},\"id\":67}")] + [TestCase(AccessListProvided.Partial, false, 2, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]},{\"address\":\"0xbd770416a3345f91e4b34576cb804a576fa48eb1\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\"]}],\"gasUsed\":\"0xf71b\"},\"id\":67}")] - [TestCase(AccessListProvided.None, true, 2, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0xfffffffffffffffffffffffffffffffffffffffe\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\"]},{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]}],\"gasUsed\":\"0xf71b\"},\"id\":67}")] - [TestCase(AccessListProvided.Full, true, 2, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0xfffffffffffffffffffffffffffffffffffffffe\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\"]},{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]}],\"gasUsed\":\"0xf71b\"},\"id\":67}")] - [TestCase(AccessListProvided.Partial, true, 2, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]},{\"address\":\"0xfffffffffffffffffffffffffffffffffffffffe\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\"]}],\"gasUsed\":\"0xf71b\"},\"id\":67}")] + [TestCase(AccessListProvided.None, true, 2, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0xbd770416a3345f91e4b34576cb804a576fa48eb1\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\"]},{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]}],\"gasUsed\":\"0xf71b\"},\"id\":67}")] + [TestCase(AccessListProvided.Full, true, 2, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0xfffffffffffffffffffffffffffffffffffffffe\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\"]},{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]},{\"address\":\"0xbd770416a3345f91e4b34576cb804a576fa48eb1\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\"]}],\"gasUsed\":\"0x10f53\"},\"id\":67}")] + [TestCase(AccessListProvided.Partial, true, 2, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]},{\"address\":\"0xbd770416a3345f91e4b34576cb804a576fa48eb1\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\"]}],\"gasUsed\":\"0xf71b\"},\"id\":67}")] - [TestCase(AccessListProvided.None, true, 12, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0xfffffffffffffffffffffffffffffffffffffffe\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\",\"0x0000000000000000000000000000000000000000000000000000000000000003\",\"0x0000000000000000000000000000000000000000000000000000000000000004\",\"0x0000000000000000000000000000000000000000000000000000000000000005\",\"0x0000000000000000000000000000000000000000000000000000000000000006\",\"0x0000000000000000000000000000000000000000000000000000000000000007\",\"0x0000000000000000000000000000000000000000000000000000000000000008\",\"0x0000000000000000000000000000000000000000000000000000000000000009\",\"0x000000000000000000000000000000000000000000000000000000000000000a\",\"0x000000000000000000000000000000000000000000000000000000000000000b\",\"0x000000000000000000000000000000000000000000000000000000000000000c\"]},{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]}],\"gasUsed\":\"0x14739\"},\"id\":67}")] - [TestCase(AccessListProvided.Full, true, 12, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0xfffffffffffffffffffffffffffffffffffffffe\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\",\"0x0000000000000000000000000000000000000000000000000000000000000003\",\"0x0000000000000000000000000000000000000000000000000000000000000004\",\"0x0000000000000000000000000000000000000000000000000000000000000005\",\"0x0000000000000000000000000000000000000000000000000000000000000006\",\"0x0000000000000000000000000000000000000000000000000000000000000007\",\"0x0000000000000000000000000000000000000000000000000000000000000008\",\"0x0000000000000000000000000000000000000000000000000000000000000009\",\"0x000000000000000000000000000000000000000000000000000000000000000a\",\"0x000000000000000000000000000000000000000000000000000000000000000b\",\"0x000000000000000000000000000000000000000000000000000000000000000c\"]},{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]}],\"gasUsed\":\"0x14739\"},\"id\":67}")] - [TestCase(AccessListProvided.Partial, true, 12, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]},{\"address\":\"0xfffffffffffffffffffffffffffffffffffffffe\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\",\"0x0000000000000000000000000000000000000000000000000000000000000003\",\"0x0000000000000000000000000000000000000000000000000000000000000004\",\"0x0000000000000000000000000000000000000000000000000000000000000005\",\"0x0000000000000000000000000000000000000000000000000000000000000006\",\"0x0000000000000000000000000000000000000000000000000000000000000007\",\"0x0000000000000000000000000000000000000000000000000000000000000008\",\"0x0000000000000000000000000000000000000000000000000000000000000009\",\"0x000000000000000000000000000000000000000000000000000000000000000a\",\"0x000000000000000000000000000000000000000000000000000000000000000b\",\"0x000000000000000000000000000000000000000000000000000000000000000c\"]}],\"gasUsed\":\"0x14739\"},\"id\":67}")] + [TestCase(AccessListProvided.None, true, 12, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0xbd770416a3345f91e4b34576cb804a576fa48eb1\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\",\"0x0000000000000000000000000000000000000000000000000000000000000003\",\"0x0000000000000000000000000000000000000000000000000000000000000004\",\"0x0000000000000000000000000000000000000000000000000000000000000005\",\"0x0000000000000000000000000000000000000000000000000000000000000006\",\"0x0000000000000000000000000000000000000000000000000000000000000007\",\"0x0000000000000000000000000000000000000000000000000000000000000008\",\"0x0000000000000000000000000000000000000000000000000000000000000009\",\"0x000000000000000000000000000000000000000000000000000000000000000a\",\"0x000000000000000000000000000000000000000000000000000000000000000b\",\"0x000000000000000000000000000000000000000000000000000000000000000c\"]},{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]}],\"gasUsed\":\"0x14739\"},\"id\":67}")] + [TestCase(AccessListProvided.Full, true, 12, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0xfffffffffffffffffffffffffffffffffffffffe\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\"]},{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]},{\"address\":\"0xbd770416a3345f91e4b34576cb804a576fa48eb1\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\",\"0x0000000000000000000000000000000000000000000000000000000000000003\",\"0x0000000000000000000000000000000000000000000000000000000000000004\",\"0x0000000000000000000000000000000000000000000000000000000000000005\",\"0x0000000000000000000000000000000000000000000000000000000000000006\",\"0x0000000000000000000000000000000000000000000000000000000000000007\",\"0x0000000000000000000000000000000000000000000000000000000000000008\",\"0x0000000000000000000000000000000000000000000000000000000000000009\",\"0x000000000000000000000000000000000000000000000000000000000000000a\",\"0x000000000000000000000000000000000000000000000000000000000000000b\",\"0x000000000000000000000000000000000000000000000000000000000000000c\"]}],\"gasUsed\":\"0x15f71\"},\"id\":67}")] + [TestCase(AccessListProvided.Partial, true, 12, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]},{\"address\":\"0xbd770416a3345f91e4b34576cb804a576fa48eb1\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\",\"0x0000000000000000000000000000000000000000000000000000000000000003\",\"0x0000000000000000000000000000000000000000000000000000000000000004\",\"0x0000000000000000000000000000000000000000000000000000000000000005\",\"0x0000000000000000000000000000000000000000000000000000000000000006\",\"0x0000000000000000000000000000000000000000000000000000000000000007\",\"0x0000000000000000000000000000000000000000000000000000000000000008\",\"0x0000000000000000000000000000000000000000000000000000000000000009\",\"0x000000000000000000000000000000000000000000000000000000000000000a\",\"0x000000000000000000000000000000000000000000000000000000000000000b\",\"0x000000000000000000000000000000000000000000000000000000000000000c\"]}],\"gasUsed\":\"0x14739\"},\"id\":67}")] - [TestCase(AccessListProvided.None, true, 17, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0xfffffffffffffffffffffffffffffffffffffffe\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\",\"0x0000000000000000000000000000000000000000000000000000000000000003\",\"0x0000000000000000000000000000000000000000000000000000000000000004\",\"0x0000000000000000000000000000000000000000000000000000000000000005\",\"0x0000000000000000000000000000000000000000000000000000000000000006\",\"0x0000000000000000000000000000000000000000000000000000000000000007\",\"0x0000000000000000000000000000000000000000000000000000000000000008\",\"0x0000000000000000000000000000000000000000000000000000000000000009\",\"0x000000000000000000000000000000000000000000000000000000000000000a\",\"0x000000000000000000000000000000000000000000000000000000000000000b\",\"0x000000000000000000000000000000000000000000000000000000000000000c\",\"0x000000000000000000000000000000000000000000000000000000000000000d\",\"0x000000000000000000000000000000000000000000000000000000000000000e\",\"0x000000000000000000000000000000000000000000000000000000000000000f\",\"0x0000000000000000000000000000000000000000000000000000000000000010\",\"0x0000000000000000000000000000000000000000000000000000000000000011\"]},{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]}],\"gasUsed\":\"0x16f48\"},\"id\":67}")] - [TestCase(AccessListProvided.Full, true, 17, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0xfffffffffffffffffffffffffffffffffffffffe\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\",\"0x0000000000000000000000000000000000000000000000000000000000000003\",\"0x0000000000000000000000000000000000000000000000000000000000000004\",\"0x0000000000000000000000000000000000000000000000000000000000000005\",\"0x0000000000000000000000000000000000000000000000000000000000000006\",\"0x0000000000000000000000000000000000000000000000000000000000000007\",\"0x0000000000000000000000000000000000000000000000000000000000000008\",\"0x0000000000000000000000000000000000000000000000000000000000000009\",\"0x000000000000000000000000000000000000000000000000000000000000000a\",\"0x000000000000000000000000000000000000000000000000000000000000000b\",\"0x000000000000000000000000000000000000000000000000000000000000000c\",\"0x000000000000000000000000000000000000000000000000000000000000000d\",\"0x000000000000000000000000000000000000000000000000000000000000000e\",\"0x000000000000000000000000000000000000000000000000000000000000000f\",\"0x0000000000000000000000000000000000000000000000000000000000000010\",\"0x0000000000000000000000000000000000000000000000000000000000000011\"]},{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]}],\"gasUsed\":\"0x16f48\"},\"id\":67}")] - [TestCase(AccessListProvided.Partial, true, 17, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]},{\"address\":\"0xfffffffffffffffffffffffffffffffffffffffe\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\",\"0x0000000000000000000000000000000000000000000000000000000000000003\",\"0x0000000000000000000000000000000000000000000000000000000000000004\",\"0x0000000000000000000000000000000000000000000000000000000000000005\",\"0x0000000000000000000000000000000000000000000000000000000000000006\",\"0x0000000000000000000000000000000000000000000000000000000000000007\",\"0x0000000000000000000000000000000000000000000000000000000000000008\",\"0x0000000000000000000000000000000000000000000000000000000000000009\",\"0x000000000000000000000000000000000000000000000000000000000000000a\",\"0x000000000000000000000000000000000000000000000000000000000000000b\",\"0x000000000000000000000000000000000000000000000000000000000000000c\",\"0x000000000000000000000000000000000000000000000000000000000000000d\",\"0x000000000000000000000000000000000000000000000000000000000000000e\",\"0x000000000000000000000000000000000000000000000000000000000000000f\",\"0x0000000000000000000000000000000000000000000000000000000000000010\",\"0x0000000000000000000000000000000000000000000000000000000000000011\"]}],\"gasUsed\":\"0x16f48\"},\"id\":67}")] + [TestCase(AccessListProvided.None, true, 17, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0xbd770416a3345f91e4b34576cb804a576fa48eb1\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\",\"0x0000000000000000000000000000000000000000000000000000000000000003\",\"0x0000000000000000000000000000000000000000000000000000000000000004\",\"0x0000000000000000000000000000000000000000000000000000000000000005\",\"0x0000000000000000000000000000000000000000000000000000000000000006\",\"0x0000000000000000000000000000000000000000000000000000000000000007\",\"0x0000000000000000000000000000000000000000000000000000000000000008\",\"0x0000000000000000000000000000000000000000000000000000000000000009\",\"0x000000000000000000000000000000000000000000000000000000000000000a\",\"0x000000000000000000000000000000000000000000000000000000000000000b\",\"0x000000000000000000000000000000000000000000000000000000000000000c\",\"0x000000000000000000000000000000000000000000000000000000000000000d\",\"0x000000000000000000000000000000000000000000000000000000000000000e\",\"0x000000000000000000000000000000000000000000000000000000000000000f\",\"0x0000000000000000000000000000000000000000000000000000000000000010\",\"0x0000000000000000000000000000000000000000000000000000000000000011\"]},{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]}],\"gasUsed\":\"0x16f48\"},\"id\":67}")] + [TestCase(AccessListProvided.Full, true, 17, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0xfffffffffffffffffffffffffffffffffffffffe\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\"]},{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]},{\"address\":\"0xbd770416a3345f91e4b34576cb804a576fa48eb1\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\",\"0x0000000000000000000000000000000000000000000000000000000000000003\",\"0x0000000000000000000000000000000000000000000000000000000000000004\",\"0x0000000000000000000000000000000000000000000000000000000000000005\",\"0x0000000000000000000000000000000000000000000000000000000000000006\",\"0x0000000000000000000000000000000000000000000000000000000000000007\",\"0x0000000000000000000000000000000000000000000000000000000000000008\",\"0x0000000000000000000000000000000000000000000000000000000000000009\",\"0x000000000000000000000000000000000000000000000000000000000000000a\",\"0x000000000000000000000000000000000000000000000000000000000000000b\",\"0x000000000000000000000000000000000000000000000000000000000000000c\",\"0x000000000000000000000000000000000000000000000000000000000000000d\",\"0x000000000000000000000000000000000000000000000000000000000000000e\",\"0x000000000000000000000000000000000000000000000000000000000000000f\",\"0x0000000000000000000000000000000000000000000000000000000000000010\",\"0x0000000000000000000000000000000000000000000000000000000000000011\"]}],\"gasUsed\":\"0x18780\"},\"id\":67}")] + [TestCase(AccessListProvided.Partial, true, 17, "{\"jsonrpc\":\"2.0\",\"result\":{\"accessList\":[{\"address\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"storageKeys\":[]},{\"address\":\"0xbd770416a3345f91e4b34576cb804a576fa48eb1\",\"storageKeys\":[\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000000000000000000000000000002\",\"0x0000000000000000000000000000000000000000000000000000000000000003\",\"0x0000000000000000000000000000000000000000000000000000000000000004\",\"0x0000000000000000000000000000000000000000000000000000000000000005\",\"0x0000000000000000000000000000000000000000000000000000000000000006\",\"0x0000000000000000000000000000000000000000000000000000000000000007\",\"0x0000000000000000000000000000000000000000000000000000000000000008\",\"0x0000000000000000000000000000000000000000000000000000000000000009\",\"0x000000000000000000000000000000000000000000000000000000000000000a\",\"0x000000000000000000000000000000000000000000000000000000000000000b\",\"0x000000000000000000000000000000000000000000000000000000000000000c\",\"0x000000000000000000000000000000000000000000000000000000000000000d\",\"0x000000000000000000000000000000000000000000000000000000000000000e\",\"0x000000000000000000000000000000000000000000000000000000000000000f\",\"0x0000000000000000000000000000000000000000000000000000000000000010\",\"0x0000000000000000000000000000000000000000000000000000000000000011\"]}],\"gasUsed\":\"0x16f48\"},\"id\":67}")] public async Task Eth_create_access_list_sample(AccessListProvided accessListProvided, bool optimize, long loads, string expected) { @@ -1262,6 +1278,16 @@ public static void Should_handle_gasCap_as_max_if_null_or_zero(long? gasCap) Assert.That(rpcTx.Gas, Is.EqualTo(long.MaxValue), "Gas must be set to max if gasCap is null or 0"); } + [Test] + public static void Should_handle_fromAddress_as_zero_if_null() + { + LegacyTransactionForRpc rpcTx = new LegacyTransactionForRpc(); + + rpcTx.EnsureDefaults(0); + + Assert.That(rpcTx.From, Is.EqualTo(Address.Zero), "From address must be set to zero if tx.from is null"); + } + [Ignore(reason: "Shows disparity across 'default' methods")] [TestCase(null)] [TestCase(0)] @@ -1270,16 +1296,16 @@ public static void ToTransactionWithDefaults_and_EnsureDefaults_same_GasLimit(lo long toTransactionWitDefaultsGasLimit; { var rpcTx = new LegacyTransactionForRpc(); - Transaction tx = rpcTx.ToTransaction(); - toTransactionWitDefaultsGasLimit = tx.GasLimit; + Result tx = rpcTx.ToTransaction(); + toTransactionWitDefaultsGasLimit = ((Transaction)tx).GasLimit; } long ensureDefaultsGasLimit; { var rpcTx = new LegacyTransactionForRpc(); rpcTx.EnsureDefaults(gasCap); - var tx = rpcTx.ToTransaction(); - ensureDefaultsGasLimit = tx.GasLimit; + Result tx = rpcTx.ToTransaction(); + ensureDefaultsGasLimit = ((Transaction)tx).GasLimit; } toTransactionWitDefaultsGasLimit.Should().Be(ensureDefaultsGasLimit); @@ -1450,7 +1476,7 @@ public async Task eth_getTransactionByHash_returns_correct_values_on_SetCode_tx( public async Task Eth_get_block_by_number_pruned() { using Context ctx = await Context.CreateWithAncientBarriers(10000); - string serialized = await ctx.Test.TestEthRpc("eth_getBlockByNumber", "100", "false"); + string serialized = await ctx.Test.TestEthRpc("eth_getBlockByNumber", "0xF4240", "false"); Assert.That(serialized, Is.EqualTo("""{"jsonrpc":"2.0","result":null,"id":67}""")); } @@ -1458,7 +1484,7 @@ public async Task Eth_get_block_by_number_pruned() public async Task Eth_tx_count_by_number_pruned() { using Context ctx = await Context.CreateWithAncientBarriers(10000); - string serialized = await ctx.Test.TestEthRpc("eth_getBlockTransactionCountByNumber", 100); + string serialized = await ctx.Test.TestEthRpc("eth_getBlockTransactionCountByNumber", "0x64"); Assert.That(serialized, Is.EqualTo("""{"jsonrpc":"2.0","result":null,"id":67}""")); } @@ -1466,7 +1492,7 @@ public async Task Eth_tx_count_by_number_pruned() public async Task Eth_get_uncle_count_by_block_number_pruned() { using Context ctx = await Context.CreateWithAncientBarriers(10000); - string serialized = await ctx.Test.TestEthRpc("eth_getUncleCountByBlockNumber", 100); + string serialized = await ctx.Test.TestEthRpc("eth_getUncleCountByBlockNumber", "0x64"); Assert.That(serialized, Is.EqualTo("""{"jsonrpc":"2.0","result":null,"id":67}""")); } @@ -1474,7 +1500,7 @@ public async Task Eth_get_uncle_count_by_block_number_pruned() public async Task Eth_get_transaction_by_block_number_and_index_pruned() { using Context ctx = await Context.CreateWithAncientBarriers(10000); - string serialized = await ctx.Test.TestEthRpc("eth_getTransactionByBlockNumberAndIndex", 100, "1"); + string serialized = await ctx.Test.TestEthRpc("eth_getTransactionByBlockNumberAndIndex", "0x100", "1"); Assert.That(serialized, Is.EqualTo("""{"jsonrpc":"2.0","result":null,"id":67}""")); } @@ -1518,7 +1544,7 @@ public async Task Eth_get_transaction_by_block_hash_and_index_pruned() public async Task Eth_getBlockReceipts_pruned() { using Context ctx = await Context.CreateWithAncientBarriers(10000); - string serialized = await ctx.Test.TestEthRpc("eth_getBlockReceipts", 100); + string serialized = await ctx.Test.TestEthRpc("eth_getBlockReceipts", "0x100"); Assert.That(serialized, Is.EqualTo("""{"jsonrpc":"2.0","result":null,"id":67}""")); } @@ -1647,14 +1673,16 @@ 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; }); }); } - public static Task Create(ISpecProvider? specProvider = null, IBlockchainBridge? blockchainBridge = null, Action? configurer = null) + public static Task Create(ISpecProvider? specProvider = null, + IBlockchainBridge? blockchainBridge = null, + Action? configurer = null) { Action wrappedConfigurer = builder => { @@ -1666,7 +1694,7 @@ public static Task Create(ISpecProvider? specProvider = null, IBlockcha { TestFactory = () => TestRpcBlockchain.ForTest(SealEngineType.NethDev) .WithBlockchainBridge(blockchainBridge!) - .WithConfig(new JsonRpcConfig() { EstimateErrorMargin = 0 }) + .WithConfig(new JsonRpcConfig { EstimateErrorMargin = 0 }) .Build(wrappedConfigurer).Result, AuraTestFactory = () => TestRpcBlockchain.ForTest(SealEngineType.AuRa) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcSimulateTestsBase.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcSimulateTestsBase.cs index 6c2d70a4cd96..01f55b0dee1a 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcSimulateTestsBase.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcSimulateTestsBase.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -174,6 +173,7 @@ protected static byte[] GenerateTransactionDataForEcRecover(Hash256 keccak, Sign SystemTransaction transaction = new() { Data = bytes, To = toAddress, SenderAddress = senderAddress }; transaction.Hash = transaction.CalculateHash(); TransactionForRpc transactionForRpc = TransactionForRpc.FromTransaction(transaction); + transactionForRpc.Gas = null; ResultWrapper mainChainResult = testRpcBlockchain.EthRpcModule.eth_call(transactionForRpc, BlockParameter.Pending); return ParseEcRecoverAddress(Bytes.FromHexString(mainChainResult.Data)); } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/FilterTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/FilterTests.cs index 2ab35c9ae37c..af06485f1bdb 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 2d245618bd39..157a985a12c4 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); @@ -38,11 +38,11 @@ public static SimulatePayload CreateSerialisationPayload(Test [ new() { - BlockOverrides = new BlockOverride { Number = 10 }, - Calls = [TransactionForRpc.FromTransaction(txToFail), TransactionForRpc.FromTransaction(tx)], + BlockOverrides = new BlockOverride { Number = 10, BaseFeePerGas = 0 }, + Calls = [ToRpcForInput(txToFail), ToRpcForInput(tx)], StateOverrides = new Dictionary { - { TestItem.AddressA, new AccountOverride { Balance = Math.Max(420_000_004_000_001UL, 1_000_000_004_000_001UL) } } + { TestItem.AddressA, new AccountOverride { Balance = 2100.Ether() } } } } ], @@ -72,8 +72,7 @@ public static SimulatePayload CreateEthMovedPayload(TestRpcBl FeeRecipient = TestItem.AddressC, BaseFeePerGas = 0 }, - Calls = [TransactionForRpc.FromTransaction(txAtoB1), TransactionForRpc.FromTransaction(txAtoB2) - ] + Calls = [ToRpcForInput(txAtoB1), ToRpcForInput(txAtoB2)] }, new() { @@ -85,13 +84,22 @@ public static SimulatePayload CreateEthMovedPayload(TestRpcBl FeeRecipient = TestItem.AddressC, BaseFeePerGas = 0 }, - Calls = [TransactionForRpc.FromTransaction(txAtoB3), TransactionForRpc.FromTransaction(txAtoB4)] + Calls = [ToRpcForInput(txAtoB3), ToRpcForInput(txAtoB4)] } }, TraceTransfers = true }; } + // Helper to convert Transaction to RPC format suitable for input (clears GasPrice for EIP-1559+ to avoid ambiguity) + private static TransactionForRpc ToRpcForInput(Transaction tx) + { + TransactionForRpc rpc = TransactionForRpc.FromTransaction(tx); + if (rpc is EIP1559TransactionForRpc eip1559Rpc) + eip1559Rpc.GasPrice = null; + return rpc; + } + public static SimulatePayload CreateTransactionsForcedFail(TestRpcBlockchain chain, UInt256 nonceA) { //shall be Ok @@ -102,9 +110,9 @@ public static SimulatePayload CreateTransactionsForcedFail(Te Transaction txAtoB2 = GetTransferTxData(nonceA + 2, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, UInt256.MaxValue); - LegacyTransactionForRpc transactionForRpc = (LegacyTransactionForRpc)TransactionForRpc.FromTransaction(txAtoB2); + LegacyTransactionForRpc transactionForRpc = (LegacyTransactionForRpc)ToRpcForInput(txAtoB2); transactionForRpc.Nonce = null; - LegacyTransactionForRpc transactionForRpc2 = (LegacyTransactionForRpc)TransactionForRpc.FromTransaction(txAtoB1); + LegacyTransactionForRpc transactionForRpc2 = (LegacyTransactionForRpc)ToRpcForInput(txAtoB1); transactionForRpc2.Nonce = null; return new() @@ -141,16 +149,18 @@ public static SimulatePayload CreateTransactionsForcedFail(Te }; } - public static Transaction GetTransferTxData(UInt256 nonce, IEthereumEcdsa ethereumEcdsa, PrivateKey from, Address to, UInt256 amount) + public static Transaction GetTransferTxData(UInt256 nonce, IEthereumEcdsa ethereumEcdsa, PrivateKey from, Address to, UInt256 amount, TxType type = TxType.EIP1559) { Transaction tx = new() { + Type = type, Value = amount, Nonce = nonce, GasLimit = 50_000, SenderAddress = from.Address, To = to, - GasPrice = 20.GWei() + GasPrice = 20.GWei(), + DecodedMaxFeePerGas = type >= TxType.EIP1559 ? 20.GWei() : 0 }; ethereumEcdsa.Sign(from, tx); @@ -159,11 +169,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); @@ -173,7 +183,8 @@ public async Task Test_eth_simulate_serialisation() SimulateTxExecutor executor = new(chain.Bridge, chain.BlockFinder, new JsonRpcConfig(), new SimulateBlockMutatorTracerFactory()); ResultWrapper>> result = executor.Execute(payload, BlockParameter.Latest); IReadOnlyList> data = result.Data; - Assert.That(data.Count, Is.EqualTo(7)); + Assert.That((bool)result.Result, Is.EqualTo(true), result.Result.ToString()); + Assert.That(data, Has.Count.EqualTo(7)); SimulateBlockResult blockResult = data.Last(); blockResult.Calls.Select(static c => c.Status).Should().BeEquivalentTo(new[] { (ulong)ResultType.Success, (ulong)ResultType.Success }); @@ -192,7 +203,7 @@ public async Task Test_eth_simulate_eth_moved() TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain(); UInt256 nonceA = chain.ReadOnlyState.GetNonce(TestItem.AddressA); - Transaction txMainnetAtoB = GetTransferTxData(nonceA, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1); + Transaction txMainnetAtoB = GetTransferTxData(nonceA, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1, type: TxType.Legacy); SimulatePayload payload = CreateEthMovedPayload(chain, nonceA); @@ -204,7 +215,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!); @@ -233,7 +244,7 @@ public async Task Test_eth_simulate_transactions_forced_fail() UInt256 nonceA = chain.ReadOnlyState.GetNonce(TestItem.AddressA); Transaction txMainnetAtoB = - GetTransferTxData(nonceA, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1); + GetTransferTxData(nonceA, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1, type: TxType.Legacy); SimulatePayload payload = CreateTransactionsForcedFail(chain, nonceA); @@ -245,7 +256,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!); @@ -276,6 +287,7 @@ public static SimulatePayload CreateTransferLogsAddressPayloa }, "calls": [ { + "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 5a10a051e3b7..45e931f91e8d 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; @@ -27,67 +28,76 @@ public class EthSimulateTestsHiveBase { private static readonly object[] HiveTestCases = { -new object[] {"baseFee", "{\"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[] {"multicall-add-more-non-defined-BlockStateCalls-than-fit-but-now-with-fit", "{\"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", "{\"blockStateCalls\": [{\"blockOverrides\": {\"baseFeePerGas\": \"0xa\"}, \"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}}, \"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"maxFeePerGas\": \"0x0\", \"maxPriorityFeePerGas\": \"0x0\"}]}]}"}, -//new object[] {"multicall-block-override-reflected-in-contract-simple", "{\"blockStateCalls\": [{\"blockOverrides\": {\"number\": \"0x12a\", \"time\": \"0x64\"}}, {\"blockOverrides\": {\"number\": \"0x14\", \"time\": \"0x65\"}}, {\"blockOverrides\": {\"number\": \"0x15\", \"time\": \"0xc8\"}}]}"}, -//new object[] {"multicall-block-timestamps-incrementing", "{\"blockStateCalls\": [{\"blockOverrides\": {\"time\": \"0x12b\"}}, {\"blockOverrides\": {\"time\": \"0x12c\"}}]}"}, -new object[] {"multicall-blockhash-complex", "{\"blockStateCalls\": [{\"blockOverrides\": {\"number\": \"0xa\"}, \"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x1e8480\"}, \"0xc200000000000000000000000000000000000000\": {\"code\": \"0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063ee82ac5e14602d575b600080fd5b60436004803603810190603f91906098565b6057565b604051604e919060d7565b60405180910390f35b600081409050919050565b600080fd5b6000819050919050565b6078816067565b8114608257600080fd5b50565b6000813590506092816071565b92915050565b60006020828403121560ab5760aa6062565b5b600060b7848285016085565b91505092915050565b6000819050919050565b60d18160c0565b82525050565b600060208201905060ea600083018460ca565b9291505056fea2646970667358221220a4d7face162688805e99e86526524ac3dadfb01cc29366d0d68b70dadcf01afe64736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e0000000000000000000000000000000000000000000000000000000000000001\"}]}, {\"blockOverrides\": {\"number\": \"0x14\"}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e000000000000000000000000000000000000000000000000000000000000000a\"}]}, {\"blockOverrides\": {\"number\": \"0x1e\"}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e000000000000000000000000000000000000000000000000000000000000001d\"}]}]}"}, -new object[] {"multicall-blockhash-simple", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc200000000000000000000000000000000000000\": {\"code\": \"0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063ee82ac5e14602d575b600080fd5b60436004803603810190603f91906098565b6057565b604051604e919060d7565b60405180910390f35b600081409050919050565b600080fd5b6000819050919050565b6078816067565b8114608257600080fd5b50565b6000813590506092816071565b92915050565b60006020828403121560ab5760aa6062565b5b600060b7848285016085565b91505092915050565b6000819050919050565b60d18160c0565b82525050565b600060208201905060ea600083018460ca565b9291505056fea2646970667358221220a4d7face162688805e99e86526524ac3dadfb01cc29366d0d68b70dadcf01afe64736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e0000000000000000000000000000000000000000000000000000000000000001\"}]}]}"}, -new object[] {"multicall-blockhash-start-before-head", "{\"blockStateCalls\": [{\"blockOverrides\": {\"number\": \"0xa\"}, \"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x1e8480\"}, \"0xc200000000000000000000000000000000000000\": {\"code\": \"0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063ee82ac5e14602d575b600080fd5b60436004803603810190603f91906098565b6057565b604051604e919060d7565b60405180910390f35b600081409050919050565b600080fd5b6000819050919050565b6078816067565b8114608257600080fd5b50565b6000813590506092816071565b92915050565b60006020828403121560ab5760aa6062565b5b600060b7848285016085565b91505092915050565b6000819050919050565b60d18160c0565b82525050565b600060208201905060ea600083018460ca565b9291505056fea2646970667358221220a4d7face162688805e99e86526524ac3dadfb01cc29366d0d68b70dadcf01afe64736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e0000000000000000000000000000000000000000000000000000000000000001\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e0000000000000000000000000000000000000000000000000000000000000002\"}]}, {\"blockOverrides\": {\"number\": \"0x14\"}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e0000000000000000000000000000000000000000000000000000000000000013\"}]}]}"}, -new object[] {"multicall-check-that-balance-is-there-after-new-block", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x2710\"}, \"0xc200000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f8b2cb4f14610030575b600080fd5b61004a600480360381019061004591906100e4565b610060565b604051610057919061012a565b60405180910390f35b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100b182610086565b9050919050565b6100c1816100a6565b81146100cc57600080fd5b50565b6000813590506100de816100b8565b92915050565b6000602082840312156100fa576100f9610081565b5b6000610108848285016100cf565b91505092915050565b6000819050919050565b61012481610111565b82525050565b600060208201905061013f600083018461011b565b9291505056fea2646970667358221220172c443a163d8a43e018c339d1b749c312c94b6de22835953d960985daf228c764736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xf8b2cb4f000000000000000000000000c000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xf8b2cb4f000000000000000000000000c100000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xf8b2cb4f000000000000000000000000c000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xf8b2cb4f000000000000000000000000c100000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}]}"}, -new object[] {"multicall-contract-calls-itself", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506000366060484641444543425a3a60014361002c919061009b565b406040516020016100469a99989796959493929190610138565b6040516020818303038152906040529050915050805190602001f35b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006100a682610062565b91506100b183610062565b92508282039050818111156100c9576100c861006c565b5b92915050565b6100d881610062565b82525050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610109826100de565b9050919050565b610119816100fe565b82525050565b6000819050919050565b6101328161011f565b82525050565b60006101408201905061014e600083018d6100cf565b61015b602083018c6100cf565b610168604083018b610110565b610175606083018a6100cf565b61018260808301896100cf565b61018f60a08301886100cf565b61019c60c08301876100cf565b6101a960e08301866100cf565b6101b76101008301856100cf565b6101c5610120830184610129565b9b9a505050505050505050505056fea26469706673582212205139ae3ba8d46d11c29815d001b725f9840c90e330884ed070958d5af4813d8764736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc000000000000000000000000000000000000000\"}]}], \"traceTransfers\": true}"}, -new object[] {"multicall-empty-calls-and-overrides-multicall", "{\"blockStateCalls\": [{\"stateOverrides\": {}, \"calls\": [{}]}, {\"stateOverrides\": {}, \"calls\": [{}]}], \"traceTransfers\": true}"}, -new object[] {"multicall-eth-send-should-not-produce-logs-by-default", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}]}"}, -new object[] {"multicall-eth-send-should-not-produce-logs-on-revert", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}, \"0xc100000000000000000000000000000000000000\": {\"code\": \"0x608060405260006042576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401603990609d565b60405180910390fd5b005b600082825260208201905092915050565b7f416c7761797320726576657274696e6720636f6e747261637400000000000000600082015250565b600060896019836044565b91506092826055565b602082019050919050565b6000602082019050818103600083015260b481607e565b905091905056fea264697066735822122005cbbbc709291f66fadc17416c1b0ed4d72941840db11468a21b8e1a0362024c64736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}], \"traceTransfers\": true}"}, -new object[] {"multicall-eth-send-should-produce-logs", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}], \"traceTransfers\": true}"}, -new object[] {"multicall-eth-send-should-produce-more-logs-on-forward", "{\"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", "{\"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", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc100000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506000366060484641444543425a3a60014361002c919061009b565b406040516020016100469a99989796959493929190610138565b6040516020818303038152906040529050915050805190602001f35b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006100a682610062565b91506100b183610062565b92508282039050818111156100c9576100c861006c565b5b92915050565b6100d881610062565b82525050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610109826100de565b9050919050565b610119816100fe565b82525050565b6000819050919050565b6101328161011f565b82525050565b60006101408201905061014e600083018d6100cf565b61015b602083018c6100cf565b610168604083018b610110565b610175606083018a6100cf565b61018260808301896100cf565b61018f60a08301886100cf565b61019c60c08301876100cf565b6101a960e08301866100cf565b6101b76101008301856100cf565b6101c5610120830184610129565b9b9a505050505050505050505056fea26469706673582212205139ae3ba8d46d11c29815d001b725f9840c90e330884ed070958d5af4813d8764736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x\"}]}]}"}, -new object[] {"multicall-instrict-gas-38013", "{\"blockStateCalls\": [{\"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"gas\": \"0x0\"}]}]}"}, -new object[] {"multicall-logs", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc200000000000000000000000000000000000000\": {\"code\": \"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80600080a1600080f3\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x6057361d0000000000000000000000000000000000000000000000000000000000000005\"}]}]}"}, -new object[] {"multicall-move-account-twice", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x3e8\"}, \"0xc200000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}, \"0xc300000000000000000000000000000000000000\": {\"balance\": \"0xbb8\"}, \"0xc400000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f8b2cb4f14610030575b600080fd5b61004a600480360381019061004591906100e4565b610060565b604051610057919061012a565b60405180910390f35b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100b182610086565b9050919050565b6100c1816100a6565b81146100cc57600080fd5b50565b6000813590506100de816100b8565b92915050565b6000602082840312156100fa576100f9610081565b5b6000610108848285016100cf565b91505092915050565b6000819050919050565b61012481610111565b82525050565b600060208201905061013f600083018461011b565b9291505056fea2646970667358221220172c443a163d8a43e018c339d1b749c312c94b6de22835953d960985daf228c764736f6c63430008120033\"}}}, {\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"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", "{\"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\"}]}]}"}, -new object[] {"multicall-move-to-address-itself-reference-38022", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x30d40\"}, \"0xc100000000000000000000000000000000000000\": {\"MovePrecompileToAddress\": \"0xc100000000000000000000000000000000000000\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x1\"}]}]}"}, -new object[] {"multicall-move-two-accounts-to-same-38023", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0x0000000000000000000000000000000000000001\": {\"MovePrecompileToAddress\": \"0xc200000000000000000000000000000000000000\"}, \"0x0000000000000000000000000000000000000002\": {\"MovePrecompileToAddress\": \"0xc200000000000000000000000000000000000000\"}}}]}"}, -new object[] {"multicall-move-two-non-precompiles-accounts-to-same", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0x0100000000000000000000000000000000000000\": {\"MovePrecompileToAddress\": \"0xc200000000000000000000000000000000000000\"}, \"0x0200000000000000000000000000000000000000\": {\"MovePrecompileToAddress\": \"0xc200000000000000000000000000000000000000\"}}}]}"}, -new object[] {"multicall-no-fields-call", "{\"blockStateCalls\": [{\"calls\": [{}]}], \"traceTransfers\": true}"}, -new object[] {"multicall-only-from-to-transaction", "{\"blockStateCalls\": [{\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\"}]}], \"traceTransfers\": true}"}, -new object[] {"multicall-only-from-transaction", "{\"blockStateCalls\": [{\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\"}]}], \"traceTransfers\": true}"}, -new object[] {"multicall-override-address-twice-in-separate-BlockStateCalls", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}, {\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}], \"traceTransfers\": true}"}, -//new object[] {"multicall-override-all-in-BlockStateCalls", "{\"blockStateCalls\": [{\"blockOverrides\": {\"number\": \"0x3e9\", \"time\": \"0x3eb\", \"gasLimit\": \"0x3ec\", \"feeRecipient\": \"0xc200000000000000000000000000000000000000\", \"prevRandao\": \"0xc300000000000000000000000000000000000000000000000000000000000000\", \"baseFeePerGas\": \"0x3ef\"}}]}"}, -new object[] {"multicall-override-block-num", "{\"blockStateCalls\": [{\"blockOverrides\": {\"number\": \"0xb\"}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"input\": \"0x4360005260206000f3\"}]}, {\"blockOverrides\": {\"number\": \"0xc\"}, \"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x4360005260206000f3\"}]}]}"}, -new object[] {"multicall-override-ecrecover", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0x0000000000000000000000000000000000000001\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061003a5760003560e01c806305fdbc81146101ee578063c00692601461020a5761003b565b5b600036606060008060008086868101906100559190610462565b93509350935093506000806000868686866040516020016100799493929190610520565b60405160208183030381529060405280519060200120815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036101bb576000806212345673ffffffffffffffffffffffffffffffffffffffff166127108b8b6040516101249291906105ad565b60006040518083038160008787f1925050503d8060008114610162576040519150601f19603f3d011682016040523d82523d6000602084013e610167565b606091505b5091509150816101ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101a39061066f565b60405180910390fd5b809750505050505050506101e3565b806040516020016101cc9190610709565b604051602081830303815290604052955050505050505b915050805190602001f35b6102086004803603810190610203919061093a565b610226565b005b610224600480360381019061021f9190610983565b6102ec565b005b60005b81518110156102e8576102d5828281518110610248576102476109fe565b5b602002602001015160000151838381518110610267576102666109fe565b5b602002602001015160200151848481518110610286576102856109fe565b5b6020026020010151604001518585815181106102a5576102a46109fe565b5b6020026020010151606001518686815181106102c4576102c36109fe565b5b6020026020010151608001516102ec565b80806102e090610a66565b915050610229565b5050565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361035b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161035290610afa565b60405180910390fd5b80600080878787876040516020016103769493929190610520565b60405160208183030381529060405280519060200120815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050505050565b6000604051905090565b600080fd5b600080fd5b6000819050919050565b610406816103f3565b811461041157600080fd5b50565b600081359050610423816103fd565b92915050565b600060ff82169050919050565b61043f81610429565b811461044a57600080fd5b50565b60008135905061045c81610436565b92915050565b6000806000806080858703121561047c5761047b6103e9565b5b600061048a87828801610414565b945050602061049b8782880161044d565b93505060406104ac87828801610414565b92505060606104bd87828801610414565b91505092959194509250565b6000819050919050565b6104e46104df826103f3565b6104c9565b82525050565b60008160f81b9050919050565b6000610502826104ea565b9050919050565b61051a61051582610429565b6104f7565b82525050565b600061052c82876104d3565b60208201915061053c8286610509565b60018201915061054c82856104d3565b60208201915061055c82846104d3565b60208201915081905095945050505050565b600081905092915050565b82818337600083830152505050565b6000610594838561056e565b93506105a1838584610579565b82840190509392505050565b60006105ba828486610588565b91508190509392505050565b600082825260208201905092915050565b7f6661696c656420746f2063616c6c206d6f7665642065637265636f766572206160008201527f742061646472657373203078303030303030303030303030303030303030303060208201527f3030303030303030303030303030313233343536000000000000000000000000604082015250565b60006106596054836105c6565b9150610664826105d7565b606082019050919050565b600060208201905081810360008301526106888161064c565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006106ba8261068f565b9050919050565b60008160601b9050919050565b60006106d9826106c1565b9050919050565b60006106eb826106ce565b9050919050565b6107036106fe826106af565b6106e0565b82525050565b600061071582846106f2565b60148201915081905092915050565b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61077282610729565b810181811067ffffffffffffffff821117156107915761079061073a565b5b80604052505050565b60006107a46103df565b90506107b08282610769565b919050565b600067ffffffffffffffff8211156107d0576107cf61073a565b5b602082029050602081019050919050565b600080fd5b600080fd5b6107f4816106af565b81146107ff57600080fd5b50565b600081359050610811816107eb565b92915050565b600060a0828403121561082d5761082c6107e6565b5b61083760a061079a565b9050600061084784828501610414565b600083015250602061085b8482850161044d565b602083015250604061086f84828501610414565b604083015250606061088384828501610414565b606083015250608061089784828501610802565b60808301525092915050565b60006108b66108b1846107b5565b61079a565b90508083825260208201905060a084028301858111156108d9576108d86107e1565b5b835b8181101561090257806108ee8882610817565b84526020840193505060a0810190506108db565b5050509392505050565b600082601f83011261092157610920610724565b5b81356109318482602086016108a3565b91505092915050565b6000602082840312156109505761094f6103e9565b5b600082013567ffffffffffffffff81111561096e5761096d6103ee565b5b61097a8482850161090c565b91505092915050565b600080600080600060a0868803121561099f5761099e6103e9565b5b60006109ad88828901610414565b95505060206109be8882890161044d565b94505060406109cf88828901610414565b93505060606109e088828901610414565b92505060806109f188828901610802565b9150509295509295909350565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000819050919050565b6000610a7182610a5c565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610aa357610aa2610a2d565b5b600182019050919050565b7f72657475726e20616464726573732063616e6e6f742062652030783000000000600082015250565b6000610ae4601c836105c6565b9150610aef82610aae565b602082019050919050565b60006020820190508181036000830152610b1381610ad7565b905091905056fea2646970667358221220154f5b68ccfa5be744e7245765a3530dac4035052284a68b5dded1945b45075e64736f6c63430008120033\", \"MovePrecompileToAddress\": \"0x0000000000000000000000000000000000123456\"}, \"0xc100000000000000000000000000000000000000\": {\"balance\": \"0x30d40\"}}, \"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000123456\", \"input\": \"0x4554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554480000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000123456\", \"input\": \"0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8000000000000000000000000000000000000000000000000000000000000001cb7cf302145348387b9e69fde82d8e634a0f8761e78da3bfa059efced97cbed0d2a66b69167cafe0ccfc726aec6ee393fea3cf0e4f3f9c394705e0f56d9bfe1c9\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000001\", \"input\": \"0xc00692604554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554480000000000000000000000000000000000000000000000000000000000000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000001\", \"input\": \"0x4554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554480000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000001\", \"input\": \"0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8000000000000000000000000000000000000000000000000000000000000001cb7cf302145348387b9e69fde82d8e634a0f8761e78da3bfa059efced97cbed0d2a66b69167cafe0ccfc726aec6ee393fea3cf0e4f3f9c394705e0f56d9bfe1c9\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000001\", \"input\": \"0x4554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554490000000000000000000000000000000000000000000000000000000000\"}]}]}"}, -new object[] {"multicall-override-identity", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0x0000000000000000000000000000000000000004\": {\"code\": \"0x\", \"MovePrecompileToAddress\": \"0x0000000000000000000000000000000000123456\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000123456\", \"input\": \"0x1234\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000004\", \"input\": \"0x1234\"}]}]}"}, -new object[] {"multicall-override-sha256", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0x0000000000000000000000000000000000000002\": {\"code\": \"0x\", \"MovePrecompileToAddress\": \"0x0000000000000000000000000000000000123456\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000123456\", \"input\": \"0x1234\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000002\", \"input\": \"0x1234\"}]}]}"}, -new object[] {"multicall-override-storage-slots", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}, \"0xc100000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80630ff4c916146100515780633033413b1461008157806344e12f871461009f5780637b8d56e3146100bd575b600080fd5b61006b600480360381019061006691906101f6565b6100d9565b6040516100789190610232565b60405180910390f35b61008961013f565b6040516100969190610232565b60405180910390f35b6100a7610145565b6040516100b49190610232565b60405180910390f35b6100d760048036038101906100d2919061024d565b61014b565b005b60006002821061011e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610115906102ea565b60405180910390fd5b6000820361012c5760005490505b6001820361013a5760015490505b919050565b60015481565b60005481565b6002821061018e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610185906102ea565b60405180910390fd5b600082036101a257806000819055506101b7565b600182036101b657806001819055506101b7565b5b5050565b600080fd5b6000819050919050565b6101d3816101c0565b81146101de57600080fd5b50565b6000813590506101f0816101ca565b92915050565b60006020828403121561020c5761020b6101bb565b5b600061021a848285016101e1565b91505092915050565b61022c816101c0565b82525050565b60006020820190506102476000830184610223565b92915050565b60008060408385031215610264576102636101bb565b5b6000610272858286016101e1565b9250506020610283858286016101e1565b9150509250929050565b600082825260208201905092915050565b7f746f6f2062696720736c6f740000000000000000000000000000000000000000600082015250565b60006102d4600c8361028d565b91506102df8261029e565b602082019050919050565b60006020820190508181036000830152610303816102c7565b905091905056fea2646970667358221220ceea194bb66b5b9f52c83e5bf5a1989255de8cb7157838eff98f970c3a04cb3064736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x7b8d56e300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x7b8d56e300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000001\"}]}, {\"stateOverrides\": {\"0xc100000000000000000000000000000000000000\": {\"stateDiff\": {\"0x0000000000000000000000000000000000000000000000000000000000000000\": \"0x1200000000000000000000000000000000000000000000000000000000000000\"}}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000001\"}]}, {\"stateOverrides\": {\"0xc100000000000000000000000000000000000000\": {\"state\": {\"0x0000000000000000000000000000000000000000000000000000000000000000\": \"0x1200000000000000000000000000000000000000000000000000000000000000\"}}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000001\"}]}], \"traceTransfers\": true}"}, -new object[] {"multicall-precompile-is-sending-transaction", "{\"blockStateCalls\": [{\"calls\": [{\"from\": \"0x0000000000000000000000000000000000000004\", \"to\": \"0x0000000000000000000000000000000000000002\", \"input\": \"0x1234\"}]}]}"}, -new object[] {"multicall-run-gas-spending", "{\"blockStateCalls\": [{\"blockOverrides\": {\"gasLimit\": \"0x16e360\"}, \"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x1e8480\"}, \"0xc200000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063815b8ab414610030575b600080fd5b61004a600480360381019061004591906100b6565b61004c565b005b60005a90505b60011561007657815a826100669190610112565b106100715750610078565b610052565b505b50565b600080fd5b6000819050919050565b61009381610080565b811461009e57600080fd5b50565b6000813590506100b08161008a565b92915050565b6000602082840312156100cc576100cb61007b565b5b60006100da848285016100a1565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061011d82610080565b915061012883610080565b92508282039050818111156101405761013f6100e3565b5b9291505056fea2646970667358221220a659ba4db729a6ee4db02fcc5c1118db53246b0e5e686534fc9add6f2e93faec64736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab40000000000000000000000000000000000000000000000000000000000000000\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab40000000000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab400000000000000000000000000000000000000000000000000000000000f4240\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab40000000000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab400000000000000000000000000000000000000000000000000000000000f4240\"}]}]}"}, -new object[] {"multicall-run-out-of-gas-in-block-38015", "{\"blockStateCalls\": [{\"blockOverrides\": {\"gasLimit\": \"0x16e360\"}, \"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x1e8480\"}, \"0xc200000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063815b8ab414610030575b600080fd5b61004a600480360381019061004591906100b6565b61004c565b005b60005a90505b60011561007657815a826100669190610112565b106100715750610078565b610052565b505b50565b600080fd5b6000819050919050565b61009381610080565b811461009e57600080fd5b50565b6000813590506100b08161008a565b92915050565b6000602082840312156100cc576100cb61007b565b5b60006100da848285016100a1565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061011d82610080565b915061012883610080565b92508282039050818111156101405761013f6100e3565b5b9291505056fea2646970667358221220a659ba4db729a6ee4db02fcc5c1118db53246b0e5e686534fc9add6f2e93faec64736f6c63430008120033\"}}}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab400000000000000000000000000000000000000000000000000000000000f4240\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab400000000000000000000000000000000000000000000000000000000000f4240\"}]}]}"}, -new object[] {"multicall-self-destructing-state-override", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc200000000000000000000000000000000000000\": {\"code\": \"0x6080604052348015600f57600080fd5b506004361060285760003560e01c806383197ef014602d575b600080fd5b60336035565b005b600073ffffffffffffffffffffffffffffffffffffffff16fffea26469706673582212208e566fde20a17fff9658b9b1db37e27876fd8934ccf9b2aa308cabd37698681f64736f6c63430008120033\"}, \"0xc300000000000000000000000000000000000000\": {\"code\": \"0x73000000000000000000000000000000000000000030146080604052600436106100355760003560e01c8063dce4a4471461003a575b600080fd5b610054600480360381019061004f91906100f8565b61006a565b60405161006191906101b5565b60405180910390f35b6060813b6040519150601f19601f602083010116820160405280825280600060208401853c50919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100c58261009a565b9050919050565b6100d5816100ba565b81146100e057600080fd5b50565b6000813590506100f2816100cc565b92915050565b60006020828403121561010e5761010d610095565b5b600061011c848285016100e3565b91505092915050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561015f578082015181840152602081019050610144565b60008484015250505050565b6000601f19601f8301169050919050565b600061018782610125565b6101918185610130565b93506101a1818560208601610141565b6101aa8161016b565b840191505092915050565b600060208201905081810360008301526101cf818461017c565b90509291505056fea26469706673582212206a5f0cd9f230619fa520fc4b9d4b518643258cad412f2fa33945ce528b4b895164736f6c63430008120033\"}}}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc300000000000000000000000000000000000000\", \"input\": \"0xdce4a447000000000000000000000000c200000000000000000000000000000000000000\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x83197ef0\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc300000000000000000000000000000000000000\", \"input\": \"0xdce4a447000000000000000000000000c200000000000000000000000000000000000000\"}]}, {\"stateOverrides\": {\"0xc200000000000000000000000000000000000000\": {\"code\": \"0x6080604052348015600f57600080fd5b506004361060285760003560e01c806383197ef014602d575b600080fd5b60336035565b005b600073ffffffffffffffffffffffffffffffffffffffff16fffea26469706673582212208e566fde20a17fff9658b9b1db37e27876fd8934ccf9b2aa308cabd37698681f64736f6c63430008120033\"}}}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc300000000000000000000000000000000000000\", \"input\": \"0xdce4a447000000000000000000000000c200000000000000000000000000000000000000\"}]}]}"}, -new object[] {"multicall-self-destructive-contract-produces-logs", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc200000000000000000000000000000000000000\": {\"code\": \"0x6080604052348015600f57600080fd5b506004361060285760003560e01c806383197ef014602d575b600080fd5b60336035565b005b600073ffffffffffffffffffffffffffffffffffffffff16fffea26469706673582212208e566fde20a17fff9658b9b1db37e27876fd8934ccf9b2aa308cabd37698681f64736f6c63430008120033\", \"balance\": \"0x1e8480\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x83197ef0\"}]}], \"traceTransfers\": true}"}, -new object[] {"multicall-set-read-storage", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc200000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea2646970667358221220404e37f487a89a932dca5e77faaf6ca2de3b991f93d230604b1b8daaef64766264736f6c63430008070033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x6057361d0000000000000000000000000000000000000000000000000000000000000005\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x2e64cec1\"}]}]}"}, -new object[] {"multicall-simple", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x3e8\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}]}"}, -new object[] {"multicall-simple-send-from-contract", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"code\": \"0x60806040526004361061001e5760003560e01c80634b64e49214610023575b600080fd5b61003d6004803603810190610038919061011f565b61003f565b005b60008173ffffffffffffffffffffffffffffffffffffffff166108fc349081150290604051600060405180830381858888f193505050509050806100b8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100af906101a9565b60405180910390fd5b5050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100ec826100c1565b9050919050565b6100fc816100e1565b811461010757600080fd5b50565b600081359050610119816100f3565b92915050565b600060208284031215610135576101346100bc565b5b60006101438482850161010a565b91505092915050565b600082825260208201905092915050565b7f4661696c656420746f2073656e64204574686572000000000000000000000000600082015250565b600061019360148361014c565b915061019e8261015d565b602082019050919050565b600060208201905081810360008301526101c281610186565b905091905056fea2646970667358221220563acd6f5b8ad06a3faf5c27fddd0ecbc198408b99290ce50d15c2cf7043694964736f6c63430008120033\", \"balance\": \"0x3e8\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}], \"traceTransfers\": true}"}, -new object[] {"multicall-simple-state-diff", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}, \"0xc100000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80630ff4c916146100515780633033413b1461008157806344e12f871461009f5780637b8d56e3146100bd575b600080fd5b61006b600480360381019061006691906101f6565b6100d9565b6040516100789190610232565b60405180910390f35b61008961013f565b6040516100969190610232565b60405180910390f35b6100a7610145565b6040516100b49190610232565b60405180910390f35b6100d760048036038101906100d2919061024d565b61014b565b005b60006002821061011e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610115906102ea565b60405180910390fd5b6000820361012c5760005490505b6001820361013a5760015490505b919050565b60015481565b60005481565b6002821061018e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610185906102ea565b60405180910390fd5b600082036101a257806000819055506101b7565b600182036101b657806001819055506101b7565b5b5050565b600080fd5b6000819050919050565b6101d3816101c0565b81146101de57600080fd5b50565b6000813590506101f0816101ca565b92915050565b60006020828403121561020c5761020b6101bb565b5b600061021a848285016101e1565b91505092915050565b61022c816101c0565b82525050565b60006020820190506102476000830184610223565b92915050565b60008060408385031215610264576102636101bb565b5b6000610272858286016101e1565b9250506020610283858286016101e1565b9150509250929050565b600082825260208201905092915050565b7f746f6f2062696720736c6f740000000000000000000000000000000000000000600082015250565b60006102d4600c8361028d565b91506102df8261029e565b602082019050919050565b60006020820190508181036000830152610303816102c7565b905091905056fea2646970667358221220ceea194bb66b5b9f52c83e5bf5a1989255de8cb7157838eff98f970c3a04cb3064736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x7b8d56e300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x7b8d56e300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002\"}]}, {\"stateOverrides\": {\"0xc100000000000000000000000000000000000000\": {\"state\": {\"0x0000000000000000000000000000000000000000000000000000000000000000\": \"0x1200000000000000000000000000000000000000000000000000000000000000\"}}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000001\"}]}], \"traceTransfers\": true}"}, -new object[] {"multicall-simple-with-validation-no-funds", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x3e8\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}]}"}, -new object[] {"multicall-transaction-too-high-nonce", "{\"blockStateCalls\": [{\"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"nonce\": \"0x64\"}]}]}"}, -new object[] {"multicall-transaction-too-low-nonce-38010", "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"nonce\": \"0xa\"}}, \"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"nonce\": \"0x0\"}]}]}"}, +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\"}}]}"}, +new object[] {"multicall-blockhash-complex", true, "{\"blockStateCalls\": [{\"blockOverrides\": {\"number\": \"0xa\"}, \"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x1e8480\"}, \"0xc200000000000000000000000000000000000000\": {\"code\": \"0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063ee82ac5e14602d575b600080fd5b60436004803603810190603f91906098565b6057565b604051604e919060d7565b60405180910390f35b600081409050919050565b600080fd5b6000819050919050565b6078816067565b8114608257600080fd5b50565b6000813590506092816071565b92915050565b60006020828403121560ab5760aa6062565b5b600060b7848285016085565b91505092915050565b6000819050919050565b60d18160c0565b82525050565b600060208201905060ea600083018460ca565b9291505056fea2646970667358221220a4d7face162688805e99e86526524ac3dadfb01cc29366d0d68b70dadcf01afe64736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e0000000000000000000000000000000000000000000000000000000000000001\"}]}, {\"blockOverrides\": {\"number\": \"0x14\"}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e000000000000000000000000000000000000000000000000000000000000000a\"}]}, {\"blockOverrides\": {\"number\": \"0x1e\"}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e000000000000000000000000000000000000000000000000000000000000001d\"}]}]}"}, +new object[] {"multicall-blockhash-simple", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc200000000000000000000000000000000000000\": {\"code\": \"0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063ee82ac5e14602d575b600080fd5b60436004803603810190603f91906098565b6057565b604051604e919060d7565b60405180910390f35b600081409050919050565b600080fd5b6000819050919050565b6078816067565b8114608257600080fd5b50565b6000813590506092816071565b92915050565b60006020828403121560ab5760aa6062565b5b600060b7848285016085565b91505092915050565b6000819050919050565b60d18160c0565b82525050565b600060208201905060ea600083018460ca565b9291505056fea2646970667358221220a4d7face162688805e99e86526524ac3dadfb01cc29366d0d68b70dadcf01afe64736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e0000000000000000000000000000000000000000000000000000000000000001\"}]}]}"}, +new object[] {"multicall-blockhash-start-before-head", true, "{\"blockStateCalls\": [{\"blockOverrides\": {\"number\": \"0xa\"}, \"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x1e8480\"}, \"0xc200000000000000000000000000000000000000\": {\"code\": \"0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063ee82ac5e14602d575b600080fd5b60436004803603810190603f91906098565b6057565b604051604e919060d7565b60405180910390f35b600081409050919050565b600080fd5b6000819050919050565b6078816067565b8114608257600080fd5b50565b6000813590506092816071565b92915050565b60006020828403121560ab5760aa6062565b5b600060b7848285016085565b91505092915050565b6000819050919050565b60d18160c0565b82525050565b600060208201905060ea600083018460ca565b9291505056fea2646970667358221220a4d7face162688805e99e86526524ac3dadfb01cc29366d0d68b70dadcf01afe64736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e0000000000000000000000000000000000000000000000000000000000000001\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e0000000000000000000000000000000000000000000000000000000000000002\"}]}, {\"blockOverrides\": {\"number\": \"0x14\"}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xee82ac5e0000000000000000000000000000000000000000000000000000000000000013\"}]}]}"}, +new object[] {"multicall-check-that-balance-is-there-after-new-block", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x2710\"}, \"0xc200000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f8b2cb4f14610030575b600080fd5b61004a600480360381019061004591906100e4565b610060565b604051610057919061012a565b60405180910390f35b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100b182610086565b9050919050565b6100c1816100a6565b81146100cc57600080fd5b50565b6000813590506100de816100b8565b92915050565b6000602082840312156100fa576100f9610081565b5b6000610108848285016100cf565b91505092915050565b6000819050919050565b61012481610111565b82525050565b600060208201905061013f600083018461011b565b9291505056fea2646970667358221220172c443a163d8a43e018c339d1b749c312c94b6de22835953d960985daf228c764736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xf8b2cb4f000000000000000000000000c000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xf8b2cb4f000000000000000000000000c100000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xf8b2cb4f000000000000000000000000c000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0xf8b2cb4f000000000000000000000000c100000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}]}"}, +new object[] {"multicall-contract-calls-itself", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506000366060484641444543425a3a60014361002c919061009b565b406040516020016100469a99989796959493929190610138565b6040516020818303038152906040529050915050805190602001f35b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006100a682610062565b91506100b183610062565b92508282039050818111156100c9576100c861006c565b5b92915050565b6100d881610062565b82525050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610109826100de565b9050919050565b610119816100fe565b82525050565b6000819050919050565b6101328161011f565b82525050565b60006101408201905061014e600083018d6100cf565b61015b602083018c6100cf565b610168604083018b610110565b610175606083018a6100cf565b61018260808301896100cf565b61018f60a08301886100cf565b61019c60c08301876100cf565b6101a960e08301866100cf565b6101b76101008301856100cf565b6101c5610120830184610129565b9b9a505050505050505050505056fea26469706673582212205139ae3ba8d46d11c29815d001b725f9840c90e330884ed070958d5af4813d8764736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc000000000000000000000000000000000000000\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-empty-calls-and-overrides-multicall", true, "{\"blockStateCalls\": [{\"stateOverrides\": {}, \"calls\": [{}]}, {\"stateOverrides\": {}, \"calls\": [{}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-eth-send-should-not-produce-logs-by-default", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}]}"}, +new object[] {"multicall-eth-send-should-not-produce-logs-on-revert", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}, \"0xc100000000000000000000000000000000000000\": {\"code\": \"0x608060405260006042576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401603990609d565b60405180910390fd5b005b600082825260208201905092915050565b7f416c7761797320726576657274696e6720636f6e747261637400000000000000600082015250565b600060896019836044565b91506092826055565b602082019050919050565b6000602082019050818103600083015260b481607e565b905091905056fea264697066735822122005cbbbc709291f66fadc17416c1b0ed4d72941840db11468a21b8e1a0362024c64736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-eth-send-should-produce-logs", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}], \"traceTransfers\": true}"}, +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-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\"}]}]}"}, +new object[] {"multicall-move-to-address-itself-reference-38022", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x30d40\"}, \"0x0000000000000000000000000000000000000001\": {\"MovePrecompileToAddress\": \"0x0000000000000000000000000000000000000001\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000001\", \"value\": \"0x1\"}]}]}"}, +new object[] {"multicall-move-two-accounts-to-same-38023", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0x0000000000000000000000000000000000000001\": {\"MovePrecompileToAddress\": \"0xc200000000000000000000000000000000000000\"}, \"0x0000000000000000000000000000000000000002\": {\"MovePrecompileToAddress\": \"0xc200000000000000000000000000000000000000\"}}}]}"}, +new object[] {"multicall-move-two-non-precompiles-accounts-to-same", false,"{\"blockStateCalls\": [{\"stateOverrides\": {\"0x0100000000000000000000000000000000000000\": {\"MovePrecompileToAddress\": \"0xc200000000000000000000000000000000000000\"}, \"0x0200000000000000000000000000000000000000\": {\"MovePrecompileToAddress\": \"0xc200000000000000000000000000000000000000\"}}}]}"}, +new object[] {"multicall-no-fields-call", true, "{\"blockStateCalls\": [{\"calls\": [{}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-only-from-to-transaction", true, "{\"blockStateCalls\": [{\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-only-from-transaction", true, "{\"blockStateCalls\": [{\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-override-address-twice-in-separate-BlockStateCalls", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}, {\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-override-all-in-BlockStateCalls", true, "{\"blockStateCalls\": [{\"blockOverrides\": {\"number\": \"0x5\", \"time\": \"0x5e47e929\", \"gasLimit\": \"0x3ec\", \"feeRecipient\": \"0xc200000000000000000000000000000000000000\", \"prevRandao\": \"0xc300000000000000000000000000000000000000000000000000000000000000\", \"baseFeePerGas\": \"0x3ef\"}}]}"}, +new object[] {"multicall-override-block-num", true, "{\"blockStateCalls\": [{\"blockOverrides\": {\"number\": \"0xb\"}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"input\": \"0x4360005260206000f3\"}]}, {\"blockOverrides\": {\"number\": \"0xc\"}, \"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x4360005260206000f3\"}]}]}"}, +new object[] {"multicall-override-ecrecover", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0x0000000000000000000000000000000000000001\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061003a5760003560e01c806305fdbc81146101ee578063c00692601461020a5761003b565b5b600036606060008060008086868101906100559190610462565b93509350935093506000806000868686866040516020016100799493929190610520565b60405160208183030381529060405280519060200120815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036101bb576000806212345673ffffffffffffffffffffffffffffffffffffffff166127108b8b6040516101249291906105ad565b60006040518083038160008787f1925050503d8060008114610162576040519150601f19603f3d011682016040523d82523d6000602084013e610167565b606091505b5091509150816101ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101a39061066f565b60405180910390fd5b809750505050505050506101e3565b806040516020016101cc9190610709565b604051602081830303815290604052955050505050505b915050805190602001f35b6102086004803603810190610203919061093a565b610226565b005b610224600480360381019061021f9190610983565b6102ec565b005b60005b81518110156102e8576102d5828281518110610248576102476109fe565b5b602002602001015160000151838381518110610267576102666109fe565b5b602002602001015160200151848481518110610286576102856109fe565b5b6020026020010151604001518585815181106102a5576102a46109fe565b5b6020026020010151606001518686815181106102c4576102c36109fe565b5b6020026020010151608001516102ec565b80806102e090610a66565b915050610229565b5050565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361035b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161035290610afa565b60405180910390fd5b80600080878787876040516020016103769493929190610520565b60405160208183030381529060405280519060200120815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050505050565b6000604051905090565b600080fd5b600080fd5b6000819050919050565b610406816103f3565b811461041157600080fd5b50565b600081359050610423816103fd565b92915050565b600060ff82169050919050565b61043f81610429565b811461044a57600080fd5b50565b60008135905061045c81610436565b92915050565b6000806000806080858703121561047c5761047b6103e9565b5b600061048a87828801610414565b945050602061049b8782880161044d565b93505060406104ac87828801610414565b92505060606104bd87828801610414565b91505092959194509250565b6000819050919050565b6104e46104df826103f3565b6104c9565b82525050565b60008160f81b9050919050565b6000610502826104ea565b9050919050565b61051a61051582610429565b6104f7565b82525050565b600061052c82876104d3565b60208201915061053c8286610509565b60018201915061054c82856104d3565b60208201915061055c82846104d3565b60208201915081905095945050505050565b600081905092915050565b82818337600083830152505050565b6000610594838561056e565b93506105a1838584610579565b82840190509392505050565b60006105ba828486610588565b91508190509392505050565b600082825260208201905092915050565b7f6661696c656420746f2063616c6c206d6f7665642065637265636f766572206160008201527f742061646472657373203078303030303030303030303030303030303030303060208201527f3030303030303030303030303030313233343536000000000000000000000000604082015250565b60006106596054836105c6565b9150610664826105d7565b606082019050919050565b600060208201905081810360008301526106888161064c565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006106ba8261068f565b9050919050565b60008160601b9050919050565b60006106d9826106c1565b9050919050565b60006106eb826106ce565b9050919050565b6107036106fe826106af565b6106e0565b82525050565b600061071582846106f2565b60148201915081905092915050565b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61077282610729565b810181811067ffffffffffffffff821117156107915761079061073a565b5b80604052505050565b60006107a46103df565b90506107b08282610769565b919050565b600067ffffffffffffffff8211156107d0576107cf61073a565b5b602082029050602081019050919050565b600080fd5b600080fd5b6107f4816106af565b81146107ff57600080fd5b50565b600081359050610811816107eb565b92915050565b600060a0828403121561082d5761082c6107e6565b5b61083760a061079a565b9050600061084784828501610414565b600083015250602061085b8482850161044d565b602083015250604061086f84828501610414565b604083015250606061088384828501610414565b606083015250608061089784828501610802565b60808301525092915050565b60006108b66108b1846107b5565b61079a565b90508083825260208201905060a084028301858111156108d9576108d86107e1565b5b835b8181101561090257806108ee8882610817565b84526020840193505060a0810190506108db565b5050509392505050565b600082601f83011261092157610920610724565b5b81356109318482602086016108a3565b91505092915050565b6000602082840312156109505761094f6103e9565b5b600082013567ffffffffffffffff81111561096e5761096d6103ee565b5b61097a8482850161090c565b91505092915050565b600080600080600060a0868803121561099f5761099e6103e9565b5b60006109ad88828901610414565b95505060206109be8882890161044d565b94505060406109cf88828901610414565b93505060606109e088828901610414565b92505060806109f188828901610802565b9150509295509295909350565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000819050919050565b6000610a7182610a5c565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610aa357610aa2610a2d565b5b600182019050919050565b7f72657475726e20616464726573732063616e6e6f742062652030783000000000600082015250565b6000610ae4601c836105c6565b9150610aef82610aae565b602082019050919050565b60006020820190508181036000830152610b1381610ad7565b905091905056fea2646970667358221220154f5b68ccfa5be744e7245765a3530dac4035052284a68b5dded1945b45075e64736f6c63430008120033\", \"MovePrecompileToAddress\": \"0x0000000000000000000000000000000000123456\"}, \"0xc100000000000000000000000000000000000000\": {\"balance\": \"0x30d40\"}}, \"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000123456\", \"input\": \"0x4554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554480000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000123456\", \"input\": \"0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8000000000000000000000000000000000000000000000000000000000000001cb7cf302145348387b9e69fde82d8e634a0f8761e78da3bfa059efced97cbed0d2a66b69167cafe0ccfc726aec6ee393fea3cf0e4f3f9c394705e0f56d9bfe1c9\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000001\", \"input\": \"0xc00692604554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554480000000000000000000000000000000000000000000000000000000000000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000001\", \"input\": \"0x4554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554480000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000001\", \"input\": \"0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8000000000000000000000000000000000000000000000000000000000000001cb7cf302145348387b9e69fde82d8e634a0f8761e78da3bfa059efced97cbed0d2a66b69167cafe0ccfc726aec6ee393fea3cf0e4f3f9c394705e0f56d9bfe1c9\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000001\", \"input\": \"0x4554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554490000000000000000000000000000000000000000000000000000000000\"}]}]}"}, +new object[] {"multicall-override-identity", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0x0000000000000000000000000000000000000004\": {\"code\": \"0x\", \"MovePrecompileToAddress\": \"0x0000000000000000000000000000000000123456\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000123456\", \"input\": \"0x1234\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000004\", \"input\": \"0x1234\"}]}]}"}, +new object[] {"multicall-override-sha256", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0x0000000000000000000000000000000000000002\": {\"code\": \"0x\", \"MovePrecompileToAddress\": \"0x0000000000000000000000000000000000123456\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000123456\", \"input\": \"0x1234\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000002\", \"input\": \"0x1234\"}]}]}"}, +new object[] {"multicall-override-storage-slots", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}, \"0xc100000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80630ff4c916146100515780633033413b1461008157806344e12f871461009f5780637b8d56e3146100bd575b600080fd5b61006b600480360381019061006691906101f6565b6100d9565b6040516100789190610232565b60405180910390f35b61008961013f565b6040516100969190610232565b60405180910390f35b6100a7610145565b6040516100b49190610232565b60405180910390f35b6100d760048036038101906100d2919061024d565b61014b565b005b60006002821061011e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610115906102ea565b60405180910390fd5b6000820361012c5760005490505b6001820361013a5760015490505b919050565b60015481565b60005481565b6002821061018e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610185906102ea565b60405180910390fd5b600082036101a257806000819055506101b7565b600182036101b657806001819055506101b7565b5b5050565b600080fd5b6000819050919050565b6101d3816101c0565b81146101de57600080fd5b50565b6000813590506101f0816101ca565b92915050565b60006020828403121561020c5761020b6101bb565b5b600061021a848285016101e1565b91505092915050565b61022c816101c0565b82525050565b60006020820190506102476000830184610223565b92915050565b60008060408385031215610264576102636101bb565b5b6000610272858286016101e1565b9250506020610283858286016101e1565b9150509250929050565b600082825260208201905092915050565b7f746f6f2062696720736c6f740000000000000000000000000000000000000000600082015250565b60006102d4600c8361028d565b91506102df8261029e565b602082019050919050565b60006020820190508181036000830152610303816102c7565b905091905056fea2646970667358221220ceea194bb66b5b9f52c83e5bf5a1989255de8cb7157838eff98f970c3a04cb3064736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x7b8d56e300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x7b8d56e300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000001\"}]}, {\"stateOverrides\": {\"0xc100000000000000000000000000000000000000\": {\"stateDiff\": {\"0x0000000000000000000000000000000000000000000000000000000000000000\": \"0x1200000000000000000000000000000000000000000000000000000000000000\"}}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000001\"}]}, {\"stateOverrides\": {\"0xc100000000000000000000000000000000000000\": {\"state\": {\"0x0000000000000000000000000000000000000000000000000000000000000000\": \"0x1200000000000000000000000000000000000000000000000000000000000000\"}}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000001\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-precompile-is-sending-transaction", true, "{\"blockStateCalls\": [{\"calls\": [{\"from\": \"0x0000000000000000000000000000000000000004\", \"to\": \"0x0000000000000000000000000000000000000002\", \"input\": \"0x1234\"}]}]}"}, +new object[] {"multicall-run-gas-spending", true, "{\"blockStateCalls\": [{\"blockOverrides\": {\"gasLimit\": \"0x16e360\"}, \"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x1e8480\"}, \"0xc200000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063815b8ab414610030575b600080fd5b61004a600480360381019061004591906100b6565b61004c565b005b60005a90505b60011561007657815a826100669190610112565b106100715750610078565b610052565b505b50565b600080fd5b6000819050919050565b61009381610080565b811461009e57600080fd5b50565b6000813590506100b08161008a565b92915050565b6000602082840312156100cc576100cb61007b565b5b60006100da848285016100a1565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061011d82610080565b915061012883610080565b92508282039050818111156101405761013f6100e3565b5b9291505056fea2646970667358221220a659ba4db729a6ee4db02fcc5c1118db53246b0e5e686534fc9add6f2e93faec64736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab40000000000000000000000000000000000000000000000000000000000000000\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab40000000000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab400000000000000000000000000000000000000000000000000000000000f4240\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab40000000000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab400000000000000000000000000000000000000000000000000000000000f4240\"}]}]}"}, +new object[] {"multicall-run-out-of-gas-in-block-38015", true, "{\"blockStateCalls\": [{\"blockOverrides\": {\"gasLimit\": \"0x16e360\"}, \"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x1e8480\"}, \"0xc200000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063815b8ab414610030575b600080fd5b61004a600480360381019061004591906100b6565b61004c565b005b60005a90505b60011561007657815a826100669190610112565b106100715750610078565b610052565b505b50565b600080fd5b6000819050919050565b61009381610080565b811461009e57600080fd5b50565b6000813590506100b08161008a565b92915050565b6000602082840312156100cc576100cb61007b565b5b60006100da848285016100a1565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061011d82610080565b915061012883610080565b92508282039050818111156101405761013f6100e3565b5b9291505056fea2646970667358221220a659ba4db729a6ee4db02fcc5c1118db53246b0e5e686534fc9add6f2e93faec64736f6c63430008120033\"}}}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab400000000000000000000000000000000000000000000000000000000000f4240\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x815b8ab400000000000000000000000000000000000000000000000000000000000f4240\"}]}]}"}, +new object[] {"multicall-self-destructing-state-override", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc200000000000000000000000000000000000000\": {\"code\": \"0x6080604052348015600f57600080fd5b506004361060285760003560e01c806383197ef014602d575b600080fd5b60336035565b005b600073ffffffffffffffffffffffffffffffffffffffff16fffea26469706673582212208e566fde20a17fff9658b9b1db37e27876fd8934ccf9b2aa308cabd37698681f64736f6c63430008120033\"}, \"0xc300000000000000000000000000000000000000\": {\"code\": \"0x73000000000000000000000000000000000000000030146080604052600436106100355760003560e01c8063dce4a4471461003a575b600080fd5b610054600480360381019061004f91906100f8565b61006a565b60405161006191906101b5565b60405180910390f35b6060813b6040519150601f19601f602083010116820160405280825280600060208401853c50919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100c58261009a565b9050919050565b6100d5816100ba565b81146100e057600080fd5b50565b6000813590506100f2816100cc565b92915050565b60006020828403121561010e5761010d610095565b5b600061011c848285016100e3565b91505092915050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561015f578082015181840152602081019050610144565b60008484015250505050565b6000601f19601f8301169050919050565b600061018782610125565b6101918185610130565b93506101a1818560208601610141565b6101aa8161016b565b840191505092915050565b600060208201905081810360008301526101cf818461017c565b90509291505056fea26469706673582212206a5f0cd9f230619fa520fc4b9d4b518643258cad412f2fa33945ce528b4b895164736f6c63430008120033\"}}}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc300000000000000000000000000000000000000\", \"input\": \"0xdce4a447000000000000000000000000c200000000000000000000000000000000000000\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x83197ef0\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc300000000000000000000000000000000000000\", \"input\": \"0xdce4a447000000000000000000000000c200000000000000000000000000000000000000\"}]}, {\"stateOverrides\": {\"0xc200000000000000000000000000000000000000\": {\"code\": \"0x6080604052348015600f57600080fd5b506004361060285760003560e01c806383197ef014602d575b600080fd5b60336035565b005b600073ffffffffffffffffffffffffffffffffffffffff16fffea26469706673582212208e566fde20a17fff9658b9b1db37e27876fd8934ccf9b2aa308cabd37698681f64736f6c63430008120033\"}}}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc300000000000000000000000000000000000000\", \"input\": \"0xdce4a447000000000000000000000000c200000000000000000000000000000000000000\"}]}]}"}, +new object[] {"multicall-self-destructive-contract-produces-logs", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc200000000000000000000000000000000000000\": {\"code\": \"0x6080604052348015600f57600080fd5b506004361060285760003560e01c806383197ef014602d575b600080fd5b60336035565b005b600073ffffffffffffffffffffffffffffffffffffffff16fffea26469706673582212208e566fde20a17fff9658b9b1db37e27876fd8934ccf9b2aa308cabd37698681f64736f6c63430008120033\", \"balance\": \"0x1e8480\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x83197ef0\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-set-read-storage", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc200000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea2646970667358221220404e37f487a89a932dca5e77faaf6ca2de3b991f93d230604b1b8daaef64766264736f6c63430008070033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x6057361d0000000000000000000000000000000000000000000000000000000000000005\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x2e64cec1\"}]}]}"}, +new object[] {"multicall-simple", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x3e8\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}]}"}, +new object[] {"multicall-simple-send-from-contract", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"code\": \"0x60806040526004361061001e5760003560e01c80634b64e49214610023575b600080fd5b61003d6004803603810190610038919061011f565b61003f565b005b60008173ffffffffffffffffffffffffffffffffffffffff166108fc349081150290604051600060405180830381858888f193505050509050806100b8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100af906101a9565b60405180910390fd5b5050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100ec826100c1565b9050919050565b6100fc816100e1565b811461010757600080fd5b50565b600081359050610119816100f3565b92915050565b600060208284031215610135576101346100bc565b5b60006101438482850161010a565b91505092915050565b600082825260208201905092915050565b7f4661696c656420746f2073656e64204574686572000000000000000000000000600082015250565b600061019360148361014c565b915061019e8261015d565b602082019050919050565b600060208201905081810360008301526101c281610186565b905091905056fea2646970667358221220563acd6f5b8ad06a3faf5c27fddd0ecbc198408b99290ce50d15c2cf7043694964736f6c63430008120033\", \"balance\": \"0x3e8\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-simple-state-diff", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}, \"0xc100000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80630ff4c916146100515780633033413b1461008157806344e12f871461009f5780637b8d56e3146100bd575b600080fd5b61006b600480360381019061006691906101f6565b6100d9565b6040516100789190610232565b60405180910390f35b61008961013f565b6040516100969190610232565b60405180910390f35b6100a7610145565b6040516100b49190610232565b60405180910390f35b6100d760048036038101906100d2919061024d565b61014b565b005b60006002821061011e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610115906102ea565b60405180910390fd5b6000820361012c5760005490505b6001820361013a5760015490505b919050565b60015481565b60005481565b6002821061018e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610185906102ea565b60405180910390fd5b600082036101a257806000819055506101b7565b600182036101b657806001819055506101b7565b5b5050565b600080fd5b6000819050919050565b6101d3816101c0565b81146101de57600080fd5b50565b6000813590506101f0816101ca565b92915050565b60006020828403121561020c5761020b6101bb565b5b600061021a848285016101e1565b91505092915050565b61022c816101c0565b82525050565b60006020820190506102476000830184610223565b92915050565b60008060408385031215610264576102636101bb565b5b6000610272858286016101e1565b9250506020610283858286016101e1565b9150509250929050565b600082825260208201905092915050565b7f746f6f2062696720736c6f740000000000000000000000000000000000000000600082015250565b60006102d4600c8361028d565b91506102df8261029e565b602082019050919050565b60006020820190508181036000830152610303816102c7565b905091905056fea2646970667358221220ceea194bb66b5b9f52c83e5bf5a1989255de8cb7157838eff98f970c3a04cb3064736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x7b8d56e300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x7b8d56e300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002\"}]}, {\"stateOverrides\": {\"0xc100000000000000000000000000000000000000\": {\"state\": {\"0x0000000000000000000000000000000000000000000000000000000000000000\": \"0x1200000000000000000000000000000000000000000000000000000000000000\"}}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x0ff4c9160000000000000000000000000000000000000000000000000000000000000001\"}]}], \"traceTransfers\": true}"}, +new object[] {"multicall-simple-with-validation-no-funds", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x3e8\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"value\": \"0x3e8\"}]}]}"}, +new object[] {"multicall-transaction-too-high-nonce", true, "{\"blockStateCalls\": [{\"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"nonce\": \"0x64\"}]}]}"}, +new object[] {"multicall-transaction-too-low-nonce-38010", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"nonce\": \"0xa\"}}, \"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"nonce\": \"0x0\"}]}]}"}, }; [Test, TestCaseSource(nameof(HiveTestCases)), Parallelizable(ParallelScope.All)] - public async Task TestsimulateHive(string name, string data) + 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); Console.WriteLine(); - Assert.That(result.Result.ResultType, Is.EqualTo(ResultType.Success)); - Assert.That(result.Data, Is.Not.Null); + if (shouldSucceed) + { + Assert.That(result.Result.ResultType, Is.EqualTo(ResultType.Success)); + Assert.That(result.Data, Is.Not.Null); + } + else + { + Assert.That(result.Result.ResultType, Is.EqualTo(ResultType.Failure)); + Assert.That(result.Data, Is.Null); + } + } /// @@ -119,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 { @@ -137,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 bf3fef65b980..ad5623d7f906 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 54aa2244d4fb..7c4566d8e549 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/ParityRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/ParityRpcModuleTests.cs @@ -15,15 +15,14 @@ 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; using Nethermind.Crypto; -using Nethermind.Db; using Nethermind.Int256; using Nethermind.JsonRpc.Modules.Parity; using Nethermind.Logging; -using Nethermind.Evm.State; using Nethermind.KeyStore; using Nethermind.Network; using Nethermind.Network.Contract.P2P; @@ -34,8 +33,7 @@ using NSubstitute; using NUnit.Framework; using System; -using Nethermind.Core.Test.Db; -using Nethermind.State; +using Nethermind.Serialization.Json; namespace Nethermind.JsonRpc.Test.Modules { @@ -64,7 +62,9 @@ public void Initialize() Peer peerC = SetUpPeerC(); //Node is null, Caps are empty IPeerManager peerManager = Substitute.For(); peerManager.ActivePeers.Returns(new List { peerA, peerB, peerC }); + peerManager.ActivePeersCount.Returns(3); peerManager.ConnectedPeers.Returns(new List { peerA, peerB, peerA, peerC, peerB }); + peerManager.ConnectedPeersCount.Returns(5); peerManager.MaxActivePeers.Returns(15); TestReadOnlyStateProvider stateProvider = new TestReadOnlyStateProvider(); @@ -358,6 +358,7 @@ public async Task parity_netPeers_empty_ActivePeers() IPeerManager peerManager = Substitute.For(); peerManager.ActivePeers.Returns(new List { }); peerManager.ConnectedPeers.Returns(new List { new(new Node(TestItem.PublicKeyA, "111.1.1.1", 11111, true)) }); + peerManager.ConnectedPeersCount.Returns(1); IParityRpcModule parityRpcModule = CreateParityRpcModule(peerManager); @@ -375,5 +376,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 ae5050b572c0..b1d44b34b49b 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Proof/ProofRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Proof/ProofRpcModuleTests.cs @@ -20,11 +20,11 @@ using Nethermind.Specs.Forks; using Nethermind.Evm.State; using Nethermind.State.Proofs; -using Nethermind.TxPool; using NUnit.Framework; using System.Threading.Tasks; using Autofac; using FluentAssertions; +using Nethermind.Blockchain.Headers; using Nethermind.Config; using Nethermind.Consensus.Processing; using Nethermind.Core.Buffers; @@ -44,10 +44,8 @@ namespace Nethermind.JsonRpc.Test.Modules.Proof; // [TestFixture(true, true)] TODO fix or remove test? [TestFixture(true, false)] [TestFixture(false, false)] -public class ProofRpcModuleTests +public class ProofRpcModuleTests(bool createZeroAccount, bool useNonZeroGasPrice) { - private readonly bool _createSystemAccount; - private readonly bool _useNonZeroGasPrice; private IProofRpcModule _proofRpcModule = null!; private IBlockTree _blockTree = null!; private IDbProvider _dbProvider = null!; @@ -55,12 +53,6 @@ public class ProofRpcModuleTests private WorldStateManager _worldStateManager = null!; private IContainer _container; - public ProofRpcModuleTests(bool createSystemAccount, bool useNonZeroGasPrice) - { - _createSystemAccount = createSystemAccount; - _useNonZeroGasPrice = useNonZeroGasPrice; - } - [SetUp] public async Task Setup() { @@ -68,7 +60,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); @@ -79,10 +71,10 @@ public async Task Setup() InMemoryReceiptStorage receiptStorage = new(); _specProvider = new TestSpecProvider(London.Instance); - _blockTree = Build.A.BlockTree(new Block(Build.A.BlockHeader.WithStateRoot(stateRoot).TestObject, new BlockBody()), _specProvider) + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree(new Block(Build.A.BlockHeader.WithStateRoot(stateRoot).TestObject, new BlockBody()), _specProvider) .WithTransactions(receiptStorage) - .OfChainLength(10) - .TestObject; + .OfChainLength(10); + _blockTree = blockTreeBuilder.TestObject; _container = new ContainerBuilder() .AddModule(new TestNethermindModule(new ConfigProvider())) @@ -90,6 +82,7 @@ public async Task Setup() .AddSingleton(new CompositeBlockPreprocessorStep(new RecoverSignatures(new EthereumEcdsa(TestBlockchainIds.ChainId), _specProvider, LimboLogs.Instance))) .AddSingleton(_blockTree) .AddSingleton(_dbProvider) + .AddSingleton(blockTreeBuilder.HeaderStore) .AddSingleton(receiptStorage) .AddSingleton(_worldStateManager) .Build(); @@ -176,14 +169,7 @@ public async Task Can_get_receipt(bool withHeader, string expectedResult) Assert.That(receiptWithProof.Receipt, Is.Not.Null); Assert.That(receiptWithProof.ReceiptProof.Length, Is.EqualTo(2)); - if (withHeader) - { - Assert.That(receiptWithProof.BlockHeader, Is.Not.Null); - } - else - { - Assert.That(receiptWithProof.BlockHeader, Is.Null); - } + Assert.That(receiptWithProof.BlockHeader, withHeader ? Is.Not.Null : Is.Null); string response = await RpcTest.TestSerializedRequest(_proofRpcModule, "proof_getTransactionReceipt", txHash, withHeader); response.Should().Be(expectedResult); @@ -274,7 +260,7 @@ public async Task Can_call() { From = TestItem.AddressA, To = TestItem.AddressB, - GasPrice = _useNonZeroGasPrice ? 10.GWei() : 0 + GasPrice = useNonZeroGasPrice ? 10.GWei() : 0 }; _proofRpcModule.proof_call(tx, new BlockParameter(block.Number)); @@ -297,7 +283,7 @@ public async Task Can_call_by_hash() { From = TestItem.AddressA, To = TestItem.AddressB, - GasPrice = _useNonZeroGasPrice ? 10.GWei() : 0 + GasPrice = useNonZeroGasPrice ? 10.GWei() : 0 }; _proofRpcModule.proof_call(tx, new BlockParameter(block.Hash!)); @@ -320,14 +306,14 @@ public async Task Can_call_by_hash_canonical() { From = TestItem.AddressA, To = TestItem.AddressB, - GasPrice = _useNonZeroGasPrice ? 10.GWei() : 0 + GasPrice = useNonZeroGasPrice ? 10.GWei() : 0 }; string response = await RpcTest.TestSerializedRequest(_proofRpcModule, "proof_call", tx, new { blockHash = block.Hash, requireCanonical = true }); Assert.That(response.Contains("-32000"), Is.True); response = await RpcTest.TestSerializedRequest(_proofRpcModule, "proof_call", tx, new { blockHash = TestItem.KeccakG, requireCanonical = true }); - Assert.That(response.Contains("-32001"), Is.True); + Assert.That(response.Contains(ErrorCodes.ResourceNotFound.ToString()), Is.True); } [TestCase] @@ -376,7 +362,7 @@ public async Task Can_call_with_storage_load() .Done; CallResultWithProof result = await TestCallWithCode(code); - Assert.That(result.Accounts.Length, Is.EqualTo(1 + (_useNonZeroGasPrice ? 1 : 0))); + Assert.That(result.Accounts.Length, Is.EqualTo(2)); } [TestCase] @@ -389,7 +375,7 @@ public async Task Can_call_with_many_storage_loads() .Op(Instruction.SLOAD) .Done; CallResultWithProof result = await TestCallWithCode(code); - Assert.That(result.Accounts.Length, Is.EqualTo(1 + (_useNonZeroGasPrice ? 1 : 0))); + Assert.That(result.Accounts.Length, Is.EqualTo(2)); } [TestCase] @@ -402,7 +388,7 @@ public async Task Can_call_with_storage_write() .Done; CallResultWithProof result = await TestCallWithCode(code); - Assert.That(result.Accounts.Length, Is.EqualTo(1 + (_useNonZeroGasPrice ? 1 : 0))); + Assert.That(result.Accounts.Length, Is.EqualTo(2)); } [TestCase] @@ -416,17 +402,17 @@ public async Task Can_call_with_extcodecopy() .Op(Instruction.EXTCODECOPY) .Done; CallResultWithProof result = await TestCallWithCode(code); - Assert.That(result.Accounts.Length, Is.EqualTo(2 + (_useNonZeroGasPrice ? 1 : 0))); + Assert.That(result.Accounts.Length, Is.EqualTo(3)); } [TestCase] - public async Task Can_call_with_extcodecopy_to_system_account() + public async Task Can_call_with_extcodecopy_to_zero_account() { byte[] code = Prepare.EvmCode .PushData("0x20") .PushData("0x00") .PushData("0x00") - .PushData(Address.SystemUser) + .PushData(Address.Zero) .Op(Instruction.EXTCODECOPY) .Done; CallResultWithProof result = await TestCallWithCode(code); @@ -441,14 +427,14 @@ public async Task Can_call_with_extcodesize() .Op(Instruction.EXTCODESIZE) .Done; CallResultWithProof result = await TestCallWithCode(code); - Assert.That(result.Accounts.Length, Is.EqualTo(2 + (_useNonZeroGasPrice ? 1 : 0))); + Assert.That(result.Accounts.Length, Is.EqualTo(3)); } [TestCase] - public async Task Can_call_with_extcodesize_to_system_account() + public async Task Can_call_with_extcodesize_to_zero_account() { byte[] code = Prepare.EvmCode - .PushData(Address.SystemUser) + .PushData(Address.Zero) .Op(Instruction.EXTCODESIZE) .Done; CallResultWithProof result = await TestCallWithCode(code); @@ -464,15 +450,15 @@ public async Task Can_call_with_extcodehash() .Op(Instruction.EXTCODEHASH) .Done; CallResultWithProof result = await TestCallWithCode(code); - Assert.That(result.Accounts.Length, Is.EqualTo(2 + (_useNonZeroGasPrice ? 1 : 0))); + Assert.That(result.Accounts.Length, Is.EqualTo(3)); } [TestCase] - public async Task Can_call_with_extcodehash_to_system_account() + public async Task Can_call_with_extcodehash_to_zero_account() { _specProvider.NextForkSpec = MuirGlacier.Instance; byte[] code = Prepare.EvmCode - .PushData(Address.SystemUser) + .PushData(Address.Zero) .Op(Instruction.EXTCODEHASH) .Done; CallResultWithProof result = await TestCallWithCode(code); @@ -487,7 +473,7 @@ public async Task Can_call_with_just_basic_addresses() .Op(Instruction.STOP) .Done; CallResultWithProof result = await TestCallWithCode(code); - Assert.That(result.Accounts.Length, Is.EqualTo(1 + (_useNonZeroGasPrice ? 1 : 0))); + Assert.That(result.Accounts.Length, Is.EqualTo(2)); } [TestCase] @@ -500,7 +486,7 @@ public async Task Can_call_with_balance() .Done; CallResultWithProof result = await TestCallWithCode(code); - Assert.That(result.Accounts.Length, Is.EqualTo(2 + (_useNonZeroGasPrice ? 1 : 0))); + Assert.That(result.Accounts.Length, Is.EqualTo(3 + (useNonZeroGasPrice ? 1 : 0))); } [TestCase] @@ -512,15 +498,15 @@ public async Task Can_call_with_self_balance() .Done; CallResultWithProof result = await TestCallWithCode(code); - Assert.That(result.Accounts.Length, Is.EqualTo(1 + (_useNonZeroGasPrice ? 1 : 0))); + Assert.That(result.Accounts.Length, Is.EqualTo(2)); } [TestCase] - public async Task Can_call_with_balance_of_system_account() + public async Task Can_call_with_balance_of_zero_account() { _specProvider.NextForkSpec = MuirGlacier.Instance; byte[] code = Prepare.EvmCode - .PushData(Address.SystemUser) + .PushData(Address.Zero) .Op(Instruction.BALANCE) .Done; CallResultWithProof result = await TestCallWithCode(code); @@ -528,7 +514,7 @@ public async Task Can_call_with_balance_of_system_account() } [TestCase] - public async Task Can_call_with_call_to_system_account_with_zero_value() + public async Task Can_call_with_call_to_zero_account_with_zero_value() { _specProvider.NextForkSpec = MuirGlacier.Instance; byte[] code = Prepare.EvmCode @@ -537,7 +523,7 @@ public async Task Can_call_with_call_to_system_account_with_zero_value() .PushData(0) .PushData(0) .PushData(0) - .PushData(Address.SystemUser) + .PushData(Address.Zero) .PushData(1000000) .Op(Instruction.CALL) .Done; @@ -546,7 +532,7 @@ public async Task Can_call_with_call_to_system_account_with_zero_value() } [TestCase] - public async Task Can_call_with_static_call_to_system_account() + public async Task Can_call_with_static_call_to_zero_account() { _specProvider.NextForkSpec = MuirGlacier.Instance; byte[] code = Prepare.EvmCode @@ -554,7 +540,7 @@ public async Task Can_call_with_static_call_to_system_account() .PushData(0) .PushData(0) .PushData(0) - .PushData(Address.SystemUser) + .PushData(Address.Zero) .PushData(1000000) .Op(Instruction.STATICCALL) .Done; @@ -563,7 +549,7 @@ public async Task Can_call_with_static_call_to_system_account() } [TestCase] - public async Task Can_call_with_delegate_call_to_system_account() + public async Task Can_call_with_delegate_call_to_zero_account() { _specProvider.NextForkSpec = MuirGlacier.Instance; byte[] code = Prepare.EvmCode @@ -571,7 +557,7 @@ public async Task Can_call_with_delegate_call_to_system_account() .PushData(0) .PushData(0) .PushData(0) - .PushData(Address.SystemUser) + .PushData(Address.Zero) .PushData(1000000) .Op(Instruction.DELEGATECALL) .Done; @@ -580,7 +566,7 @@ public async Task Can_call_with_delegate_call_to_system_account() } [TestCase] - public async Task Can_call_with_call_to_system_account_with_non_zero_value() + public async Task Can_call_with_call_to_zero_account_with_non_zero_value() { _specProvider.NextForkSpec = MuirGlacier.Instance; byte[] code = Prepare.EvmCode @@ -589,7 +575,7 @@ public async Task Can_call_with_call_to_system_account_with_non_zero_value() .PushData(0) .PushData(0) .PushData(1) - .PushData(Address.SystemUser) + .PushData(Address.Zero) .PushData(1000000) .Op(Instruction.CALL) .Done; @@ -612,7 +598,7 @@ public async Task Can_call_with_call_with_zero_value() .Op(Instruction.CALL) .Done; CallResultWithProof result = await TestCallWithCode(code); - Assert.That(result.Accounts.Length, Is.EqualTo(2 + (_useNonZeroGasPrice ? 1 : 0))); + Assert.That(result.Accounts.Length, Is.EqualTo(3)); } [TestCase] @@ -629,7 +615,7 @@ public async Task Can_call_with_static_call() .Op(Instruction.STATICCALL) .Done; CallResultWithProof result = await TestCallWithCode(code); - Assert.That(result.Accounts.Length, Is.EqualTo(2 + (_useNonZeroGasPrice ? 1 : 0))); + Assert.That(result.Accounts.Length, Is.EqualTo(3)); } [TestCase] @@ -646,7 +632,7 @@ public async Task Can_call_with_delegate_call() .Op(Instruction.DELEGATECALL) .Done; CallResultWithProof result = await TestCallWithCode(code); - Assert.That(result.Accounts.Length, Is.EqualTo(_createSystemAccount && _useNonZeroGasPrice ? 3 : 2)); + Assert.That(result.Accounts.Length, Is.EqualTo(3)); } [TestCase] @@ -664,7 +650,7 @@ public async Task Can_call_with_call_with_non_zero_value() .Op(Instruction.CALL) .Done; CallResultWithProof result = await TestCallWithCode(code); - Assert.That(result.Accounts.Length, Is.EqualTo(2 + (_useNonZeroGasPrice ? 1 : 0))); + Assert.That(result.Accounts.Length, Is.EqualTo(3 + (useNonZeroGasPrice ? 1 : 0))); } [TestCase] @@ -677,15 +663,15 @@ public async Task Can_call_with_self_destruct() .Done; CallResultWithProof result = await TestCallWithCode(code); - Assert.That(result.Accounts.Length, Is.EqualTo(2 + (_useNonZeroGasPrice ? 1 : 0))); + Assert.That(result.Accounts.Length, Is.EqualTo(3 + (useNonZeroGasPrice ? 1 : 0))); } [TestCase] - public async Task Can_call_with_self_destruct_to_system_account() + public async Task Can_call_with_self_destruct_to_zero_account() { _specProvider.NextForkSpec = MuirGlacier.Instance; byte[] code = Prepare.EvmCode - .PushData(Address.SystemUser) + .PushData(Address.Zero) .Op(Instruction.SELFDESTRUCT) .Done; CallResultWithProof result = await TestCallWithCode(code); @@ -705,7 +691,7 @@ public async Task Can_call_with_many_storage_writes() .Op(Instruction.SSTORE) .Done; CallResultWithProof result = await TestCallWithCode(code); - Assert.That(result.Accounts.Length, Is.EqualTo(1 + (_useNonZeroGasPrice ? 1 : 0))); + Assert.That(result.Accounts.Length, Is.EqualTo(2)); } [TestCase] @@ -755,7 +741,7 @@ public async Task Can_call_with_mix_of_everything_and_storage() .Op(Instruction.SSTORE) .Done; - await TestCallWithStorageAndCode(code, _useNonZeroGasPrice ? 10.GWei() : 0); + await TestCallWithStorageAndCode(code, useNonZeroGasPrice ? 10.GWei() : 0); } [TestCase] @@ -798,7 +784,7 @@ private async Task TestCallWithCode(byte[] code, Address? f { From = from, To = TestItem.AddressB, - GasPrice = _useNonZeroGasPrice ? 10.GWei() : 0 + GasPrice = useNonZeroGasPrice ? 10.GWei() : 0 }; CallResultWithProof callResultWithProof = _proofRpcModule.proof_call(tx, new BlockParameter(blockOnTop.Number)).Data; @@ -846,7 +832,7 @@ private async Task TestCallWithStorageAndCode(byte[] code, UInt256 gasPrice, Add TransactionForRpc tx = new LegacyTransactionForRpc { - // we are testing system transaction here when From is null + // we are testing transaction from zero address here when From is null From = from, To = TestItem.AddressB, GasPrice = gasPrice, @@ -856,19 +842,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 @@ -897,7 +870,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()); @@ -908,9 +881,9 @@ private async Task TestCallWithStorageAndCode(byte[] code, UInt256 gasPrice, Add AddCode(stateProvider, TestItem.AddressB, code); } - if (_createSystemAccount) + if (createZeroAccount) { - AddAccount(stateProvider, Address.SystemUser, 1.Ether()); + AddAccount(stateProvider, Address.Zero, 1.Ether()); } stateProvider.CommitTree(0); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/PruningTrieStateAdminModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/PruningTrieStateAdminModuleTests.cs index 2f31d250d4c5..bdfda4b93049 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/PruningTrieStateAdminModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/PruningTrieStateAdminModuleTests.cs @@ -6,7 +6,6 @@ using Nethermind.Blockchain; using Nethermind.Blockchain.FullPruning; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Core.Test.Builders; using Nethermind.JsonRpc.Modules.Admin; using Nethermind.State; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/AccessListTransactionForRpcTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/AccessListTransactionForRpcTests.cs index fe7adfe2e487..d69dca748a33 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/RpcTransaction/TransactionForRpcTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/TransactionForRpcTests.cs index 23e9aeb10b25..ac50b07d68f0 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/TransactionForRpcTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/TransactionForRpcTests.cs @@ -1,13 +1,16 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Facade.Eth; using System; using System.Text.Json; using FluentAssertions; using FluentAssertions.Json; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using Nethermind.Facade.Eth.RpcTransaction; +using Nethermind.Int256; using Nethermind.Serialization.Json; using Newtonsoft.Json.Linq; using NUnit.Framework; @@ -58,7 +61,7 @@ public void R_and_s_are_quantity_and_not_data() [TestCaseSource(nameof(Transactions))] public void Serialized_JSON_satisfies_schema(Transaction transaction) { - TransactionForRpc rpcTransaction = TransactionForRpc.FromTransaction(transaction, chainId: SomeChainId); + TransactionForRpc rpcTransaction = TransactionForRpc.FromTransaction(transaction, new(SomeChainId)); string serialized = _serializer.Serialize(rpcTransaction); using var jsonDocument = JsonDocument.Parse(serialized); JsonElement json = jsonDocument.RootElement; @@ -88,7 +91,7 @@ public void Serialized_JSON_satisfies_schema(Transaction transaction) [TestCaseSource(nameof(Transactions))] public void Serialized_JSON_satisfies_Nethermind_fields_schema(Transaction transaction) { - TransactionForRpc rpcTransaction = TransactionForRpc.FromTransaction(transaction, chainId: SomeChainId); + TransactionForRpc rpcTransaction = TransactionForRpc.FromTransaction(transaction, new(SomeChainId)); string serialized = _serializer.Serialize(rpcTransaction); using var jsonDocument = JsonDocument.Parse(serialized); JsonElement json = jsonDocument.RootElement; @@ -98,4 +101,63 @@ public void Serialized_JSON_satisfies_Nethermind_fields_schema(Transaction trans json.GetProperty("blockHash").GetString()?.Should().MatchRegex("^0x[0-9a-fA-F]{64}$"); json.GetProperty("blockNumber").GetString()?.Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); } + + [Test] + public void Legacy_transaction_should_populate_chainId_from_signature_when_transaction_chainId_is_null() + { + Transaction tx = new() + { + Type = TxType.Legacy, + Nonce = 0x9a, + To = new Address("0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f"), + Value = 0, + GasLimit = 0x11c32, + GasPrice = 0x5763d65, + Data = null, + ChainId = null, + Signature = new Signature( + new UInt256(Bytes.FromHexString("0x551fe45ccebb0318196e31dbc60da87c43dc60b8fb01afb3286693fa09878730"), true), + new UInt256(Bytes.FromHexString("0x40d33e9afecfe1516b045d61a3272bddbc83f482a7f2c749311248b50fe62e81"), true), + 0x18e5bb3abd109ful + ) + }; + + TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx); + + rpcTx.Should().BeOfType(); + var legacyRpcTx = (LegacyTransactionForRpc)rpcTx; + + ulong? expectedChainId = tx.Signature.ChainId; + expectedChainId.Should().Be(0xc72dd9d5e883eul); + legacyRpcTx.ChainId.Should().Be(expectedChainId); + } + + [Test] + public void Legacy_transaction_should_use_transaction_chainId_when_present() + { + ulong explicitChainId = 1ul; + Transaction tx = new() + { + Type = TxType.Legacy, + Nonce = 1, + To = new Address("0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f"), + Value = 0, + GasLimit = 21000, + GasPrice = 100, + Data = null, + ChainId = explicitChainId, + Signature = new Signature( + new UInt256(Bytes.FromHexString("0x551fe45ccebb0318196e31dbc60da87c43dc60b8fb01afb3286693fa09878730"), true), + new UInt256(Bytes.FromHexString("0x40d33e9afecfe1516b045d61a3272bddbc83f482a7f2c749311248b50fe62e81"), true), + 0x18e5bb3abd109ful + ) + }; + + TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx); + + rpcTx.Should().BeOfType(); + var legacyRpcTx = (LegacyTransactionForRpc)rpcTx; + + legacyRpcTx.ChainId.Should().Be(explicitChainId); + } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs index cde82c8033a5..1033def4ff7e 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs @@ -19,6 +19,7 @@ using Nethermind.JsonRpc.Modules.Eth.FeeHistory; using Nethermind.JsonRpc.Modules.Eth.GasPrice; using Nethermind.Config; +using Nethermind.Db.LogIndex; using Nethermind.Network; using Nethermind.State; @@ -55,7 +56,8 @@ public Task Initialize() Substitute.For(), Substitute.For(), new BlocksConfig(), - Substitute.For()); + Substitute.For(), + Substitute.For()); return Task.CompletedTask; } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs index e05324b2d805..63c8d46b2bbb 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs @@ -10,9 +10,7 @@ using System.Threading.Tasks; using FluentAssertions; using FluentAssertions.Json; -using Google.Protobuf.WellKnownTypes; using Nethermind.Blockchain; -using Nethermind.Blockchain.Blocks; using Nethermind.Blockchain.Filters; using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; @@ -24,7 +22,6 @@ using Nethermind.Facade.Eth; using Nethermind.Int256; using Nethermind.JsonRpc.Modules; -using Nethermind.Stats.Model; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.JsonRpc.Modules.Subscribe; using Nethermind.JsonRpc.WebSockets; @@ -34,7 +31,6 @@ using Nethermind.Sockets; using Nethermind.Specs; using Nethermind.Synchronization; -using Nethermind.Synchronization.FastBlocks; using Nethermind.Synchronization.ParallelSync; using Nethermind.TxPool; using Newtonsoft.Json.Linq; @@ -52,7 +48,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!; @@ -128,7 +124,7 @@ private JsonRpcResult GetBlockAddedToMainResult(BlockReplacementEventArgs blockR })); _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockReplacementEventArgs); - _receiptStorage.ReceiptsInserted += Raise.EventWith(new object(), blockReplacementEventArgs); + _receiptStorage.NewCanonicalReceipts += Raise.EventWith(new object(), blockReplacementEventArgs); manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(1000)).Should().Be(shouldReceiveResult); subscriptionId = newHeadSubscription.Id; @@ -148,7 +144,7 @@ private List GetLogsSubscriptionResult(Filter filter, BlockReplac })); _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockEventArgs); - _receiptStorage.ReceiptsInserted += Raise.EventWith(new object(), blockEventArgs); + _receiptStorage.NewCanonicalReceipts += Raise.EventWith(new object(), blockEventArgs); semaphoreSlim.Wait(TimeSpan.FromMilliseconds(500)); subscriptionId = logsSubscription.Id; @@ -603,8 +599,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 +647,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 +695,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; @@ -769,7 +769,7 @@ public void LogsSubscription_should_not_send_logs_of_new_txs_on_ReceiptsInserted BlockReplacementEventArgs blockEventArgs = new(block); _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockEventArgs); - _receiptStorage.ReceiptsInserted += Raise.EventWith(new object(), blockEventArgs); + _receiptStorage.NewCanonicalReceipts += Raise.EventWith(new object(), blockEventArgs); manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(200)); jsonRpcResults.Count.Should().Be(1); @@ -860,7 +860,7 @@ public void NewPendingTransactionsSubscription_on_NewPending_with_includeTransac jsonRpcResult.Response.Should().NotBeNull(); string serialized = _jsonSerializer.Serialize(jsonRpcResult.Response); - JToken.Parse(serialized).Should().BeEquivalentTo($$$$"""{"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"{{{{subscriptionId}}}}","result":{"nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"to":"0x0000000000000000000000000000000000000000","value":"0x1","gasPrice":"0x1","gas":"0x5208","input":"0x","type":"0x0","hash":null,"v":"0x0","r":"0x0","s":"0x0","from":null}}}"""); + JToken.Parse(serialized).Should().BeEquivalentTo($$$$"""{"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"{{{{subscriptionId}}}}","result":{"nonce":"0x0","blockHash":null,"blockNumber":null,"blockTimestamp":null,"transactionIndex":null,"to":"0x0000000000000000000000000000000000000000","value":"0x1","gasPrice":"0x1","gas":"0x5208","input":"0x","type":"0x0","hash":null,"v":"0x0","r":"0x0","s":"0x0","from":null}}}"""); } [TestCase(2)] @@ -922,12 +922,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,18 +940,20 @@ 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++) { BlockReplacementEventArgs eventArgs = new(Build.A.Block.TestObject); blockTree.BlockAddedToMain += Raise.EventWith(eventArgs); - _receiptStorage.ReceiptsInserted += Raise.EventWith(new object(), eventArgs); + _receiptStorage.NewCanonicalReceipts += Raise.EventWith(new object(), eventArgs); } }); @@ -1204,7 +1208,7 @@ public void LogsSubscription_can_send_logs_with_removed_txs_when_inserted() })); _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockEventArgs); - _receiptStorage.ReceiptsInserted += Raise.EventWith(new object(), blockEventArgs); + _receiptStorage.NewCanonicalReceipts += Raise.EventWith(new object(), blockEventArgs); manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(2000)); jsonRpcResults.Count.Should().Be(1); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs index 1a43ecd6fa4d..77ba56292c40 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs @@ -1,10 +1,9 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System; using System.IO; using System.Threading.Tasks; -using Nethermind.Blockchain.Filters; using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; using Nethermind.Core.Specs; @@ -35,12 +34,13 @@ using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Test.Container; +using Nethermind.Db.LogIndex; using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Modules; using Nethermind.JsonRpc.Modules.Trace; using Nethermind.Network; -using Nethermind.Network.P2P.Subprotocols.Eth; using Nethermind.Network.Rlpx; +using Nethermind.Serialization.Json; using Nethermind.Stats; using Nethermind.Synchronization.ParallelSync; using Nethermind.Synchronization.Peers; @@ -59,6 +59,7 @@ public class TestRpcBlockchain : TestBlockchain public IReceiptFinder ReceiptFinder => Container.Resolve(); public IGasPriceOracle GasPriceOracle { get; private set; } = null!; public IProtocolsManager ProtocolsManager { get; private set; } = null!; + public ILogIndexConfig LogIndexConfig { get; } = new LogIndexConfig(); public IKeyStore KeyStore { get; } = new MemKeyStore(TestItem.PrivateKeys, Path.Combine("testKeyStoreDir", Path.GetRandomFileName())); public IWallet TestWallet { get; } = @@ -68,8 +69,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); @@ -194,10 +195,12 @@ public async Task Build(Action configurer) new FeeHistoryOracle(@this.BlockTree, @this.ReceiptStorage, @this.SpecProvider), @this.ProtocolsManager, @this.ForkInfo, + @this.LogIndexConfig, @this.BlocksConfig.SecondsPerSlot); protected override async Task Build(Action? configurer = null) { + EthereumJsonSerializer.StrictHexFormat = RpcConfig.StrictHexFormat; await base.Build(builder => { builder.AddSingleton(new TestSpecProvider(Berlin.Instance)); @@ -217,7 +220,6 @@ await base.Build(builder => Substitute.For(), Substitute.For(), TxPool, - Substitute.For(), Substitute.For(), Substitute.For(), Substitute.For(), @@ -228,6 +230,8 @@ await base.Build(builder => Substitute.For(), WorldStateManager, LimboLogs.Instance, + Substitute.For(), + Substitute.For(), Substitute.For() ); @@ -254,7 +258,7 @@ public async Task RestartBlockchainProcessor() // simulating restarts - we stopped the old blockchain processor and create the new one _currentBlockchainProcessor = new BlockchainProcessor(BlockTree, BranchProcessor, - BlockPreprocessorStep, StateReader, LimboLogs.Instance, Nethermind.Consensus.Processing.BlockchainProcessor.Options.Default); + BlockPreprocessorStep, StateReader, LimboLogs.Instance, Nethermind.Consensus.Processing.BlockchainProcessor.Options.Default, Substitute.For()); _currentBlockchainProcessor.Start(); } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParitySimulateTestsBlocksAndTransactions.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParitySimulateTestsBlocksAndTransactions.cs index b1149798b009..cbd96f3430ff 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); @@ -51,7 +51,7 @@ public async Task Test_trace_simulate_eth_moved() TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain(); UInt256 nonceA = chain.ReadOnlyState.GetNonce(TestItem.AddressA); - Transaction txMainnetAtoB = EthSimulateTestsBlocksAndTransactions.GetTransferTxData(nonceA, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1); + Transaction txMainnetAtoB = EthSimulateTestsBlocksAndTransactions.GetTransferTxData(nonceA, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1, type: TxType.Legacy); SimulatePayload payload = EthSimulateTestsBlocksAndTransactions.CreateEthMovedPayload(chain, nonceA); @@ -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!); @@ -89,7 +89,7 @@ public async Task Test_trace_simulate_transactions_forced_fail() UInt256 nonceA = chain.ReadOnlyState.GetNonce(TestItem.AddressA); Transaction txMainnetAtoB = - EthSimulateTestsBlocksAndTransactions.GetTransferTxData(nonceA, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1); + EthSimulateTestsBlocksAndTransactions.GetTransferTxData(nonceA, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 1, type: TxType.Legacy); SimulatePayload payload = EthSimulateTestsBlocksAndTransactions.CreateTransactionsForcedFail(chain, nonceA); @@ -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!); @@ -120,7 +120,7 @@ public async Task TestTransferLogsAddress() TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain(); Console.WriteLine("current test: simulateTransferOverBlockStateCalls"); var result = chain.TraceRpcModule.trace_simulateV1(payload!, BlockParameter.Latest); - Assert.That(result.Data.First().Traces.First().BlockHash, Is.EqualTo(new Core.Crypto.Hash256("0xbac8f3ad5d5859d65310c2e4d0f4b845175b21caf3f36722ae542ac00768ba79"))); + Assert.That(result.Data.First().Traces.First().BlockHash, Is.EqualTo(new Core.Crypto.Hash256("0x45635998c509d5571fcc391772c5af77f3f202b70ea9fafb48ea8eb475288b59"))); } [Test] @@ -132,7 +132,7 @@ public async Task TestSerializationEthSimulate() Assert.That(response, Is.TypeOf()); JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse)response; IReadOnlyList> data = (IReadOnlyList>)successResponse.Result!; - Assert.That(data.First().Traces.First().BlockHash, Is.EqualTo(new Core.Crypto.Hash256("0xbac8f3ad5d5859d65310c2e4d0f4b845175b21caf3f36722ae542ac00768ba79"))); + Assert.That(data.First().Traces.First().BlockHash, Is.EqualTo(new Core.Crypto.Hash256("0x45635998c509d5571fcc391772c5af77f3f202b70ea9fafb48ea8eb475288b59"))); } [Test] diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs index 22e456055eb9..9ee6c353c934 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs @@ -9,6 +9,7 @@ using Nethermind.Blockchain; using Nethermind.Blockchain.Find; using Nethermind.Consensus; +using Nethermind.Consensus.Processing; using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; @@ -30,6 +31,7 @@ public class ParityStyleTracerTests private IPoSSwitcher? _poSSwitcher; private ITraceRpcModule _traceRpcModule; private IContainer _container; + private IBlockProcessingQueue _blockProcessingQueue; [SetUp] public async Task Setup() @@ -55,6 +57,8 @@ public async Task Setup() await _container.Resolve().StartBlockProcessing(default); _traceRpcModule = _container.Resolve>().Create(); + _blockProcessingQueue = _container.Resolve(); + } [TearDown] @@ -66,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); } @@ -79,10 +83,10 @@ public void Can_trace_raw_parity_style_berlin_tx() [TestCase(true)] [TestCase(false)] - public void Should_return_correct_block_reward(bool isPostMerge) + public async Task Should_return_correct_block_reward(bool isPostMerge) { Block block = Build.A.Block.WithParent(_blockTree!.Head!).TestObject; - _blockTree!.SuggestBlock(block).Should().Be(AddBlockResult.Added); + (await _blockTree!.SuggestBlockAsync(block, BlockTreeSuggestOptions.None)).Should().Be(AddBlockResult.Added); _poSSwitcher!.IsPostMerge(Arg.Any()).Returns(isPostMerge); ResultWrapper> rpcResult = _traceRpcModule.trace_block(new BlockParameter(block.Number)); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs index c34e75ef9cfe..5fbfd51e17a7 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using Autofac; using FluentAssertions; using FluentAssertions.Execution; using FluentAssertions.Json; @@ -28,9 +27,7 @@ using Nethermind.Specs.Test; using Nethermind.JsonRpc.Data; using Nethermind.Serialization.Rlp; -using Nethermind.Evm.State; using Newtonsoft.Json.Linq; -using Nethermind.JsonRpc.Modules; using Nethermind.State; namespace Nethermind.JsonRpc.Test.Modules; @@ -94,7 +91,7 @@ public async Task Trace_filter_return_fail_with_not_existing_block() context.TraceRpcModule, "trace_filter", request); Assert.That( - serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32001,\"message\":\"Block 340 could not be found\"},\"id\":67}"), serialized.Replace("\"", "\\\"")); + serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"Block 340 could not be found\"},\"id\":67}"), serialized.Replace("\"", "\\\"")); } [Test] @@ -675,6 +672,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 +703,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 +734,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; @@ -749,10 +752,10 @@ public async Task Trace_call_simple_tx_test() { Context context = new(); await context.Build(); - object transaction = new { from = "0xaaaaaaaa8583de65cc752fe3fad5098643244d22", to = "0xd6a8d04cb9846759416457e2c593c99390092df6" }; + object transaction = new { from = "0xaaaaaaaa8583de65cc752fe3fad5098643244d22", to = "0xd6a8d04cb9846759416457e2c593c99390092df6", gas = 1000000 }; string[] traceTypes = { "trace" }; string blockParameter = "latest"; - string expectedResult = "{\"jsonrpc\":\"2.0\",\"result\":{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0xaaaaaaaa8583de65cc752fe3fad5098643244d22\",\"gas\":\"0x5f58ef8\",\"input\":\"0x\",\"to\":\"0xd6a8d04cb9846759416457e2c593c99390092df6\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null},\"id\":67}"; + string expectedResult = "{\"jsonrpc\":\"2.0\",\"result\":{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0xaaaaaaaa8583de65cc752fe3fad5098643244d22\",\"gas\":\"0xef038\",\"input\":\"0x\",\"to\":\"0xd6a8d04cb9846759416457e2c593c99390092df6\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null},\"id\":67}"; string serialized = await RpcTest.TestSerializedRequest( context.TraceRpcModule, @@ -762,8 +765,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" }, ["trace"], "{\"jsonrpc\":\"2.0\",\"result\":{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0x7f554713be84160fdf0178cc8df86f5aabd33397\",\"gas\":\"0x5f58ef8\",\"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" }, ["trace"], "{\"jsonrpc\":\"2.0\",\"result\":{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0xc71acc7863f3bc7347b24c3b835643bd89d4d161\",\"gas\":\"0x5f58ef8\",\"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 +820,7 @@ public async Task Trace_callMany_is_blockParameter_optional_test() { Context context = new(); await context.Build(); - string calls = "[[{\"from\":\"0xfe35e70599578efef562e1f1cdc9ef693b865e9d\",\"to\":\"0x8cf85548ae57a91f8132d0831634c0fcef06e505\"},[\"trace\"]],[{\"from\":\"0x2a6ae6f33729384a00b4ffbd25e3f1bf1b9f5b8d\",\"to\":\"0xab736519b5433974059da38da74b8db5376942cd\",\"gasPrice\":\"0xb2b29a6dc\"},[\"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 +830,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\":\"0x5f58ef8\",\"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\":\"0x5f58ef8\",\"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\":\"0x5f58ef8\",\"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\":\"0x5f58ef8\",\"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] @@ -837,13 +840,16 @@ public async Task Trace_callMany_accumulates_state_changes() Context context = new(); await context.Build(); - string calls = $"[[{{\"from\":\"{TestItem.AddressA}\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"1\"}},[\"statediff\"]],[{{\"from\":\"{TestItem.AddressA}\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"1\"}},[\"statediff\"]]]"; + string calls = $"[[{{\"from\":\"{TestItem.AddressA}\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"1\",\"gas\":\"0xf4240\"}},[\"statediff\"]],[{{\"from\":\"{TestItem.AddressA}\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"1\",\"gas\":\"0xf4240\"}},[\"statediff\"]]]"; string serialized = await RpcTest.TestSerializedRequest( context.TraceRpcModule, "trace_callMany", calls); - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":[{\"output\":null,\"stateDiff\":{\"0x0000000000000000000000000000000000000000\":{\"balance\":{\"*\":{\"from\":\"0x2d\",\"to\":\"0x2e\"}},\"code\":\"=\",\"nonce\":\"=\",\"storage\":{}},\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\":{\"balance\":{\"*\":{\"from\":\"0x3635c9adc5de9f09e5\",\"to\":\"0x3635c9adc5de9f09e4\"}},\"code\":\"=\",\"nonce\":{\"*\":{\"from\":\"0x3\",\"to\":\"0x4\"}},\"storage\":{}}},\"trace\":[],\"vmTrace\":null},{\"output\":null,\"stateDiff\":{\"0x0000000000000000000000000000000000000000\":{\"balance\":{\"*\":{\"from\":\"0x2e\",\"to\":\"0x2f\"}},\"code\":\"=\",\"nonce\":\"=\",\"storage\":{}},\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\":{\"balance\":{\"*\":{\"from\":\"0x3635c9adc5de9f09e4\",\"to\":\"0x3635c9adc5de9f09e3\"}},\"code\":\"=\",\"nonce\":{\"*\":{\"from\":\"0x4\",\"to\":\"0x5\"}},\"storage\":{}}},\"trace\":[],\"vmTrace\":null}],\"id\":67}"), serialized.Replace("\"", "\\\"")); + string expected = + "{\"jsonrpc\":\"2.0\",\"result\":[{\"output\":null,\"stateDiff\":{\"0x0000000000000000000000000000000000000000\":{\"balance\":{\"*\":{\"from\":\"0x2d\",\"to\":\"0x2e\"}},\"code\":\"=\",\"nonce\":\"=\",\"storage\":{}},\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\":{\"balance\":{\"*\":{\"from\":\"0x3635c9adc5de9f09e5\",\"to\":\"0x3635c9adc5de9f09e4\"}},\"code\":\"=\",\"nonce\":{\"*\":{\"from\":\"0x3\",\"to\":\"0x4\"}},\"storage\":{}}},\"trace\":[],\"vmTrace\":null},{\"output\":null,\"stateDiff\":{\"0x0000000000000000000000000000000000000000\":{\"balance\":{\"*\":{\"from\":\"0x2e\",\"to\":\"0x2f\"}},\"code\":\"=\",\"nonce\":\"=\",\"storage\":{}},\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\":{\"balance\":{\"*\":{\"from\":\"0x3635c9adc5de9f09e4\",\"to\":\"0x3635c9adc5de9f09e3\"}},\"code\":\"=\",\"nonce\":{\"*\":{\"from\":\"0x4\",\"to\":\"0x5\"}},\"storage\":{}}},\"trace\":[],\"vmTrace\":null}],\"id\":67}"; + + Assert.That(serialized, Is.EqualTo(expected), serialized.Replace("\"", "\\\"")); } [Test] @@ -924,38 +930,38 @@ public async Task Trace_replayBlockTransactions_stateDiff() [TestCase( "Nonce increments from state override", - """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000"}""", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","gas":"0xf4240"}""", "stateDiff", """{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"nonce":"0x123"}}""", """{"jsonrpc":"2.0","result":{"output":null,"stateDiff":{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":"=","code":"=","nonce":{"*":{"from":"0x123","to":"0x124"}},"storage":{}}},"trace":[],"vmTrace":null},"id":67}""" )] [TestCase( "Uses account balance from state override", - """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xbe5c953dd0ddb0ce033a98f36c981f1b74d3b33f","value":"0x100"}""", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xbe5c953dd0ddb0ce033a98f36c981f1b74d3b33f","value":"0x100","gas":"0xf4240"}""", "stateDiff", """{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":"0x100"}}""", """{"jsonrpc":"2.0","result":{"output":null,"stateDiff":{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":{"*":{"from":"0x100","to":"0x0"}},"code":"=","nonce":{"*":{"from":"0x0","to":"0x1"}},"storage":{}},"0xbe5c953dd0ddb0ce033a98f36c981f1b74d3b33f":{"balance":{"\u002B":"0x100"},"code":"=","nonce":{"\u002B":"0x0"},"storage":{}}},"trace":[],"vmTrace":null},"id":67}""" )] [TestCase( "Executes code from state override", - """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0x60fe47b1112233445566778899001122334455667788990011223344556677889900112233445566778899001122"}""", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0x60fe47b1112233445566778899001122334455667788990011223344556677889900112233445566778899001122","gas":"0xf4240"}""", "stateDiff", """{"0xc200000000000000000000000000000000000000":{"code":"0x6080604052348015600e575f80fd5b50600436106030575f3560e01c80632a1afcd914603457806360fe47b114604d575b5f80fd5b603b5f5481565b60405190815260200160405180910390f35b605c6058366004605e565b5f55565b005b5f60208284031215606d575f80fd5b503591905056fea2646970667358221220fd4e5f3894be8e57fc7460afebb5c90d96c3486d79bf47b00c2ed666ab2f82b364736f6c634300081a0033"}}""", """{"jsonrpc":"2.0","result":{"output":null,"stateDiff":{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":{"\u002B":"0x0"},"code":"=","nonce":{"\u002B":"0x1"},"storage":{}},"0xc200000000000000000000000000000000000000":{"balance":"=","code":"=","nonce":"=","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":{"*":{"from":"0x0000000000000000000000000000000000000000000000000000000000000000","to":"0x1122334455667788990011223344556677889900112233445566778899001122"}}}}},"trace":[],"vmTrace":null},"id":67}""" )] [TestCase( "Uses storage from state override", - """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0x60fe47b1112233445566778899001122334455667788990011223344556677889900112233445566778899001122"}""", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0x60fe47b1112233445566778899001122334455667788990011223344556677889900112233445566778899001122","gas":"0xf4240"}""", "stateDiff", """{"0xc200000000000000000000000000000000000000":{"state": {"0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000123456"}, "code":"0x6080604052348015600e575f80fd5b50600436106030575f3560e01c80632a1afcd914603457806360fe47b114604d575b5f80fd5b603b5f5481565b60405190815260200160405180910390f35b605c6058366004605e565b5f55565b005b5f60208284031215606d575f80fd5b503591905056fea2646970667358221220fd4e5f3894be8e57fc7460afebb5c90d96c3486d79bf47b00c2ed666ab2f82b364736f6c634300081a0033"}}""", """{"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", - """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0x0000000000000000000000000000000000123456","input":"0xB6E16D27AC5AB427A7F68900AC5559CE272DC6C37C82B3E052246C82244C50E4000000000000000000000000000000000000000000000000000000000000001C7B8B1991EB44757BC688016D27940DF8FB971D7C87F77A6BC4E938E3202C44037E9267B0AEAA82FA765361918F2D8ABD9CDD86E64AA6F2B81D3C4E0B69A7B055"}""", + "Executes precompile using overridden address", + """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0x0000000000000000000000000000000000123456","input":"0xB6E16D27AC5AB427A7F68900AC5559CE272DC6C37C82B3E052246C82244C50E4000000000000000000000000000000000000000000000000000000000000001C7B8B1991EB44757BC688016D27940DF8FB971D7C87F77A6BC4E938E3202C44037E9267B0AEAA82FA765361918F2D8ABD9CDD86E64AA6F2B81D3C4E0B69A7B055","gas":"0xf4240"}""", "trace", """{"0x0000000000000000000000000000000000000001":{"movePrecompileToAddress":"0x0000000000000000000000000000000000123456", "code": "0x"}}""", - """{"jsonrpc":"2.0","result":{"output":"0x000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099","stateDiff":null,"trace":[{"action":{"callType":"call","from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","gas":"0x5f58878","input":"0xb6e16d27ac5ab427a7f68900ac5559ce272dc6c37c82b3e052246c82244c50e4000000000000000000000000000000000000000000000000000000000000001c7b8b1991eb44757bc688016d27940df8fb971d7c87f77a6bc4e938e3202c44037e9267b0aeaa82fa765361918f2d8abd9cdd86e64aa6f2b81d3c4e0b69a7b055","to":"0x0000000000000000000000000000000000123456","value":"0x0"},"result":{"gasUsed":"0xbb8","output":"0x000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099"},"subtraces":0,"traceAddress":[],"type":"call"}],"vmTrace":null},"id":67}""" + """{"jsonrpc":"2.0","result":{"output":"0x000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099","stateDiff":null,"trace":[{"action":{"callType":"call","from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","gas":"0xee9b8","input":"0xb6e16d27ac5ab427a7f68900ac5559ce272dc6c37c82b3e052246c82244c50e4000000000000000000000000000000000000000000000000000000000000001c7b8b1991eb44757bc688016d27940df8fb971d7c87f77a6bc4e938e3202c44037e9267b0aeaa82fa765361918f2d8abd9cdd86e64aa6f2b81d3c4e0b69a7b055","to":"0x0000000000000000000000000000000000123456","value":"0x0"},"result":{"gasUsed":"0xbb8","output":"0x000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099"},"subtraces":0,"traceAddress":[],"type":"call"}],"vmTrace":null},"id":67}""" )] public async Task Trace_call_with_state_override(string name, string transactionJson, string traceType, string stateOverrideJson, string expectedResult) { @@ -972,12 +978,12 @@ public async Task Trace_call_with_state_override(string name, string transaction } [TestCase( - """{"from":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","to":"0xc200000000000000000000000000000000000000"}""", + """{"from":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","to":"0xc200000000000000000000000000000000000000","gas":"0xf4240"}""", "stateDiff", """{"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099":{"balance":"0x123", "nonce": "0x123"}}""" )] [TestCase( - """{"from":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","to":"0xc200000000000000000000000000000000000000","input":"0xf8b2cb4f000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099"}""", + """{"from":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","to":"0xc200000000000000000000000000000000000000","input":"0xf8b2cb4f000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099","gas":"0xf4240"}""", "trace", """{"0xc200000000000000000000000000000000000000":{"code":"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f8b2cb4f14610030575b600080fd5b61004a600480360381019061004591906100e4565b610060565b604051610057919061012a565b60405180910390f35b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100b182610086565b9050919050565b6100c1816100a6565b81146100cc57600080fd5b50565b6000813590506100de816100b8565b92915050565b6000602082840312156100fa576100f9610081565b5b6000610108848285016100cf565b91505092915050565b6000819050919050565b61012481610111565b82525050565b600060208201905061013f600083018461011b565b9291505056fea2646970667358221220172c443a163d8a43e018c339d1b749c312c94b6de22835953d960985daf228c764736f6c63430008120033"}}""" )] diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/RpcTest.cs b/src/Nethermind/Nethermind.JsonRpc.Test/RpcTest.cs index 148322f93210..b793d13a9fdd 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/RpcTest.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/RpcTest.cs @@ -15,6 +15,7 @@ using System.Reflection; using System.Text.Json; using System.Threading.Tasks; +using NUnit.Framework; namespace Nethermind.JsonRpc.Test; @@ -47,13 +48,15 @@ public static async Task TestSerializedRequest(T module, string metho Stream stream = new MemoryStream(); long size = await serializer.SerializeAsync(stream, response, cts.Token).ConfigureAwait(false); - // for coverage (and to prove that it does not throw + // for coverage (and to prove that it does not throw) Stream indentedStream = new MemoryStream(); await serializer.SerializeAsync(indentedStream, response, cts.Token, true).ConfigureAwait(false); stream.Seek(0, SeekOrigin.Begin); string serialized = await new StreamReader(stream).ReadToEndAsync().ConfigureAwait(false); + await TestContext.Out.WriteLineAsync(serialized); + size.Should().Be(serialized.Length); return serialized; diff --git a/src/Nethermind/Nethermind.JsonRpc.TraceStore/TraceStoreRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc.TraceStore/TraceStoreRpcModule.cs index a38952dc51d0..a26bf787bc3d 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/Client/BasicJsonRpcClient.cs b/src/Nethermind/Nethermind.JsonRpc/Client/BasicJsonRpcClient.cs index 666e8701c42c..6ae15afe6432 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Client/BasicJsonRpcClient.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Client/BasicJsonRpcClient.cs @@ -34,7 +34,7 @@ public BasicJsonRpcClient(Uri uri, IJsonSerializer jsonSerializer, ILogManager l AddAuthorizationHeader(); } - public async Task Post(string method, params object[] parameters) + public async Task Post(string method, params object?[] parameters) { string request = GetJsonRequest(method, parameters); HttpResponseMessage response = await _client.PostAsync("", new StringContent(request, Encoding.UTF8, "application/json")); @@ -42,7 +42,7 @@ public async Task Post(string method, params object[] parameters) return content; } - public async Task Post(string method, params object[] parameters) + public async Task Post(string method, params object?[] parameters) { string responseString = string.Empty; try @@ -72,7 +72,7 @@ e is not NotSupportedException } } - private string GetJsonRequest(string method, IEnumerable parameters) + private string GetJsonRequest(string method, IEnumerable parameters) { var request = new { diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/AccessListResultForRpc.cs b/src/Nethermind/Nethermind.JsonRpc/Data/AccessListResultForRpc.cs index a86aa7317b57..bcb12477e900 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Data/AccessListResultForRpc.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Data/AccessListResultForRpc.cs @@ -12,9 +12,12 @@ public readonly struct AccessListResultForRpc public UInt256 GasUsed { get; init; } - public AccessListResultForRpc(AccessListForRpc accessList, in UInt256 gasUsed) + public string? Error { get; init; } + + public AccessListResultForRpc(AccessListForRpc accessList, in UInt256 gasUsed, string? error) { AccessList = accessList; GasUsed = gasUsed; + Error = error; } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/EthRpcJsonContext.cs b/src/Nethermind/Nethermind.JsonRpc/Data/EthRpcJsonContext.cs new file mode 100644 index 000000000000..a7537c67d09c --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/Data/EthRpcJsonContext.cs @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json.Serialization; +using Nethermind.Blockchain.Tracing.GethStyle; +using Nethermind.Blockchain.Tracing.ParityStyle; +using Nethermind.Evm; +using Nethermind.JsonRpc.Modules.DebugModule; +using Nethermind.JsonRpc.Modules.Eth; +using Nethermind.JsonRpc.Modules.Trace; +using Nethermind.State.Proofs; + +namespace Nethermind.JsonRpc.Data; + +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Metadata, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +// eth_ types +[JsonSerializable(typeof(ReceiptForRpc))] +[JsonSerializable(typeof(LogEntryForRpc))] +[JsonSerializable(typeof(FeeHistoryResults))] +[JsonSerializable(typeof(AccessListResultForRpc))] +[JsonSerializable(typeof(AccountInfoForRpc))] +[JsonSerializable(typeof(AccountOverride))] +[JsonSerializable(typeof(AccountProof))] +[JsonSerializable(typeof(BadBlock))] +// debug_ types +[JsonSerializable(typeof(GethLikeTxTrace))] +[JsonSerializable(typeof(GethTraceOptions))] +[JsonSerializable(typeof(ChainLevelForRpc))] +// trace_ types +[JsonSerializable(typeof(ParityTxTraceFromReplay))] +[JsonSerializable(typeof(ParityTxTraceFromStore))] +[JsonSerializable(typeof(ParityLikeTxTrace))] +[JsonSerializable(typeof(TraceFilterForRpc))] +public partial class EthRpcJsonContext : JsonSerializerContext; diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/ReceiptForRpc.cs b/src/Nethermind/Nethermind.JsonRpc/Data/ReceiptForRpc.cs index 2e097f92799e..7eb5c8c82403 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Data/ReceiptForRpc.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Data/ReceiptForRpc.cs @@ -33,7 +33,7 @@ public ReceiptForRpc(Hash256 txHash, TxReceipt receipt, ulong blockTimestamp, Tx Logs = (receipt.Logs ?? []).Select((l, idx) => new LogEntryForRpc(receipt, l, blockTimestamp, idx + logIndexStart)).ToArray(); LogsBloom = receipt.Bloom; Root = receipt.PostTransactionState; - Status = receipt.StatusCode; + Status = receipt.PostTransactionState is null ? receipt.StatusCode : null; Error = string.IsNullOrEmpty(receipt.Error) ? null : receipt.Error; Type = receipt.TxType; } @@ -62,7 +62,7 @@ public ReceiptForRpc(Hash256 txHash, TxReceipt receipt, ulong blockTimestamp, Tx public LogEntryForRpc[] Logs { get; set; } public Bloom? LogsBloom { get; set; } public Hash256? Root { get; set; } - public long Status { get; set; } + public long? Status { get; set; } public string? Error { get; set; } public TxType Type { get; set; } @@ -80,7 +80,7 @@ public TxReceipt ToReceipt() BlockNumber = BlockNumber, ContractAddress = ContractAddress, GasUsed = GasUsed, - StatusCode = (byte)Status, + StatusCode = Status is not null ? (byte)Status : byte.MinValue, TxHash = TransactionHash, GasUsedTotal = CumulativeGasUsed, PostTransactionState = Root, diff --git a/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs b/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs index 64880d8964aa..82123f9ca084 100644 --- a/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs +++ b/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs @@ -7,6 +7,11 @@ public static class ErrorCodes { public const int None = 0; + /// + /// Execution reverted (Geth compatibility) + /// + public const int ExecutionReverted = 3; + /// /// Invalid JSON /// @@ -40,38 +45,28 @@ public static class ErrorCodes /// /// Requested resource not found /// - public const int ResourceNotFound = -32001; + public const int ResourceNotFound = -32000; /// - /// Requested resource not available + /// Transaction creation failed /// - public const int ResourceUnavailable = -32002; + public const int TransactionRejected = -32000; /// - /// Transaction creation failed + /// Requested resource not available /// - public const int TransactionRejected = -32010; + public const int ResourceUnavailable = -32002; /// /// Account locked /// public const int AccountLocked = -32020; - /// - /// Method is not implemented - /// - public const int MethodNotSupported = -32004; - /// /// Request exceeds defined limit /// public const int LimitExceeded = -32005; - /// - /// - /// - public const int ExecutionError = -32015; - /// /// Request exceeds defined timeout limit /// @@ -103,23 +98,43 @@ public static class ErrorCodes public const int InvalidInputTooManyBlocks = -38026; /// - /// Invalid RPC simulate call Not enough gas provided to pay for intrinsic gas for a transaction + /// Too many blocks for simulation /// - public const int InsufficientIntrinsicGas = -38013; + public const int ClientLimitExceededError = -38026; /// - /// Invalid RPC simulate call transaction + /// Block is not available due to history expiry policy /// - public const int InvalidTransaction = -38014; + public const int PrunedHistoryUnavailable = 4444; /// - /// Too many blocks for simulation + /// Default error code /// - public const int ClientLimitExceededError = -38026; + public const int Default = -32000; /// - /// Block is not available due to history expirty policy + /// Invalid intrinsic gas. Miner premium is negative /// - public const int PrunedHistoryUnavailable = 4444; + public const int IntrinsicGas = -38013; + + /// + /// Not enough value to cover transaction costs + /// + public const int InsufficientFunds = -38014; + + /// + /// Gas limit reached + /// + public const int BlockGasLimitReached = -38015; + + /// + /// EIP-3860. Code size is to big + /// + public const int MaxInitCodeSizeExceeded = -38025; + + /// + /// Error during EVM execution + /// + public const int VMError = -32015; } } diff --git a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcConfig.cs b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcConfig.cs index 85a41f240fa4..18e994d02c7e 100644 --- a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcConfig.cs +++ b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcConfig.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Config; -using Nethermind.Serialization.Json; namespace Nethermind.JsonRpc; @@ -187,4 +186,9 @@ public interface IJsonRpcConfig : IConfig [ConfigItem(Description = "Preload rpc modules. Useful in rpc provider to reduce latency on first request.", DefaultValue = "false")] bool PreloadRpcModules { get; set; } + + [ConfigItem( + Description = "Enable strict parsing rules for Block Params and Hashas in RPC requests. this will decrease compatibility but increase compliance with the spec.", + DefaultValue = "true")] + bool StrictHexFormat { get; set; } } diff --git a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcResult.cs b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcResult.cs deleted file mode 100644 index 845540f0d11e..000000000000 --- a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcResult.cs +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.JsonRpc -{ - public interface IJsonRpcResult - { - object ToJson(); - } -} diff --git a/src/Nethermind/Nethermind.JsonRpc/IStreamableResult.cs b/src/Nethermind/Nethermind.JsonRpc/IStreamableResult.cs new file mode 100644 index 000000000000..6e3c1cf02c03 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/IStreamableResult.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; + +namespace Nethermind.JsonRpc; + +/// +/// Implemented by result objects that can write themselves directly to a , +/// bypassing to avoid extra buffer copies. +/// +public interface IStreamableResult +{ + ValueTask WriteToAsync(PipeWriter writer, CancellationToken cancellationToken); +} diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfig.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfig.cs index f5ad44064837..3df17371429a 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfig.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfig.cs @@ -74,4 +74,5 @@ public string[] EnabledModules public bool EnablePerMethodMetrics { get; set; } = false; public int FiltersTimeout { get; set; } = 900000; public bool PreloadRpcModules { get; set; } + public bool StrictHexFormat { get; set; } = true; }; diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfigExtension.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfigExtension.cs index ce2181efb4f5..acb929e83633 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfigExtension.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfigExtension.cs @@ -1,15 +1,22 @@ // SPDX-FileCopyrightText: 2022 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.Linq; +using System.Runtime.CompilerServices; using System.Threading; namespace Nethermind.JsonRpc { public static class JsonRpcConfigExtension { + private static readonly ConcurrentQueue _ctsPool = new(); + private const int MaxPoolSize = 64; + private static int _ctsPoolSize; + public static void EnableModules(this IJsonRpcConfig config, params string[] modules) { HashSet enabledModules = config.EnabledModules.ToHashSet(); @@ -21,12 +28,45 @@ public static void EnableModules(this IJsonRpcConfig config, params string[] mod } /// - /// Constructs a that timeouts after - /// if is false. + /// Rents a that timeouts after . + /// When debugger is attached, no timeout is applied. + /// Call to return to pool. /// public static CancellationTokenSource BuildTimeoutCancellationToken(this IJsonRpcConfig config) { - return Debugger.IsAttached ? new CancellationTokenSource() : new CancellationTokenSource(config.Timeout); + if (Debugger.IsAttached) + { + return new CancellationTokenSource(); + } + + if (_ctsPool.TryDequeue(out CancellationTokenSource? cts)) + { + Interlocked.Decrement(ref _ctsPoolSize); + cts.CancelAfter(config.Timeout); + return cts; + } + + return new CancellationTokenSource(config.Timeout); + } + + /// + /// Returns a CTS to the pool if it can be reset, otherwise disposes it. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ReturnTimeoutCancellationToken(CancellationTokenSource cts) + { + if (cts.TryReset()) + { + if (Interlocked.Increment(ref _ctsPoolSize) <= MaxPoolSize) + { + _ctsPool.Enqueue(cts); + return; + } + + Interlocked.Decrement(ref _ctsPoolSize); + } + + cts.Dispose(); } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcContext.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcContext.cs index c145607924d5..b8e3f2f803f7 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcContext.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcContext.cs @@ -2,14 +2,16 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Threading; using Nethermind.JsonRpc.Modules; namespace Nethermind.JsonRpc { public class JsonRpcContext : IDisposable { - public static AsyncLocal Current { get; private set; } = new(); + [ThreadStatic] + private static JsonRpcContext? _current; + + public static ThreadStaticAccessor Current { get; } = new(); public static JsonRpcContext Http(JsonRpcUrl url) => new(RpcEndpoint.Http, url: url); public static JsonRpcContext WebSocket(JsonRpcUrl url) => new(RpcEndpoint.Ws, url: url); @@ -20,7 +22,7 @@ public JsonRpcContext(RpcEndpoint rpcEndpoint, IJsonRpcDuplexClient? duplexClien DuplexClient = duplexClient; Url = url; IsAuthenticated = Url?.IsAuthenticated == true || RpcEndpoint == RpcEndpoint.IPC; - Current.Value = this; + _current = this; } public RpcEndpoint RpcEndpoint { get; } @@ -29,9 +31,22 @@ public JsonRpcContext(RpcEndpoint rpcEndpoint, IJsonRpcDuplexClient? duplexClien public bool IsAuthenticated { get; } public void Dispose() { - if (Current.Value == this) + if (_current == this) + { + _current = null; + } + } + + /// + /// Provides .Value accessor compatible with AsyncLocal API shape so callers + /// like JsonRpcContext.Current.Value?.IsAuthenticated continue to compile. + /// + public sealed class ThreadStaticAccessor + { + public JsonRpcContext? Value { - Current.Value = null; + get => _current; + set => _current = value; } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs index db3c5edb7391..8093b2c40c97 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs @@ -9,7 +9,6 @@ using System.IO; using System.IO.Abstractions; using System.IO.Pipelines; -using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; @@ -21,12 +20,13 @@ using Nethermind.Core.Collections; using Nethermind.Core.Extensions; using Nethermind.Core.Resettables; +using Nethermind.JsonRpc.Modules; using Nethermind.Logging; using Nethermind.Serialization.Json; namespace Nethermind.JsonRpc; -public class JsonRpcProcessor : IJsonRpcProcessor +public sealed class JsonRpcProcessor : IJsonRpcProcessor { private readonly IJsonRpcConfig _jsonRpcConfig; private readonly ILogger _logger; @@ -52,21 +52,21 @@ public JsonRpcProcessor(IJsonRpcService jsonRpcService, IJsonRpcConfig jsonRpcCo } } - public CancellationToken ProcessExit - => _processExitSource?.Token ?? default; + public CancellationToken ProcessExit => _processExitSource?.Token ?? default; - private (JsonRpcRequest? Model, ArrayPoolList? Collection) DeserializeObjectOrArray(JsonDocument doc) + private void DeserializeObjectOrArray(JsonDocument doc, out JsonRpcRequest? model, out ArrayPoolList? collection) { - return doc.RootElement.ValueKind switch + collection = null; + model = null; + switch (doc.RootElement.ValueKind) { - JsonValueKind.Array => (null, DeserializeArray(doc.RootElement)), - JsonValueKind.Object => (DeserializeObject(doc.RootElement), null), - _ => ThrowInvalid() - }; - - [DoesNotReturn, StackTraceHidden] - static (JsonRpcRequest? Model, ArrayPoolList? Collection) ThrowInvalid() - => throw new JsonException("Invalid"); + case JsonValueKind.Array: + collection = DeserializeArray(doc.RootElement); + break; + case JsonValueKind.Object: + model = DeserializeObject(doc.RootElement); + break; + } } private JsonRpcRequest DeserializeObject(JsonElement element) @@ -89,7 +89,7 @@ private JsonRpcRequest DeserializeObject(JsonElement element) { id = idNumber; } - else if (decimal.TryParse(idElement.GetRawText(), out var value)) + else if (idElement.TryGetDecimal(out var value)) { id = value; } @@ -103,7 +103,7 @@ private JsonRpcRequest DeserializeObject(JsonElement element) string? method = null; if (element.TryGetProperty("method"u8, out JsonElement methodElement)) { - method = methodElement.GetString(); + method = InternMethodName(methodElement); } if (!element.TryGetProperty("params"u8, out JsonElement paramsElement)) @@ -120,176 +120,243 @@ private JsonRpcRequest DeserializeObject(JsonElement element) }; } - private ArrayPoolList DeserializeArray(JsonElement element) => - new(element.GetArrayLength(), element.EnumerateArray().Select(DeserializeObject)); - - public async IAsyncEnumerable ProcessAsync(PipeReader reader, JsonRpcContext context) + private ArrayPoolList DeserializeArray(JsonElement element) { - if (ProcessExit.IsCancellationRequested) + ArrayPoolList list = new(element.GetArrayLength()); + foreach (JsonElement item in element.EnumerateArray()) { - JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(ErrorCodes.ResourceUnavailable, "Shutting down"); - yield return JsonRpcResult.Single(RecordResponse(response, new RpcReport("Shutdown", 0, false))); + list.Add(DeserializeObject(item)); } + return list; + } - if (IsRecordingRequest) - { - reader = await RecordRequest(reader); - } + /// + /// Returns a cached string constant for known engine method names to avoid allocation. + /// Falls back to for unknown methods. + /// + private static string? InternMethodName(JsonElement methodElement) + { + if (methodElement.ValueEquals("engine_newPayloadV4"u8)) return "engine_newPayloadV4"; + if (methodElement.ValueEquals("engine_forkchoiceUpdatedV3"u8)) return "engine_forkchoiceUpdatedV3"; + if (methodElement.ValueEquals("engine_newPayloadV3"u8)) return "engine_newPayloadV3"; + if (methodElement.ValueEquals("engine_forkchoiceUpdatedV2"u8)) return "engine_forkchoiceUpdatedV2"; + if (methodElement.ValueEquals("engine_getPayloadV4"u8)) return "engine_getPayloadV4"; + if (methodElement.ValueEquals("engine_getPayloadV3"u8)) return "engine_getPayloadV3"; + if (methodElement.ValueEquals("engine_newPayloadV2"u8)) return "engine_newPayloadV2"; + if (methodElement.ValueEquals("engine_newPayloadV1"u8)) return "engine_newPayloadV1"; + if (methodElement.ValueEquals("engine_exchangeCapabilities"u8)) return "engine_exchangeCapabilities"; + return methodElement.GetString(); + } - long startTime = Stopwatch.GetTimestamp(); - using CancellationTokenSource timeoutSource = _jsonRpcConfig.BuildTimeoutCancellationToken(); + private static readonly JsonReaderOptions _socketJsonReaderOptions = new() { AllowMultipleValues = true }; - // Initializes a buffer to store the data read from the reader. - ReadOnlySequence buffer = default; + public async IAsyncEnumerable ProcessAsync(PipeReader reader, JsonRpcContext context) + { + // Engine API (authenticated) requests skip the timeout CTS entirely -- they are from + // trusted consensus clients, must complete for consensus, and connection drops are + // handled by the PipeReader. Non-engine paths still use the pooled CTS. + CancellationTokenSource? timeoutSource = context.IsAuthenticated ? null : _jsonRpcConfig.BuildTimeoutCancellationToken(); + CancellationToken timeoutToken = timeoutSource?.Token ?? CancellationToken.None; try { - // Asynchronously reads data from the PipeReader. - ReadResult readResult = await reader.ReadToEndAsync(timeoutSource.Token); + if (ProcessExit.IsCancellationRequested) + { + JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(ErrorCodes.ResourceUnavailable, "Shutting down"); + yield return JsonRpcResult.Single(RecordResponse(response, new RpcReport("Shutdown", 0, false))); + yield break; + } + + if (IsRecordingRequest) + { + reader = await RecordRequest(reader); + } - buffer = readResult.Buffer; - // Placeholder for a result in case of deserialization failure. - JsonRpcResult? deserializationFailureResult = null; + JsonReaderState readerState = CreateJsonReaderState(context); + bool freshState = true; + bool shouldExit = false; - // Processes the buffer while it's not empty; before going out to outer loop to get more data. - while (!buffer.IsEmpty) + while (!shouldExit) { - JsonDocument? jsonDocument = null; - JsonRpcRequest? model = null; - ArrayPoolList? collection = null; + long startTime = Stopwatch.GetTimestamp(); + + ReadResult readResult; try { - // Tries to parse the JSON from the buffer. - if (!TryParseJson(ref buffer, out jsonDocument)) - { - deserializationFailureResult = GetParsingError(startTime, in buffer, "Error during parsing/validation."); - } - else - { - // Deserializes the JSON document into a request object or a collection of requests. - (model, collection) = DeserializeObjectOrArray(jsonDocument); - } + readResult = await reader.ReadAsync(timeoutToken); } catch (BadHttpRequestException e) { - // Increments failure metric and logs the exception, then stops processing. - Metrics.JsonRpcRequestDeserializationFailures++; - if (_logger.IsDebug) _logger.Debug($"Couldn't read request.{Environment.NewLine}{e}"); - yield break; + Handle(e); + break; } catch (ConnectionResetException e) { - // Logs exception, then stop processing. - if (_logger.IsTrace) _logger.Trace($"Connection reset.{Environment.NewLine}{e}"); - yield break; - } - catch (Exception ex) - { - deserializationFailureResult = GetParsingError(startTime, in buffer, "Error during parsing/validation.", ex); - } - - // Checks for deserialization failure and yields the result. - if (deserializationFailureResult.HasValue) - { - yield return deserializationFailureResult.Value; + Handle(e); break; } - // Handles a single JSON RPC request. - if (model is not null) - { - if (_logger.IsDebug) _logger.Debug($"JSON RPC request {model.Method}"); - - // Processes the individual request. - JsonRpcResult.Entry result = await HandleSingleRequest(model, context); - result.Response.AddDisposable(() => jsonDocument.Dispose()); + ReadOnlySequence buffer = readResult.Buffer; + bool advanced = false; - // Returns the result of the processed request. - yield return JsonRpcResult.Single(RecordResponse(result)); - } - - // Processes a collection of JSON RPC requests. - if (collection is not null) + try { - if (_logger.IsDebug) _logger.Debug($"{collection.Count} JSON RPC requests"); + bool isCompleted = readResult.IsCompleted || readResult.IsCanceled; + JsonRpcResult? result = null; + if (freshState) + { + buffer = buffer.TrimStart(); + } - // Checks for authentication and batch size limit. - if (!context.IsAuthenticated && collection.Count > _jsonRpcConfig.MaxBatchSize) + if (!buffer.IsEmpty) { - if (_logger.IsWarn) _logger.Warn($"The batch size limit was exceeded. The requested batch size {collection.Count}, and the current config setting is JsonRpc.{nameof(_jsonRpcConfig.MaxBatchSize)} = {_jsonRpcConfig.MaxBatchSize}."); - JsonRpcErrorResponse? response = _jsonRpcService.GetErrorResponse(ErrorCodes.LimitExceeded, "Batch size limit exceeded"); - response.AddDisposable(() => jsonDocument.Dispose()); - - deserializationFailureResult = JsonRpcResult.Single(RecordResponse(response, RpcReport.Error)); - collection.Dispose(); - yield return deserializationFailureResult.Value; - break; + try + { + freshState = TryParseJson(ref buffer, isCompleted, ref readerState, out JsonDocument? jsonDocument, context); + if (freshState) + { + result = await ProcessJsonDocument(jsonDocument, context, startTime); + } + else if (isCompleted && !buffer.IsEmpty) + { + result = GetParsingError(startTime, in buffer, "Error during parsing/validation: incomplete request."); + shouldExit = true; + } + + reader.AdvanceTo(buffer.Start, buffer.End); + advanced = true; + } + catch (BadHttpRequestException e) + { + Handle(e); + shouldExit = true; + } + catch (ConnectionResetException e) + { + Handle(e); + shouldExit = true; + } + catch (JsonException ex) + { + result = GetParsingError(startTime, in buffer, "Error during parsing/validation.", ex); + shouldExit = true; + } } - JsonRpcBatchResult jsonRpcBatchResult = new((e, c) => IterateRequest(collection, context, e).GetAsyncEnumerator(c)); - jsonRpcBatchResult.AddDisposable(() => collection.Dispose()); - yield return JsonRpcResult.Collection(jsonRpcBatchResult); - } - // Handles invalid requests. - if (model is null && collection is null) - { - Metrics.JsonRpcInvalidRequests++; - JsonRpcErrorResponse errorResponse = _jsonRpcService.GetErrorResponse(ErrorCodes.InvalidRequest, "Invalid request"); - errorResponse.AddDisposable(() => jsonDocument.Dispose()); + if (result.HasValue) + { + yield return result.Value; + } - if (_logger.IsTrace) + shouldExit |= isCompleted && buffer.IsEmpty; + } + finally + { + if (!advanced) { - TraceResult(errorResponse); - TraceFailure(startTime); + reader.AdvanceTo(buffer.Start, buffer.End); } - deserializationFailureResult = JsonRpcResult.Single(RecordResponse(errorResponse, new RpcReport("# parsing error #", (long)Stopwatch.GetElapsedTime(startTime).TotalMilliseconds, false))); - yield return deserializationFailureResult.Value; - break; } - - buffer = buffer.TrimStart(); } + } + finally + { + await reader.CompleteAsync(); + if (timeoutSource is not null) + JsonRpcConfigExtension.ReturnTimeoutCancellationToken(timeoutSource); + } + } - // Checks if the deserialization failed - if (deserializationFailureResult.HasValue) + private void Handle(ConnectionResetException e) + { + if (_logger.IsTrace) _logger.Trace($"Connection reset.{Environment.NewLine}{e}"); + } + + private void Handle(BadHttpRequestException e) + { + Metrics.JsonRpcRequestDeserializationFailures++; + if (_logger.IsDebug) _logger.Debug($"Couldn't read request.{Environment.NewLine}{e}"); + } + + private static bool TryParseJson( + ref ReadOnlySequence buffer, + bool isFinalBlock, + ref JsonReaderState readerState, + [NotNullWhen(true)] out JsonDocument? jsonDocument, + JsonRpcContext context) + { + Utf8JsonReader jsonReader = new(buffer, isFinalBlock, readerState); + bool parsed = JsonDocument.TryParseValue(ref jsonReader, out jsonDocument); + buffer = buffer.Slice(jsonReader.BytesConsumed); + readerState = parsed + ? CreateJsonReaderState(context) // Reset state for the next document + : jsonReader.CurrentState; // Preserve state for resumption when more data arrives + + return parsed; + } + + private static JsonReaderState CreateJsonReaderState(JsonRpcContext context) => + new(context.RpcEndpoint == RpcEndpoint.Http ? default : _socketJsonReaderOptions); + + private async Task ProcessJsonDocument(JsonDocument jsonDocument, JsonRpcContext context, long startTime) + { + try + { + DeserializeObjectOrArray(jsonDocument, out JsonRpcRequest? model, out ArrayPoolList? collection); + + // Handles a single JSON RPC request + if (model is not null) { - yield break; + if (_logger.IsDebug) _logger.Debug($"JSON RPC request {model.Method}"); + + JsonRpcResult.Entry result = await HandleSingleRequest(model, context); + result.Response.AddDisposable(jsonDocument.Dispose); + + return JsonRpcResult.Single(RecordResponse(result)); } - // Checks if the read operation is completed. - if (readResult.IsCompleted) + // Processes a collection of JSON RPC requests + if (collection is not null) { - if (buffer.Length > 0 && HasNonWhitespace(buffer)) + if (_logger.IsDebug) _logger.Debug($"{collection.Count} JSON RPC requests"); + + if (!context.IsAuthenticated && collection.Count > _jsonRpcConfig.MaxBatchSize) { - yield return GetParsingError(startTime, in buffer, "Error during parsing/validation: incomplete request."); + if (_logger.IsWarn) _logger.Warn($"The batch size limit was exceeded. The requested batch size {collection.Count}, and the current config setting is JsonRpc.{nameof(_jsonRpcConfig.MaxBatchSize)} = {_jsonRpcConfig.MaxBatchSize}."); + JsonRpcErrorResponse? errorResponse = _jsonRpcService.GetErrorResponse(ErrorCodes.LimitExceeded, "Batch size limit exceeded"); + errorResponse.AddDisposable(jsonDocument.Dispose); + + collection.Dispose(); + return JsonRpcResult.Single(RecordResponse(errorResponse, RpcReport.Error)); } + JsonRpcBatchResult jsonRpcBatchResult = new((e, c) => IterateRequest(collection, context, e).GetAsyncEnumerator(c)); + jsonRpcBatchResult.AddDisposable(jsonDocument.Dispose); + jsonRpcBatchResult.AddDisposable(collection.Dispose); + return JsonRpcResult.Collection(jsonRpcBatchResult); } - } - finally - { - // Advances the reader to the end of the buffer if not null. - if (!buffer.FirstSpan.IsNull()) + + // Handles invalid requests (neither object nor array) + Metrics.JsonRpcInvalidRequests++; + JsonRpcErrorResponse invalidResponse = _jsonRpcService.GetErrorResponse(ErrorCodes.InvalidRequest, "Invalid request"); + invalidResponse.AddDisposable(jsonDocument.Dispose); + + if (_logger.IsTrace) { - reader.AdvanceTo(buffer.End); + TraceResult(invalidResponse); + _logger.Trace($" Failed request handled in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"); } + return JsonRpcResult.Single(RecordResponse(invalidResponse, new RpcReport("# parsing error #", (long)Stopwatch.GetElapsedTime(startTime).TotalMicroseconds, false))); + } + catch + { + jsonDocument.Dispose(); + throw; } - - // Completes the PipeReader's asynchronous reading operation. - await reader.CompleteAsync(); - - [MethodImpl(MethodImplOptions.NoInlining)] - void TraceFailure(long startTime) => _logger.Trace($" Failed request handled in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"); } - // Handles general exceptions during parsing and validation. - // Sends an error response and stops the stopwatch. private JsonRpcResult GetParsingError(long startTime, ref readonly ReadOnlySequence buffer, string error, Exception? exception = null) { Metrics.JsonRpcRequestDeserializationFailures++; - - if (_logger.IsError) - { - _logger.Error(error, exception); - } + if (_logger.IsError) _logger.Error(error, exception); if (_logger.IsDebug) { @@ -305,35 +372,11 @@ private JsonRpcResult GetParsingError(long startTime, ref readonly ReadOnlySeque } } - JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(ErrorCodes.ParseError, "Incorrect message"); + JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(ErrorCodes.ParseError, "parse error"); if (_logger.IsTrace) TraceResult(response); return JsonRpcResult.Single(RecordResponse(response, new RpcReport("# parsing error #", (long)Stopwatch.GetElapsedTime(startTime).TotalMicroseconds, false))); } - private static bool HasNonWhitespace(ReadOnlySequence buffer) - { - static bool HasNonWhitespace(ReadOnlySpan span) - { - static ReadOnlySpan WhiteSpace() => " \n\r\t"u8; - return span.IndexOfAnyExcept(WhiteSpace()) >= 0; - } - - if (buffer.IsSingleSegment) - { - return HasNonWhitespace(buffer.FirstSpan); - } - - foreach (ReadOnlyMemory memory in buffer) - { - if (HasNonWhitespace(memory.Span)) - { - return true; - } - } - - return false; - } - private async IAsyncEnumerable IterateRequest( ArrayPoolList requests, JsonRpcContext context, @@ -399,15 +442,6 @@ static bool HasNonWhitespace(ReadOnlySpan span) return result; } - private static bool TryParseJson(ref ReadOnlySequence buffer, [NotNullWhen(true)] out JsonDocument? jsonDocument) - { - Utf8JsonReader reader = new(buffer); - if (!JsonDocument.TryParseValue(ref reader, out jsonDocument)) return false; - buffer = buffer.Slice(reader.BytesConsumed); - return true; - - } - private bool IsRecordingRequest => (_jsonRpcConfig.RpcRecorderState & RpcRecorderState.Request) != 0; private bool IsRecordingResponse => (_jsonRpcConfig.RpcRecorderState & RpcRecorderState.Response) != 0; diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcResponseJsonContext.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcResponseJsonContext.cs new file mode 100644 index 000000000000..322a04158fa3 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcResponseJsonContext.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json.Serialization; + +namespace Nethermind.JsonRpc; + +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Metadata, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(JsonRpcSuccessResponse))] +[JsonSerializable(typeof(JsonRpcErrorResponse))] +[JsonSerializable(typeof(JsonRpcResponse))] +[JsonSerializable(typeof(Error))] +public partial class JsonRpcResponseJsonContext : JsonSerializerContext; diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs index 82de21c147f5..781e7da43de2 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs @@ -9,11 +9,12 @@ using System.Text; using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using System.Threading; using System.Threading.Tasks; +using Nethermind.Blockchain; using Nethermind.Core; using Nethermind.Core.Collections; -using Nethermind.Core.Threading; using Nethermind.JsonRpc.Exceptions; using Nethermind.JsonRpc.Modules; using Nethermind.Logging; @@ -24,8 +25,11 @@ namespace Nethermind.JsonRpc; -public class JsonRpcService : IJsonRpcService +public sealed class JsonRpcService : IJsonRpcService { + private readonly static Lock _reparseLock = new(); + private static Dictionary _reparseReflectionCache = new(); + private readonly ILogger _logger; private readonly IRpcModuleProvider _rpcModuleProvider; private readonly HashSet _methodsLoggingFiltering; @@ -106,6 +110,75 @@ private Task ExecuteRequestAsync(JsonRpcRequest rpcRequest, Jso private async Task ExecuteAsync(JsonRpcRequest request, string methodName, ResolvedMethodInfo method, JsonRpcContext context) { + const string GetLogsMethodName = "eth_getLogs"; + + JsonRpcErrorResponse? value = PrepareParameters(request, methodName, method, out object[]? parameters, out bool hasMissing); + if (value is not null) + { + return value; + } + + IRpcModule rpcModule = await _rpcModuleProvider.Rent(methodName, method.ReadOnly); + if (rpcModule is IContextAwareRpcModule contextAwareModule) + { + contextAwareModule.Context = context; + } + bool returnImmediately = methodName != GetLogsMethodName; + Action? returnAction = returnImmediately ? null : () => _rpcModuleProvider.Return(methodName, rpcModule); + IResultWrapper? resultWrapper = null; + try + { + // Execute method + object invocationResult = hasMissing ? + method.MethodInfo.Invoke(rpcModule, parameters) : + method.Invoker.Invoke(rpcModule, new Span(parameters)); + + switch (invocationResult) + { + case IResultWrapper wrapper: + resultWrapper = wrapper; + break; + case Task task: + await task; + resultWrapper = GetResultProperty(task)?.GetValue(task) as IResultWrapper; + break; + } + } + catch (Exception ex) + { + return HandleInvocationException(ex, methodName, request, returnAction); + } + finally + { + if (returnImmediately) + { + _rpcModuleProvider.Return(methodName, rpcModule); + } + } + + if (resultWrapper is null) + { + return HandleMissingResultWrapper(request, methodName, returnAction); + } + + Result result = resultWrapper.Result; + return result.ResultType != ResultType.Success + ? GetErrorResponse(methodName, resultWrapper.ErrorCode, result.Error, resultWrapper.Data, request.Id, returnAction, resultWrapper.IsTemporary) + : GetSuccessResponse(methodName, resultWrapper.Data, request.Id, returnAction); + + [MethodImpl(MethodImplOptions.NoInlining)] + JsonRpcResponse HandleMissingResultWrapper(JsonRpcRequest request, string methodName, Action returnAction) + { + string errorMessage = $"Method {methodName} execution result does not implement IResultWrapper"; + if (_logger.IsError) _logger.Error(errorMessage); + return GetErrorResponse(methodName, ErrorCodes.InternalError, errorMessage, null, request.Id, returnAction); + } + } + + private JsonRpcErrorResponse? PrepareParameters(JsonRpcRequest request, string methodName, ResolvedMethodInfo method, out object[]? parameters, out bool hasMissing) + { + parameters = null; + hasMissing = false; JsonElement providedParameters = request.Params; if (_logger.IsTrace) LogRequest(methodName, providedParameters, method.ExpectedParameters); @@ -166,87 +239,57 @@ private async Task ExecuteAsync(JsonRpcRequest request, string missingParamsCount -= explicitNullableParamsCount; - //prepare parameters - object[]? parameters = null; - bool hasMissing = false; + // Prepare parameters if (method.ExpectedParameters.Length > 0) { - (parameters, hasMissing) = DeserializeParameters(method.ExpectedParameters, providedParametersLength, providedParameters, missingParamsCount); - if (parameters is null) + try { - if (_logger.IsWarn) _logger.Warn($"Incorrect JSON RPC parameters when calling {methodName} with params [{string.Join(", ", providedParameters)}]"); + (parameters, hasMissing) = DeserializeParameters(method.ExpectedParameters, providedParametersLength, providedParameters, missingParamsCount); + } + catch (Exception e) + { + if (_logger.IsWarn) _logger.Warn($"Incorrect JSON RPC parameters when calling {methodName} with params [{string.Join(", ", providedParameters)}] {e}"); return GetErrorResponse(methodName, ErrorCodes.InvalidParams, "Invalid params", null, request.Id); } } - //execute method - IResultWrapper resultWrapper = null; - IRpcModule rpcModule = await _rpcModuleProvider.Rent(methodName, method.ReadOnly); - if (rpcModule is IContextAwareRpcModule contextAwareModule) - { - contextAwareModule.Context = context; - } - bool returnImmediately = methodName != "eth_getLogs"; - Action? returnAction = returnImmediately ? null : () => _rpcModuleProvider.Return(methodName, rpcModule); - try - { - object invocationResult = hasMissing ? - method.MethodInfo.Invoke(rpcModule, parameters) : - method.Invoker.Invoke(rpcModule, new Span(parameters)); + return null; + } - switch (invocationResult) - { - case IResultWrapper wrapper: - resultWrapper = wrapper; - break; - case Task task: - await task; - resultWrapper = GetResultProperty(task)?.GetValue(task) as IResultWrapper; - break; - } - } - catch (Exception e) when (e is TargetParameterCountException || e is ArgumentException) - { - return GetErrorResponse(methodName, ErrorCodes.InvalidParams, e.Message, e.ToString(), request.Id, returnAction); - } - catch (TargetInvocationException e) when (e.InnerException is JsonException) - { - return GetErrorResponse(methodName, ErrorCodes.InvalidParams, "Invalid params", e.InnerException?.ToString(), request.Id, returnAction); - } - catch (Exception e) when (e is OperationCanceledException || e.InnerException is OperationCanceledException) - { - string errorMessage = $"{methodName} request was canceled due to enabled timeout."; - return GetErrorResponse(methodName, ErrorCodes.Timeout, errorMessage, null, request.Id, returnAction); - } - catch (Exception e) when (e.InnerException is InsufficientBalanceException) + private JsonRpcErrorResponse HandleInvocationException(Exception ex, string methodName, JsonRpcRequest request, Action? returnAction) + { + return ex switch { - return GetErrorResponse(methodName, ErrorCodes.InvalidInput, e.InnerException.Message, e.ToString(), request.Id, returnAction); - } - catch (Exception ex) + TargetParameterCountException or ArgumentException => + GetErrorResponse(methodName, ErrorCodes.InvalidParams, ex.Message, ex.ToString(), request.Id, returnAction), + + TargetInvocationException and { InnerException: JsonException } => + GetErrorResponse(methodName, ErrorCodes.InvalidParams, "Invalid params", ex.InnerException?.ToString(), request.Id, returnAction), + + OperationCanceledException or { InnerException: OperationCanceledException } => + GetErrorResponse(methodName, ErrorCodes.Timeout, + $"{methodName} request was canceled due to enabled timeout.", null, request.Id, returnAction), + + { 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) + }; + + JsonRpcErrorResponse HandleException(Exception ex, string methodName, JsonRpcRequest request, Action? returnAction) { if (_logger.IsError) _logger.Error($"Error during method execution, request: {request}", ex); return GetErrorResponse(methodName, ErrorCodes.InternalError, "Internal error", ex.ToString(), request.Id, returnAction); } - finally - { - if (returnImmediately) - { - _rpcModuleProvider.Return(methodName, rpcModule); - } - } - - if (resultWrapper is null) - { - string errorMessage = $"Method {methodName} execution result does not implement IResultWrapper"; - if (_logger.IsError) _logger.Error(errorMessage); - return GetErrorResponse(methodName, ErrorCodes.InternalError, errorMessage, null, request.Id, returnAction); - } - - Result? result = resultWrapper.Result; - - return result.ResultType != ResultType.Success - ? GetErrorResponse(methodName, resultWrapper.ErrorCode, result.Error, resultWrapper.Data, request.Id, returnAction, resultWrapper.IsTemporary) - : GetSuccessResponse(methodName, resultWrapper.Data, request.Id, returnAction); } private PropertyInfo? GetResultProperty(Task task) @@ -302,7 +345,7 @@ private void LogRequest(string methodName, JsonElement providedParameters, Expec { foreach (JsonElement param in providedParameters.EnumerateArray()) { - string? parameter = expectedParameters.ElementAtOrDefault(paramsCount).Info?.Name == "passphrase" + string? parameter = (uint)paramsCount < (uint)expectedParameters.Length && expectedParameters[paramsCount].Info?.Name == "passphrase" ? "{passphrase}" : param.GetRawText(); @@ -361,88 +404,86 @@ private void LogRequest(string methodName, JsonElement providedParameters, Expec } else { + EthereumJsonSerializer.JsonOptions.TryGetTypeInfo(paramType, out JsonTypeInfo? typeInfo); if (providedParameter.ValueKind == JsonValueKind.String) { - JsonConverter converter = EthereumJsonSerializer.JsonOptions.GetConverter(paramType); - executionParam = converter.GetType().Namespace.StartsWith("System.", StringComparison.Ordinal) + if (!_reparseReflectionCache.TryGetValue(paramType, out bool reparseString)) + { + reparseString = CreateNewCacheEntry(paramType); + } + + executionParam = reparseString ? JsonSerializer.Deserialize(providedParameter.GetString(), paramType, EthereumJsonSerializer.JsonOptions) - : providedParameter.Deserialize(paramType, EthereumJsonSerializer.JsonOptions); + : typeInfo is not null + ? providedParameter.Deserialize(typeInfo) + : providedParameter.Deserialize(paramType, EthereumJsonSerializer.JsonOptions); } else { - executionParam = providedParameter.Deserialize(paramType, EthereumJsonSerializer.JsonOptions); + executionParam = typeInfo is not null + ? providedParameter.Deserialize(typeInfo) + : providedParameter.Deserialize(paramType, EthereumJsonSerializer.JsonOptions); } } return executionParam; } - private (object[]? parameters, bool hasMissing) DeserializeParameters( - ExpectedParameter[] expectedParameters, - int providedParametersLength, - JsonElement providedParameters, - int missingParamsCount) + private static bool CreateNewCacheEntry(Type paramType) { - const int parallelThreshold = 4; - try + lock (_reparseLock) { - bool hasMissing = false; - int totalLength = providedParametersLength + missingParamsCount; - - if (totalLength == 0) return (Array.Empty(), false); + // Re-check inside the lock in case another thread already added it + if (_reparseReflectionCache.TryGetValue(paramType, out bool reparseString)) + { + return reparseString; + } - object[] executionParameters = new object[totalLength]; + JsonConverter converter = EthereumJsonSerializer.JsonOptions.GetConverter(paramType); + reparseString = converter.GetType().Namespace.StartsWith("System.", StringComparison.Ordinal); - if (providedParametersLength <= parallelThreshold) + // Copy-on-write: create a new dictionary so we don't mutate + Dictionary reparseReflectionCache = new Dictionary(_reparseReflectionCache) { - for (int i = 0; i < providedParametersLength; i++) - { - JsonElement providedParameter = providedParameters[i]; - ExpectedParameter expectedParameter = expectedParameters[i]; + [paramType] = reparseString + }; - object? parameter = DeserializeParameter(providedParameter, expectedParameter); - executionParameters[i] = parameter; - if (!hasMissing && ReferenceEquals(parameter, Type.Missing)) - { - hasMissing = true; - } - } - } - else if (providedParametersLength > parallelThreshold) - { - ParallelUnbalancedWork.For( - 0, - providedParametersLength, - ParallelUnbalancedWork.DefaultOptions, - (providedParameters, expectedParameters, executionParameters, hasMissing), - static (i, state) => - { - JsonElement providedParameter = state.providedParameters[i]; - ExpectedParameter expectedParameter = state.expectedParameters[i]; + // Publish the new cache instance atomically by swapping the reference. + _reparseReflectionCache = reparseReflectionCache; + return reparseString; + } + } - object? parameter = DeserializeParameter(providedParameter, expectedParameter); - state.executionParameters[i] = parameter; - if (!state.hasMissing && ReferenceEquals(parameter, Type.Missing)) - { - state.hasMissing = true; - } + private static (object[]? parameters, bool hasMissing) DeserializeParameters( + ExpectedParameter[] expectedParameters, + int providedParametersLength, + JsonElement providedParameters, + int missingParamsCount) + { + int totalLength = providedParametersLength + missingParamsCount; + if (totalLength == 0) return (Array.Empty(), false); - return state; - }); - } + object[] executionParameters = new object[totalLength]; - for (int i = providedParametersLength; i < totalLength; i++) - { - executionParameters[i] = Type.Missing; - } - hasMissing |= providedParametersLength < totalLength; - return (executionParameters, hasMissing); + bool hasMissing = missingParamsCount != 0; + int i = 0; + var enumerator = providedParameters.EnumerateArray(); + while (enumerator.MoveNext()) + { + ExpectedParameter expectedParameter = expectedParameters[i]; + + object? parameter = DeserializeParameter(enumerator.Current, expectedParameter); + executionParameters[i] = parameter; + hasMissing |= ReferenceEquals(parameter, Type.Missing); + i++; } - catch (Exception e) + + for (i = providedParametersLength; i < totalLength; i++) { - if (_logger.IsWarn) _logger.Warn("Error while parsing JSON RPC request parameters " + e); - return (null, false); + executionParameters[i] = Type.Missing; } + + return (executionParameters, hasMissing); } private static JsonRpcResponse GetSuccessResponse(string methodName, object result, object id, Action? disposableAction) diff --git a/src/Nethermind/Nethermind.JsonRpc/Metrics.cs b/src/Nethermind/Nethermind.JsonRpc/Metrics.cs index fcece5a25853..3943a58b2cfc 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Metrics.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Metrics.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Collections; using System.ComponentModel; using Nethermind.Core.Attributes; using Nethermind.Core.Metric; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs index e53e5ddfb616..357a39a348c4 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs @@ -10,10 +10,8 @@ using Nethermind.Config; using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.JsonRpc.Modules.Admin.Utils; using Nethermind.Network; using Nethermind.Network.Config; -using Nethermind.Network.Contract; using Nethermind.Specs.ChainSpecStyle; using Nethermind.Stats.Model; using Nethermind.JsonRpc.Modules.Subscribe; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/IAdminRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/IAdminRpcModule.cs index d12951a5d93f..dda8d5709e30 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/IAdminRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/IAdminRpcModule.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Nethermind.Blockchain.Find; -using Nethermind.Blockchain.FullPruning; namespace Nethermind.JsonRpc.Modules.Admin; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/Models/EthProtocolInfo.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/Models/EthProtocolInfo.cs index e20ddfeacdfe..4d234b0f57da 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/Models/EthProtocolInfo.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/Models/EthProtocolInfo.cs @@ -4,7 +4,6 @@ using Nethermind.Core.Crypto; using Nethermind.Int256; using Nethermind.Specs.ChainSpecStyle; -using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Modules.Admin.Models { diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/NodeInfo.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/NodeInfo.cs index 106de6ba2d17..c82f605c612a 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/NodeInfo.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/NodeInfo.cs @@ -2,9 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Text.Json.Serialization; using Nethermind.JsonRpc.Modules.Admin.Models; -using Nethermind.JsonRpc.Modules.Admin.Utils; using Nethermind.Network.Contract.P2P; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/PeerInfo.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/PeerInfo.cs index 230767bb7dc7..b5a21867a893 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/PeerInfo.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/PeerInfo.cs @@ -6,7 +6,6 @@ using Nethermind.Network; using Nethermind.Network.P2P; using Nethermind.Stats.Model; -using Nethermind.Serialization.Json; using Nethermind.Network.Contract.P2P; using Nethermind.Network.P2P.ProtocolHandlers; using Nethermind.JsonRpc.Modules.Admin.Utils; @@ -19,7 +18,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/Admin/PortsInfo.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/PortsInfo.cs index 37cb0ca6357a..484924b1e0d5 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/PortsInfo.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/PortsInfo.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Text.Json.Serialization; - namespace Nethermind.JsonRpc.Modules.Admin { public class PortsInfo diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/Utils/NetworkInfoBuilder.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/Utils/NetworkInfoBuilder.cs index 84ca97a5f686..d9f62c8bd774 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/Utils/NetworkInfoBuilder.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/Utils/NetworkInfoBuilder.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Net; using Nethermind.Network.P2P; using Nethermind.Network; using Nethermind.Stats.Model; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/BlockFinderExtensions.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/BlockFinderExtensions.cs index 16d0a22526ed..2e338e114af3 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/BlockFinderExtensions.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/BlockFinderExtensions.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using Nethermind.Blockchain; using Nethermind.Blockchain.Find; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -35,26 +34,18 @@ 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) + ? new SearchResult($"{blockParameter} could not be found", ErrorCodes.ResourceNotFound) : new SearchResult(header); } @@ -62,23 +53,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) { @@ -89,13 +72,15 @@ public static SearchResult SearchForBlock(this IBlockFinder blockFinder, if (blockFinder.IsBlockPruned(blockParameter)) { - return new SearchResult("Pruned history unavailable", ErrorCodes.PrunedHistoryUnavailable); + return new SearchResult( + $"pruned history unavailable for block {blockParameter}", + ErrorCodes.PrunedHistoryUnavailable); } if (!allowNulls) { return new SearchResult( - $"Block {blockParameter.BlockHash?.ToString() ?? blockParameter.BlockNumber?.ToString() ?? blockParameter.Type.ToString()} could not be found", + $"Block {blockParameter} could not be found", ErrorCodes.ResourceNotFound); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugBridge.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugBridge.cs index 65cdf2cd9bfa..97dfeb9eea44 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugBridge.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugBridge.cs @@ -19,7 +19,6 @@ using Nethermind.Blockchain.Tracing.GethStyle; using Nethermind.Crypto; using Nethermind.Serialization.Rlp; -using Nethermind.State; using Nethermind.Synchronization.ParallelSync; using Nethermind.Synchronization.Reporting; using Nethermind.Facade.Eth.RpcTransaction; @@ -188,9 +187,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() }; @@ -224,25 +223,28 @@ private IEnumerable GetBundleTrace(TransactionBundle bundle, Bl { foreach (TransactionForRpc txForRpc in bundle.Transactions) { - Transaction tx = txForRpc.ToTransaction(); GethLikeTxTrace? trace; - - try + Result txResult = txForRpc.ToTransaction(validateUserInput: true); + if (txResult.IsError) { - trace = _tracer.Trace( - blockParameter, - tx, - gethTraceOptions ?? GethTraceOptions.Default, - cancellationToken); + trace = CreateFailTrace(txForRpc.Gas); } - catch (Exception) + else { - trace = new GethLikeTxTrace + Transaction tx = txResult.Data; + + try { - Failed = true, - Gas = tx.GasLimit, - ReturnValue = [] - }; + trace = _tracer.Trace( + blockParameter, + tx, + gethTraceOptions ?? GethTraceOptions.Default, + cancellationToken); + } + catch (Exception) + { + trace = CreateFailTrace(tx.GasLimit); + } } if (trace is not null) @@ -250,5 +252,7 @@ private IEnumerable GetBundleTrace(TransactionBundle bundle, Bl yield return trace; } } + + static GethLikeTxTrace? CreateFailTrace(long? gasLimit) => new() { Failed = true, Gas = gasLimit ?? 0, ReturnValue = [] }; } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugModuleFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugModuleFactory.cs index 7ae8f6721226..2f0dca1873b5 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 02fce115a9cd..f7c4741afca2 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs @@ -41,10 +41,6 @@ public class DebugRpcModule( private readonly BlockDecoder _blockDecoder = new(); private readonly ulong _secondsPerSlot = blocksConfig.SecondsPerSlot; - private static bool HasStateForBlock(IBlockchainBridge blockchainBridge, BlockHeader header) - { - return blockchainBridge.HasStateForBlock(header); - } public ResultWrapper debug_getChainLevel(in long number) { @@ -101,7 +97,12 @@ public ResultWrapper debug_traceCall(TransactionForRpc call, Bl // enforces gas cap call.EnsureDefaults(jsonRpcConfig.GasCap); - Transaction tx = call.ToTransaction(); + Result txResult = call.ToTransaction(validateUserInput: true); + if (!txResult.Success(out Transaction? tx, out string? error)) + { + return ResultWrapper.Fail(error, ErrorCodes.InvalidInput); + } + using CancellationTokenSource timeout = BuildTimeoutCancellationTokenSource(); CancellationToken cancellationToken = timeout.Token; @@ -376,7 +377,7 @@ public ResultWrapper debug_resetHead(Hash256 blockHash) RlpBehaviors encodingSettings = RlpBehaviors.SkipTypedWrapping | (transaction.IsInMempoolForm() ? RlpBehaviors.InMempoolForm : RlpBehaviors.None); using NettyRlpStream stream = TxDecoder.Instance.EncodeToNewNettyStream(transaction, encodingSettings); - return ResultWrapper.Success(stream.AsSpan().ToHexString(false)); + return ResultWrapper.Success(stream.AsSpan().ToHexString(true)); } public ResultWrapper debug_getRawReceipts(BlockParameter blockParameter) @@ -419,9 +420,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) @@ -538,7 +539,7 @@ private ResultWrapper>> TraceCallManyWi if (simulationResult.ErrorCode != 0) { - string errorMessage = simulationResult.Result?.ToString() ?? $"Simulation failed with error code {simulationResult.ErrorCode}."; + string errorMessage = simulationResult.Result ? $"Simulation failed with error code {simulationResult.ErrorCode}." : simulationResult.Result.ToString(); if (_logger.IsWarn) _logger.Warn($"debug_traceCallMany simulation failed: Code={simulationResult.ErrorCode}, Details={errorMessage}"); return ResultWrapper>>.Fail(errorMessage, simulationResult.ErrorCode); } @@ -580,7 +581,7 @@ private static ResultWrapper GetRlpDecodingFailureResult(Rlp b error = GetFailureResult(searchResult, debugBridge.HaveNotSyncedHeadersYet()); return null; } - if (!HasStateForBlock(blockchainBridge, header)) + if (!blockchainBridge.HasStateForBlock(header)) { error = GetStateFailureResult(header); return null; @@ -601,7 +602,7 @@ private static ResultWrapper GetRlpDecodingFailureResult(Rlp b debugBridge.HaveNotSyncedHeadersYet()); return null; } - if (!HasStateForBlock(blockchainBridge, header)) + if (!blockchainBridge.HasStateForBlock(header)) { error = GetStateFailureResult(header); return null; @@ -635,7 +636,7 @@ private static ResultWrapper GetRlpDecodingFailureResult(Rlp b return null; } - if (!HasStateForBlock(blockchainBridge, block.Header)) + if (!blockchainBridge.HasStateForBlock(block.Header)) { error = GetStateFailureResult(block.Header); return null; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugBridge.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugBridge.cs index f74639c01ba1..331859fa10c0 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugBridge.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugBridge.cs @@ -33,7 +33,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 aa9421b5908e..72f8ee4af0be 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugRpcModule.cs @@ -103,8 +103,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/DebugModule/TransactionBundle.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/TransactionBundle.cs index f2da792d4d4c..4e11f17c79cb 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/TransactionBundle.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/TransactionBundle.cs @@ -2,11 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Evm; using Nethermind.Facade.Eth.RpcTransaction; -using Nethermind.Facade.Proxy.Models.Simulate; namespace Nethermind.JsonRpc.Modules.DebugModule; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs index d5b6001e4aa7..7ead40a36d49 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs @@ -5,6 +5,7 @@ using Nethermind.Blockchain.Receipts; using Nethermind.Config; using Nethermind.Core.Specs; +using Nethermind.Db.LogIndex; using Nethermind.Facade; using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Modules.Eth.GasPrice; @@ -33,7 +34,8 @@ public class EthModuleFactory( IFeeHistoryOracle feeHistoryOracle, IProtocolsManager protocolsManager, IBlocksConfig blocksConfig, - IForkInfo forkInfo) + IForkInfo forkInfo, + ILogIndexConfig logIndexConfig) : ModuleFactoryBase { private readonly ulong _secondsPerSlot = blocksConfig.SecondsPerSlot; @@ -57,6 +59,7 @@ public override IEthRpcModule Create() feeHistoryOracle, protocolsManager, forkInfo, + logIndexConfig, _secondsPerSlot); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.TransactionExecutor.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.TransactionExecutor.cs index 3e787b39a751..812d74c648bf 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.TransactionExecutor.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.TransactionExecutor.cs @@ -2,8 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; +using System.Text; using System.Threading; using Nethermind.Blockchain.Find; using Nethermind.Core; @@ -26,46 +25,17 @@ private abstract class TxExecutor(IBlockchainBridge blockchainBridge, I { private bool NoBaseFee { get; set; } - protected override string? Validate(TransactionForRpc call) + protected override Result Prepare(TransactionForRpc call) { - if (call is LegacyTransactionForRpc legacyTransaction) + if (_rpcConfig.GasCap is not null) { - if (legacyTransaction.To is null && legacyTransaction.Input is null or { Length: 0 }) - return ContractCreationWithoutDataError; + call.EnsureDefaults(_rpcConfig.GasCap); } - if (call is EIP1559TransactionForRpc eip1559Transaction) - { - if (eip1559Transaction.GasPrice != null) - return GasPriceInEip1559Error; - - if (eip1559Transaction.MaxFeePerGas is not null && eip1559Transaction.MaxFeePerGas == 0) - return ZeroMaxFeePerGasError; - - if (eip1559Transaction.MaxFeePerGas < eip1559Transaction.MaxPriorityFeePerGas) - return MaxFeePerGasSmallerThenMaxPriorityFeePerGasError( - eip1559Transaction.MaxFeePerGas, - eip1559Transaction.MaxPriorityFeePerGas); - } - - if (call is BlobTransactionForRpc blobTransaction) - { - if (blobTransaction.BlobVersionedHashes is null || blobTransaction.BlobVersionedHashes.Length == 0) - return AtLeastOneBlobInBlobTransactionError; - - if (blobTransaction.To is null) - return MissingToInBlobTxError; + Result result = call.ToTransaction(validateUserInput: true); + if (result.IsError) return result; - if (blobTransaction.MaxFeePerBlobGas is not null && blobTransaction.MaxFeePerBlobGas == 0) - return ZeroMaxFeePerBlobGasError; - } - - return null; - } - - protected override Transaction Prepare(TransactionForRpc call) - { - var tx = call.ToTransaction(); + Transaction tx = result.Data; tx.ChainId = _blockchainBridge.GetChainId(); return tx; } @@ -109,27 +79,26 @@ public ResultWrapper ExecuteTx(TransactionForRpc transactionCall, Block protected abstract ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, Dictionary? stateOverride, CancellationToken token); - protected ResultWrapper CreateResultWrapper(bool inputError, string? errorMessage, TResult? bodyData) + protected ResultWrapper CreateResultWrapper(bool inputError, string? errorMessage, TResult? bodyData, bool executionReverted, byte[]? executionRevertedReason) { - if (inputError) - return ResultWrapper.Fail(errorMessage, ErrorCodes.InvalidInput); - if (errorMessage is not null) + if (inputError || errorMessage is not null) + { + if (executionReverted) + { + if (executionRevertedReason is not null) + { + return ResultWrapper.Fail("execution reverted: " + errorMessage, ErrorCodes.ExecutionReverted, executionRevertedReason.ToHexString(true)); + } + + var errorData = errorMessage is not null ? Encoding.UTF8.GetBytes(errorMessage).ToHexString(true) : null; + return ResultWrapper.Fail("execution reverted: " + errorMessage, ErrorCodes.ExecutionReverted, errorData); + } + return ResultWrapper.Fail(errorMessage, ErrorCodes.InvalidInput, bodyData); + } return ResultWrapper.Success(bodyData); } - - private const string GasPriceInEip1559Error = "both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified"; - private const string AtLeastOneBlobInBlobTransactionError = "need at least 1 blob for a blob transaction"; - 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( - UInt256? maxFeePerGas, - UInt256? maxPriorityFeePerGas) - => $"maxFeePerGas ({maxFeePerGas}) < maxPriorityFeePerGas ({maxPriorityFeePerGas})"; - - private const string ContractCreationWithoutDataError = "contract creation without any data provided"; } private class CallTxExecutor(IBlockchainBridge blockchainBridge, IBlockFinder blockFinder, IJsonRpcConfig rpcConfig) @@ -137,9 +106,14 @@ private class CallTxExecutor(IBlockchainBridge blockchainBridge, IBlockFinder bl { protected override ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, Dictionary? stateOverride, CancellationToken token) { + if (_rpcConfig.GasCap is not null) + { + tx.GasLimit = long.Min(tx.GasLimit, _rpcConfig.GasCap.Value); + } + CallOutput result = _blockchainBridge.Call(header, tx, stateOverride, token); - return CreateResultWrapper(result.InputError, result.Error, result.OutputData?.ToHexString(true)); + return CreateResultWrapper(result.InputError, result.Error, result.OutputData?.ToHexString(true), result.ExecutionReverted, result.OutputData); } } @@ -152,7 +126,7 @@ private class EstimateGasTxExecutor(IBlockchainBridge blockchainBridge, IBlockFi { CallOutput result = _blockchainBridge.EstimateGas(header, tx, _errorMargin, stateOverride, token); - return CreateResultWrapper(result.InputError, result.Error, result.InputError || result.Error is not null ? null : (UInt256)result.GasSpent); + return CreateResultWrapper(result.InputError, result.Error, result.InputError || result.Error is not null ? null : (UInt256)result.GasSpent, result.ExecutionReverted, result.OutputData); } } @@ -165,9 +139,15 @@ private class CreateAccessListTxExecutor(IBlockchainBridge blockchainBridge, IBl var rpcAccessListResult = new AccessListResultForRpc( accessList: AccessListForRpc.FromAccessList(result.AccessList ?? tx.AccessList), - gasUsed: GetResultGas(tx, result)); + gasUsed: GetResultGas(tx, result), + result.Error); + + if (result.InputError) + { + return ResultWrapper.Fail(result.Error!, ErrorCodes.InvalidInput); + } - return CreateResultWrapper(result.InputError, result.Error, rpcAccessListResult); + return ResultWrapper.Success(rpcAccessListResult); } private static UInt256 GetResultGas(Transaction transaction, CallOutput result) diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index 6fd031174745..bce797084c92 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -1,15 +1,6 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security; -using System.Text; -using System.Text.Json; -using System.Text.Json.Nodes; -using System.Threading; -using System.Threading.Tasks; using Nethermind.Blockchain.Filters; using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; @@ -18,6 +9,7 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; +using Nethermind.Db.LogIndex; using Nethermind.Evm; using Nethermind.Evm.Precompiles; using Nethermind.Facade; @@ -41,6 +33,15 @@ using Nethermind.Trie; using Nethermind.TxPool; using Nethermind.Wallet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security; +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Threading; +using System.Threading.Tasks; using Block = Nethermind.Core.Block; using BlockHeader = Nethermind.Core.BlockHeader; using ResultType = Nethermind.Core.ResultType; @@ -65,8 +66,10 @@ public partial class EthRpcModule( IFeeHistoryOracle feeHistoryOracle, IProtocolsManager protocolsManager, IForkInfo forkInfo, + ILogIndexConfig? logIndexConfig, 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 +145,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) { @@ -293,7 +294,12 @@ public ResultWrapper eth_sign(Address addressData, byte[] message) public virtual Task> eth_sendTransaction(TransactionForRpc rpcTx) { - Transaction tx = rpcTx.ToTransaction(); + Result txResult = rpcTx.ToTransaction(validateUserInput: true); + if (!txResult.Success(out Transaction tx, out string error)) + { + return Task.FromResult(ResultWrapper.Fail(error, ErrorCodes.InvalidInput)); + } + tx.ChainId = _blockchainBridge.GetChainId(); UInt256? nonce = rpcTx is LegacyTransactionForRpc legacy ? legacy.Nonce : null; @@ -387,26 +393,29 @@ public ResultWrapper eth_getBlockByNumber(BlockParameter blockParam public virtual ResultWrapper eth_getTransactionByHash(Hash256 transactionHash) { - (TxReceipt? receipt, Transaction? transaction, UInt256? baseFee) = _blockchainBridge.GetTransaction(transactionHash, checkTxnPool: true); - if (transaction is null) + if (!_blockchainBridge.TryGetTransaction(transactionHash, out TransactionLookupResult? transactionResult, checkTxnPool: true)) { return ResultWrapper.Success(null); } - RecoverTxSenderIfNeeded(transaction); - TransactionForRpc transactionModel = TransactionForRpc.FromTransaction(transaction, receipt?.BlockHash, receipt?.BlockNumber, receipt?.Index, baseFee, _specProvider.ChainId); + RecoverTxSenderIfNeeded(transactionResult.Value.Transaction); + TransactionForRpcContext extraData = transactionResult.Value.ExtraData; + TransactionForRpc transactionModel = TransactionForRpc.FromTransaction( + transaction: transactionResult.Value.Transaction, + extraData: extraData); if (_logger.IsTrace) _logger.Trace($"eth_getTransactionByHash request {transactionHash}, result: {transactionModel.Hash}"); return ResultWrapper.Success(transactionModel); } public ResultWrapper eth_getRawTransactionByHash(Hash256 transactionHash) { - Transaction? transaction = _blockchainBridge.GetTransaction(transactionHash, checkTxnPool: true).Transaction; - if (transaction is null) + if (!_blockchainBridge.TryGetTransaction(transactionHash, out TransactionLookupResult? transactionResult, checkTxnPool: true)) { return ResultWrapper.Success(null); } + Transaction transaction = transactionResult.Value.Transaction; + RlpBehaviors encodingSettings = RlpBehaviors.SkipTypedWrapping | (transaction.IsInMempoolForm() ? RlpBehaviors.InMempoolForm : RlpBehaviors.None); using NettyRlpStream stream = TxDecoder.Instance.EncodeToNewNettyStream(transaction, encodingSettings); @@ -421,7 +430,7 @@ public ResultWrapper eth_pendingTransactions() { Transaction transaction = transactions[i]; RecoverTxSenderIfNeeded(transaction); - transactionsModels[i] = TransactionForRpc.FromTransaction(transaction, chainId: _specProvider.ChainId); + transactionsModels[i] = TransactionForRpc.FromTransaction(transaction, new(_specProvider.ChainId)); transactionsModels[i].BlockHash = Keccak.Zero; } @@ -460,7 +469,15 @@ public ResultWrapper eth_getTransactionByBlockNumberAndIndex( Transaction transaction = block.Transactions[(int)positionIndex]; RecoverTxSenderIfNeeded(transaction); - TransactionForRpc transactionModel = TransactionForRpc.FromTransaction(transaction, block.Hash, block.Number, (int)positionIndex, block.BaseFeePerGas, _specProvider.ChainId); + TransactionForRpcContext extraData = new( + chainId: _specProvider.ChainId, + blockHash: block.Hash, + blockNumber: block.Number, + txIndex: (int)positionIndex, + blockTimestamp: block.Timestamp, + baseFee: block.BaseFeePerGas, + receipt: null); + TransactionForRpc transactionModel = TransactionForRpc.FromTransaction(transaction, extraData); return ResultWrapper.Success(transactionModel); } @@ -527,7 +544,7 @@ public ResultWrapper eth_getTransactionByBlockNumberAndIndex( public ResultWrapper> eth_getFilterChanges(UInt256 filterId) { - int id = (int)filterId; + int id = filterId <= int.MaxValue ? (int)filterId : -1; FilterType filterType = _blockchainBridge.GetFilterType(id); switch (filterType) { @@ -535,19 +552,19 @@ public ResultWrapper> eth_getFilterChanges(UInt256 filterId) { return _blockchainBridge.FilterExists(id) ? ResultWrapper>.Success(_blockchainBridge.GetBlockFilterChanges(id)) - : ResultWrapper>.Fail($"Filter not found", ErrorCodes.InvalidInput); + : ResultWrapper>.Fail("Filter not found", ErrorCodes.InvalidInput); } case FilterType.PendingTransactionFilter: { return _blockchainBridge.FilterExists(id) ? ResultWrapper>.Success(_blockchainBridge.GetPendingTransactionFilterChanges(id)) - : ResultWrapper>.Fail($"Filter not found", ErrorCodes.InvalidInput); + : ResultWrapper>.Fail("Filter not found", ErrorCodes.InvalidInput); } case FilterType.LogFilter: { return _blockchainBridge.FilterExists(id) ? ResultWrapper>.Success(_blockchainBridge.GetLogFilterChanges(id).ToArray()) - : ResultWrapper>.Fail($"Filter not found", ErrorCodes.InvalidInput); + : ResultWrapper>.Fail("Filter not found", ErrorCodes.InvalidInput); } default: { @@ -570,10 +587,8 @@ public ResultWrapper> eth_getFilterLogs(UInt256 filterId) timeout.Dispose(); return ResultWrapper>.Fail($"Filter with id: {filterId} does not exist."); } - else - { - return ResultWrapper>.Success(GetLogs(filterLogs, timeout)); - } + + return ResultWrapper>.Success(GetLogs(filterLogs, timeout)); } catch (ResourceNotFoundException exception) { @@ -584,52 +599,58 @@ 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 { LogFilter logFilter = _blockchainBridge.GetFilter(fromBlock, toBlock, filter.Address, filter.Topics); + // ReSharper disable once ConditionIsAlwaysTrueOrFalse - can be null in tests + if (logFilter is not null && filter is not null) + logFilter.UseIndex = filter.UseIndex; + IEnumerable filterLogs = _blockchainBridge.GetLogs(logFilter, fromBlockHeader, toBlockHeader, cancellationToken); ArrayPoolList logs = new(_rpcConfig.MaxLogsPerResponse); @@ -646,6 +667,9 @@ public ResultWrapper> eth_getLogs(Filter filter) } } + if (logIndexConfig?.VerifyRpcResponse is true && logFilter.UseIndex) + VerifyLogsResponse(logs, logFilter, fromBlockHeader, toBlockHeader, cancellationToken); + return ResultWrapper>.Success(logs); } catch (ResourceNotFoundException exception) @@ -655,36 +679,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 +705,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()); } @@ -842,4 +848,28 @@ public ResultWrapper eth_config() private CancellationTokenSource BuildTimeoutCancellationTokenSource() => _rpcConfig.BuildTimeoutCancellationToken(); + + private void VerifyLogsResponse(IList response, LogFilter filter, BlockHeader from, BlockHeader to, CancellationToken cancellation) + { + filter.UseIndex = false; + IEnumerable? expectedResponse = _blockchainBridge.GetLogs(filter, from, to, cancellation); + + using IEnumerator expectedEnum = expectedResponse.GetEnumerator(); + + var i = -1; + while (++i < response.Count | expectedEnum.MoveNext()) + { + FilterLog? actual = i < response.Count ? response[i] : null; + FilterLog? expected = expectedEnum.Current; + + if ((actual?.BlockNumber, actual?.LogIndex) != (expected?.BlockNumber, expected?.LogIndex)) + { + throw new LogIndexStateException( + $"Incorrect result from log index at position #{i}. " + + $"Expected: block {expected?.BlockNumber}, log #{expected?.LogIndex}. " + + $"Actual: block {actual?.BlockNumber}, log #{actual?.LogIndex}." + ); + } + } + } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/ExecutorBase.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/ExecutorBase.cs index a6721f4b0add..ba9f621e5cc3 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/ExecutorBase.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/ExecutorBase.cs @@ -10,18 +10,14 @@ namespace Nethermind.JsonRpc.Modules.Eth; -public abstract class ExecutorBase +public abstract class ExecutorBase( + IBlockchainBridge blockchainBridge, + IBlockFinder blockFinder, + IJsonRpcConfig rpcConfig) { - protected readonly IBlockchainBridge _blockchainBridge; - protected readonly IBlockFinder _blockFinder; - protected readonly IJsonRpcConfig _rpcConfig; - - protected ExecutorBase(IBlockchainBridge blockchainBridge, IBlockFinder blockFinder, IJsonRpcConfig rpcConfig) - { - _blockchainBridge = blockchainBridge; - _blockFinder = blockFinder; - _rpcConfig = rpcConfig; - } + protected readonly IBlockchainBridge _blockchainBridge = blockchainBridge; + protected readonly IBlockFinder _blockFinder = blockFinder; + protected readonly IJsonRpcConfig _rpcConfig = rpcConfig; public virtual ResultWrapper Execute( TRequest call, @@ -32,32 +28,20 @@ public virtual ResultWrapper Execute( searchResult ??= _blockFinder.SearchForHeader(blockParameter); if (searchResult.Value.IsError) return ResultWrapper.Fail(searchResult.Value); - BlockHeader header = searchResult.Value.Object; - if (!_blockchainBridge.HasStateForBlock(header!)) - return ResultWrapper.Fail($"No state available for block {header.ToString(BlockHeader.Format.FullHashAndNumber)}", - ErrorCodes.ResourceUnavailable); - - using CancellationTokenSource timeout = _rpcConfig.BuildTimeoutCancellationToken(); - string? error = Validate(call); - if (error is not null) + BlockHeader header = searchResult.Value.Object!; + if (!_blockchainBridge.HasStateForBlock(header)) { - return ResultWrapper.Fail(error, ErrorCodes.InvalidInput); + return ResultWrapper.Fail($"No state available for block {header.ToString(BlockHeader.Format.FullHashAndNumber)}", ErrorCodes.ResourceUnavailable); } - TProcessing toProcess = Prepare(call); - return Execute(header.Clone(), toProcess, stateOverride, timeout.Token); - } - protected virtual string? Validate(TRequest call) - { - return null; + using CancellationTokenSource timeout = _rpcConfig.BuildTimeoutCancellationToken(); + Result prepareResult = Prepare(call); + return !prepareResult.Success(out TProcessing? data, out string? error) + ? ResultWrapper.Fail(error, ErrorCodes.InvalidInput) + : Execute(header.Clone(), data, stateOverride, timeout.Token); } - protected abstract TProcessing Prepare(TRequest call); + protected abstract Result Prepare(TRequest call); protected abstract ResultWrapper Execute(BlockHeader header, TProcessing tx, Dictionary? stateOverride, CancellationToken token); - - protected ResultWrapper? TryGetInputError(CallOutput result) - { - return result.InputError ? ResultWrapper.Fail(result.Error!, ErrorCodes.InvalidInput) : null; - } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/FeeHistory/FeeHistoryOracle.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/FeeHistory/FeeHistoryOracle.cs index d21fb76a783f..4f0265261150 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/FeeHistory/FeeHistoryOracle.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/FeeHistory/FeeHistoryOracle.cs @@ -204,8 +204,7 @@ public ResultWrapper GetFeeHistory( TryRunCleanup(); - return ResultWrapper.Success(new(oldestBlockNumber, baseFeePerGas, - gasUsedRatio, baseFeePerBlobGas, blobGasUsedRatio, rewards)); + return ResultWrapper.Success(new(oldestBlockNumber, baseFeePerGas, gasUsedRatio, baseFeePerBlobGas, blobGasUsedRatio, rewards)); } private void TryRunCleanup() @@ -258,7 +257,7 @@ static IEnumerable CalculateGasUsed(TxReceipt[] txReceipts) TxReceipt[] receipts = _receiptStorage.Get(block, false); Transaction[] txs = block.Transactions; - using ArrayPoolList gasUsed = new(txs.Length, receipts.Length == block.Transactions.Length + using ArrayPoolListRef gasUsed = new(txs.Length, receipts.Length == block.Transactions.Length ? CalculateGasUsed(receipts) // If no receipts available, approximate on GasLimit // We could just go with null here too and just don't return percentiles diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs index 0b3a0284bdb8..3f79cbd33cc3 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs @@ -1,28 +1,31 @@ -// 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; } = BlockParameter.Latest; - public BlockParameter? FromBlock { get; set; } + public BlockParameter ToBlock { get; set; } = BlockParameter.Latest; - public BlockParameter? ToBlock { get; set; } + public IEnumerable? Topics { get; set; } - public IEnumerable? Topics { get; set; } + public bool UseIndex { get; set; } = true; public void ReadJson(JsonElement filter, JsonSerializerOptions options) { JsonDocument doc = null; - string blockHash = null; try { if (filter.ValueKind == JsonValueKind.String) @@ -31,33 +34,48 @@ 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 + + if (filter.TryGetProperty("useIndex"u8, out JsonElement useIndex)) { - Topics = null; + UseIndex = useIndex.ValueKind switch + { + JsonValueKind.False => false, + JsonValueKind.True => true, + _ => UseIndex + }; } } finally @@ -66,27 +84,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 5dba25f2d096..ff517e18c4fd 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 3e829e010071..21ef06a06685 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using System.Text.Json.Nodes; using System.Threading.Tasks; @@ -95,7 +94,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 +280,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 2a9cb5b7922d..c64a3ef6ecb8 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; @@ -10,6 +9,7 @@ using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Evm; +using Nethermind.Evm.TransactionProcessing; using Nethermind.Facade; using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.Facade.Proxy.Models.Simulate; @@ -17,86 +17,74 @@ namespace Nethermind.JsonRpc.Modules.Eth; -public class SimulateTxExecutor(IBlockchainBridge blockchainBridge, IBlockFinder blockFinder, IJsonRpcConfig rpcConfig, ISimulateBlockTracerFactory simulateBlockTracerFactory, ulong? secondsPerSlot = null) +public class SimulateTxExecutor( + IBlockchainBridge blockchainBridge, + IBlockFinder blockFinder, + IJsonRpcConfig rpcConfig, + ISimulateBlockTracerFactory simulateBlockTracerFactory, + ulong? secondsPerSlot = null) : ExecutorBase>, SimulatePayload, SimulatePayload>(blockchainBridge, blockFinder, rpcConfig) { private readonly long _blocksLimit = rpcConfig.MaxSimulateBlocksCap ?? 256; - private long _gasCapBudget = rpcConfig.GasCap ?? long.MaxValue; private readonly ulong _secondsPerSlot = secondsPerSlot ?? new BlocksConfig().SecondsPerSlot; - protected override SimulatePayload Prepare(SimulatePayload call) + protected override Result> Prepare(SimulatePayload call) { - SimulatePayload result = new() + List>? blockStateCalls = null; + + if (call.BlockStateCalls is not null) { - TraceTransfers = call.TraceTransfers, - Validation = call.Validation, - ReturnFullTransactionObjects = call.ReturnFullTransactionObjects, - BlockStateCalls = call.BlockStateCalls?.Select(blockStateCall => + blockStateCalls = new List>(call.BlockStateCalls.Count); + + foreach (BlockStateCall blockStateCall in call.BlockStateCalls) { - if (blockStateCall.BlockOverrides?.GasLimit is not null) - { - blockStateCall.BlockOverrides.GasLimit = (ulong)Math.Min((long)blockStateCall.BlockOverrides.GasLimit!.Value, _gasCapBudget); - } + TransactionWithSourceDetails[]? calls = null; - return new BlockStateCall + if (blockStateCall.Calls is not null) { - BlockOverrides = blockStateCall.BlockOverrides, - StateOverrides = blockStateCall.StateOverrides, - Calls = blockStateCall.Calls?.Select(callTransactionModel => + calls = new TransactionWithSourceDetails[blockStateCall.Calls.Length]; + + for (int i = 0; i < blockStateCall.Calls.Length; i++) { - callTransactionModel = UpdateTxType(callTransactionModel); - LegacyTransactionForRpc asLegacy = callTransactionModel as LegacyTransactionForRpc; + var callTransactionModel = blockStateCall.Calls[i]; + LegacyTransactionForRpc? asLegacy = callTransactionModel as LegacyTransactionForRpc; bool hadGasLimitInRequest = asLegacy?.Gas is not null; bool hadNonceInRequest = asLegacy?.Nonce is not null; - asLegacy!.EnsureDefaults(_gasCapBudget); - _gasCapBudget -= asLegacy.Gas!.Value; - _gasCapBudget = Math.Max(0, _gasCapBudget); - Transaction tx = callTransactionModel.ToTransaction(); + Result txResult = callTransactionModel.ToTransaction(validateUserInput: call.Validation); + if (!txResult.Success(out Transaction? tx, out string? error)) + { + return error; + } - // The RPC set SystemUser as default, but we want to set it to zero to follow hive test. - if (tx.SenderAddress == Address.SystemUser) tx.SenderAddress = Address.Zero; tx.ChainId = _blockchainBridge.GetChainId(); - TransactionWithSourceDetails? result = new() + calls[i] = new TransactionWithSourceDetails { HadGasLimitInRequest = hadGasLimitInRequest, HadNonceInRequest = hadNonceInRequest, Transaction = tx }; + } + } - return result; - }).ToArray() - }; - }).ToList() - }; - - return result; - } - - private static TransactionForRpc UpdateTxType(TransactionForRpc rpcTransaction) - { - // TODO: This is a bit messy since we're changing the transaction type - if (rpcTransaction is LegacyTransactionForRpc legacy && rpcTransaction is not EIP1559TransactionForRpc) - { - rpcTransaction = new EIP1559TransactionForRpc - { - Nonce = legacy.Nonce, - To = legacy.To, - From = legacy.From, - Gas = legacy.Gas, - Value = legacy.Value, - Input = legacy.Input, - GasPrice = legacy.GasPrice, - ChainId = legacy.ChainId, - V = legacy.V, - R = legacy.R, - S = legacy.S, - }; + blockStateCalls.Add(new BlockStateCall + { + BlockOverrides = blockStateCall.BlockOverrides, + StateOverrides = blockStateCall.StateOverrides, + Calls = calls + }); + } } - return rpcTransaction; + return new SimulatePayload + { + TraceTransfers = call.TraceTransfers, + Validation = call.Validation, + ReturnFullTransactionObjects = call.ReturnFullTransactionObjects, + BlockStateCalls = blockStateCalls + }; } public override ResultWrapper>> Execute( @@ -121,7 +109,7 @@ public override ResultWrapper>> Execut if (!_blockchainBridge.HasStateForBlock(header!)) return ResultWrapper>>.Fail($"No state available for block {header.ToString(BlockHeader.Format.FullHashAndNumber)}", - ErrorCodes.ResourceUnavailable); + ErrorCodes.ResourceNotFound); if (call.BlockStateCalls?.Count > _blocksLimit) return ResultWrapper>>.Fail( @@ -133,7 +121,7 @@ public override ResultWrapper>> Execut long lastBlockNumber = header.Number; ulong lastBlockTime = header.Timestamp; - using ArrayPoolList> completeBlockStateCalls = new(call.BlockStateCalls.Count); + using ArrayPoolListRef> completeBlockStateCalls = new(call.BlockStateCalls.Count); foreach (BlockStateCall? blockToSimulate in call.BlockStateCalls) { @@ -141,18 +129,14 @@ public override ResultWrapper>> Execut ulong givenNumber = blockToSimulate.BlockOverrides.Number ?? (ulong)lastBlockNumber + 1; if (givenNumber > long.MaxValue) - return ResultWrapper>>.Fail( - $"Block number too big {givenNumber}!", ErrorCodes.InvalidParams); + return ResultWrapper>>.Fail($"Block number too big {givenNumber}!", ErrorCodes.InvalidParams); if (givenNumber <= (ulong)lastBlockNumber) - return ResultWrapper>>.Fail( - $"Block number out of order {givenNumber} is <= than previous block number of {header.Number}!", ErrorCodes.InvalidInputBlocksOutOfOrder); + return ResultWrapper>>.Fail($"Block number out of order {givenNumber} is <= than previous block number of {header.Number}!", ErrorCodes.InvalidInputBlocksOutOfOrder); // if the no. of filler blocks are greater than maximum simulate blocks cap if (givenNumber - (ulong)lastBlockNumber > (ulong)_blocksLimit) - return ResultWrapper>>.Fail( - $"too many blocks", - ErrorCodes.ClientLimitExceededError); + return ResultWrapper>>.Fail($"too many blocks", ErrorCodes.ClientLimitExceededError); for (ulong fillBlockNumber = (ulong)lastBlockNumber + 1; fillBlockNumber < givenNumber; fillBlockNumber++) { @@ -172,8 +156,7 @@ public override ResultWrapper>> Execut { if (blockToSimulate.BlockOverrides.Time <= lastBlockTime) { - return ResultWrapper>>.Fail( - $"Block timestamp out of order {blockToSimulate.BlockOverrides.Time} is <= than given base timestamp of {lastBlockTime}!", ErrorCodes.BlockTimestampNotIncreased); + return ResultWrapper>>.Fail($"Block timestamp out of order {blockToSimulate.BlockOverrides.Time} is <= than given base timestamp of {lastBlockTime}!", ErrorCodes.BlockTimestampNotIncreased); } lastBlockTime = (ulong)blockToSimulate.BlockOverrides.Time; } @@ -190,8 +173,11 @@ public override ResultWrapper>> Execut } using CancellationTokenSource timeout = _rpcConfig.BuildTimeoutCancellationToken(); - SimulatePayload toProcess = Prepare(call); - return Execute(header.Clone(), toProcess, stateOverride, timeout.Token); + + Result> prepareResult = Prepare(call); + return !prepareResult.Success(out SimulatePayload? data, out string? error) + ? ResultWrapper>>.Fail(error, ErrorCodes.InvalidInput) + : Execute(header.Clone(), data, stateOverride, timeout.Token); } protected override ResultWrapper>> Execute( @@ -208,30 +194,60 @@ protected override ResultWrapper>> Exe { foreach (SimulateCallResult? call in result.Calls) { - if (call is { Error: not null } simulateResult && !string.IsNullOrEmpty(simulateResult.Error.Message)) + if (call is { Error: not null } && !string.IsNullOrEmpty(call.Error.Message)) { - simulateResult.Error.Code = ErrorCodes.ExecutionError; + EvmExceptionType exception = call.Error.EvmException; + call.Error.Code = MapEvmExceptionType(exception); + if (exception != EvmExceptionType.Revert) + { + call.Error.Message = call.Error.EvmException.GetEvmExceptionDescription(); + call.Error.Data = null; + } } } } } - if (results.Error is not null) + int? errorCode = results.TransactionResult.TransactionExecuted + ? null + : MapSimulateErrorCode(results.TransactionResult); + if (results.IsInvalidInput) errorCode = ErrorCodes.Default; + return results.Error is null + ? ResultWrapper>>.Success([.. results.Items]) + : errorCode is not null + ? ResultWrapper>>.Fail(results.Error!, errorCode.Value) + : ResultWrapper>>.Fail(results.Error); + } + + private static int MapSimulateErrorCode(TransactionResult txResult) + { + if (txResult.Error != TransactionResult.ErrorType.None) { - results.ErrorCode = results.Error switch + return txResult.Error switch { - var e when e.Contains("invalid transaction", StringComparison.OrdinalIgnoreCase) => ErrorCodes.InvalidTransaction, - var e when e.Contains("InsufficientBalanceException", StringComparison.OrdinalIgnoreCase) => ErrorCodes.InvalidTransaction, - var e when e.Contains("InvalidBlockException", StringComparison.OrdinalIgnoreCase) => ErrorCodes.InvalidParams, - var e when e.Contains("below intrinsic gas", StringComparison.OrdinalIgnoreCase) => ErrorCodes.InsufficientIntrinsicGas, - _ => results.ErrorCode + TransactionResult.ErrorType.BlockGasLimitExceeded => ErrorCodes.BlockGasLimitReached, + TransactionResult.ErrorType.GasLimitBelowIntrinsicGas => ErrorCodes.IntrinsicGas, + TransactionResult.ErrorType.InsufficientMaxFeePerGasForSenderBalance + or TransactionResult.ErrorType.InsufficientSenderBalance => ErrorCodes.InsufficientFunds, + TransactionResult.ErrorType.MalformedTransaction => ErrorCodes.InternalError, + TransactionResult.ErrorType.MinerPremiumNegative => ErrorCodes.InvalidParams, + TransactionResult.ErrorType.NonceOverflow => ErrorCodes.InternalError, + TransactionResult.ErrorType.SenderHasDeployedCode => ErrorCodes.InvalidParams, + TransactionResult.ErrorType.SenderNotSpecified => ErrorCodes.InternalError, + TransactionResult.ErrorType.TransactionSizeOverMaxInitCodeSize => ErrorCodes.MaxInitCodeSizeExceeded, + TransactionResult.ErrorType.TransactionNonceTooHigh => ErrorCodes.InternalError, + TransactionResult.ErrorType.TransactionNonceTooLow => ErrorCodes.InternalError, + _ => ErrorCodes.InternalError }; } - return results.Error is null - ? ResultWrapper>>.Success([.. results.Items]) - : results.ErrorCode is not null - ? ResultWrapper>>.Fail(results.Error!, results.ErrorCode!.Value) - : ResultWrapper>>.Fail(results.Error); + return MapEvmExceptionType(txResult.EvmExceptionType); } + + + private static int MapEvmExceptionType(EvmExceptionType type) => type switch + { + EvmExceptionType.Revert => ErrorCodes.ExecutionReverted, + _ => ErrorCodes.VMError + }; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/ILogIndexRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/ILogIndexRpcModule.cs new file mode 100644 index 000000000000..9a62dd485a05 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/ILogIndexRpcModule.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.JsonRpc.Modules.Eth; + +namespace Nethermind.JsonRpc.Modules.LogIndex; + +[RpcModule(ModuleType.LogIndex)] +public interface ILogIndexRpcModule : IRpcModule +{ + [JsonRpcMethod(Description = "Retrieves log index block number for the given filter.", IsImplemented = true, IsSharable = true)] + ResultWrapper> logIndex_blockNumbers([JsonRpcParameter] Filter filter); + + [JsonRpcMethod(Description = "Retrieves log index status.", IsImplemented = true, IsSharable = true)] + ResultWrapper logIndex_status(); +} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexRpcModule.cs new file mode 100644 index 000000000000..94c4a48272ba --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexRpcModule.cs @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Blockchain.Filters; +using Nethermind.Blockchain.Find; +using Nethermind.Db.LogIndex; +using Nethermind.Facade; +using Nethermind.Facade.Filters; +using Nethermind.Facade.Find; +using Nethermind.JsonRpc.Modules.Eth; + +namespace Nethermind.JsonRpc.Modules.LogIndex; + +public class LogIndexRpcModule(ILogIndexStorage storage, ILogIndexBuilder builder, IBlockFinder blockFinder, IBlockchainBridge blockchainBridge) + : ILogIndexRpcModule +{ + public ResultWrapper> logIndex_blockNumbers(Filter filter) + { + LogFilter logFilter = blockchainBridge.GetFilter(filter.FromBlock, filter.ToBlock, filter.Address, filter.Topics); + + if (GetBlockNumber(logFilter.FromBlock) is not { } from) + return ResultWrapper>.Fail($"Block {logFilter.FromBlock} is not found.", ErrorCodes.UnknownBlockError); + + if (GetBlockNumber(logFilter.ToBlock) is not { } to) + return ResultWrapper>.Fail($"Block {logFilter.ToBlock} is not found.", ErrorCodes.UnknownBlockError); + + return ResultWrapper>.Success(storage.EnumerateBlockNumbersFor(logFilter, from, to)); + } + + public ResultWrapper logIndex_status() + { + return ResultWrapper.Success(new() + { + Current = new() + { + FromBlock = storage.MinBlockNumber, + ToBlock = storage.MaxBlockNumber + }, + Target = new() + { + FromBlock = builder.MinTargetBlockNumber, + ToBlock = builder.MaxTargetBlockNumber + }, + IsRunning = builder.IsRunning, + LastUpdate = builder.LastUpdate, + LastError = builder.LastError?.ToString(), + DbSize = storage.GetDbSize() + }); + } + + private long? GetBlockNumber(BlockParameter parameter) => + parameter.BlockNumber ?? blockFinder.FindBlock(parameter)?.Number; +} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexStatus.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexStatus.cs new file mode 100644 index 000000000000..47a7d1b10180 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexStatus.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.JsonRpc.Modules.LogIndex; + +public class LogIndexStatus +{ + public readonly record struct Range(int? FromBlock, int? ToBlock); + + public required Range Current { get; init; } + public required Range Target { get; init; } + public bool IsRunning { get; init; } + public DateTimeOffset? LastUpdate { get; init; } + public string? LastError { get; init; } + public required string DbSize { get; init; } +} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs index a93b8fdc203f..3dc4ec0e2707 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs @@ -14,6 +14,7 @@ public static class ModuleType public const string Debug = nameof(Debug); public const string Erc20 = nameof(Erc20); public const string Eth = nameof(Eth); + public const string LogIndex = nameof(LogIndex); public const string Evm = nameof(Evm); public const string Flashbots = nameof(Flashbots); public const string Net = nameof(Net); @@ -25,7 +26,6 @@ public static class ModuleType public const string Trace = nameof(Trace); public const string TxPool = nameof(TxPool); public const string Web3 = nameof(Web3); - public const string Vault = nameof(Vault); public const string Deposit = nameof(Deposit); public const string Health = nameof(Health); public const string Rpc = nameof(Rpc); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityRpcModule.cs index 1cfecff004d3..f53bdefc80ea 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityRpcModule.cs @@ -100,8 +100,8 @@ public ResultWrapper parity_clearEngineSigner() public ResultWrapper parity_netPeers() { ParityNetPeers parityNetPeers = new(); - parityNetPeers.Active = _peerManager.ActivePeers.Count; - parityNetPeers.Connected = _peerManager.ConnectedPeers.Count; + parityNetPeers.Active = _peerManager.ActivePeersCount; + parityNetPeers.Connected = _peerManager.ConnectedPeersCount; parityNetPeers.Max = _peerManager.MaxActivePeers; parityNetPeers.Peers = _peerManager.ActivePeers.Select(static p => new PeerInfo(p)).ToArray(); return ResultWrapper.Success(parityNetPeers); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityTransaction.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityTransaction.cs index 5aeb8b1d20a9..5d31454d1938 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 cb6350d5e1df..283610643ac6 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Personal/IPersonalRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Personal/IPersonalRpcModule.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Facade.Eth.RpcTransaction; @@ -34,7 +33,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/ProofModuleFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofModuleFactory.cs index 8108eb149156..d4c14e7ba152 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofModuleFactory.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofModuleFactory.cs @@ -10,8 +10,6 @@ using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Container; -using Nethermind.Core.Crypto; -using Nethermind.Evm.State; using Nethermind.Evm.TransactionProcessing; using Nethermind.State.OverridableEnv; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofRpcModule.cs index 0e558150ce95..191b396206c2 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofRpcModule.cs @@ -1,10 +1,8 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; using Nethermind.Blockchain; using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; @@ -13,12 +11,12 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Crypto; +using Nethermind.Facade.Eth; using Nethermind.Evm; using Nethermind.Blockchain.Tracing; using Nethermind.Blockchain.Tracing.Proofs; using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.JsonRpc.Data; -using Nethermind.Logging; using Nethermind.Serialization.Rlp; using Nethermind.State.OverridableEnv; using Nethermind.State.Proofs; @@ -61,14 +59,19 @@ public ResultWrapper proof_call(TransactionForRpc tx, Block { TxRoot = Keccak.EmptyTreeHash, ReceiptsRoot = Keccak.EmptyTreeHash, - Author = Address.SystemUser + Author = Address.Zero }; callHeader.TotalDifficulty = sourceHeader.TotalDifficulty + callHeader.Difficulty; callHeader.Hash = callHeader.CalculateHash(); - Transaction transaction = tx.ToTransaction(); - transaction.SenderAddress ??= Address.SystemUser; + Result txResult = tx.ToTransaction(validateUserInput: true); + if (!txResult.Success(out Transaction? transaction, out string? error)) + { + return ResultWrapper.Fail(error, ErrorCodes.InvalidInput); + } + + transaction.SenderAddress ??= Address.Zero; if (transaction.GasLimit == 0) { @@ -77,7 +80,7 @@ public ResultWrapper proof_call(TransactionForRpc tx, Block Block block = new(callHeader, new[] { transaction }, []); - ProofBlockTracer proofBlockTracer = new(null, transaction.SenderAddress == Address.SystemUser); + ProofBlockTracer proofBlockTracer = new(null, transaction.SenderAddress == Address.Zero); scope.Component.Trace(block, proofBlockTracer); CallResultWithProof callResultWithProof = new(); @@ -88,7 +91,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); } @@ -113,7 +116,15 @@ public ResultWrapper proof_getTransactionByHash(Hash Transaction transaction = txs[receipt.Index]; TransactionForRpcWithProof txWithProof = new(); - txWithProof.Transaction = TransactionForRpc.FromTransaction(transaction, block.Hash, block.Number, receipt.Index, block.BaseFeePerGas, specProvider.ChainId); + TransactionForRpcContext extraData = new( + chainId: specProvider.ChainId, + blockHash: block.Hash, + blockNumber: block.Number, + txIndex: receipt.Index, + blockTimestamp: block.Timestamp, + baseFee: block.BaseFeePerGas, + receipt: receipt); + txWithProof.Transaction = TransactionForRpc.FromTransaction(transaction, extraData); txWithProof.TxProof = BuildTxProofs(txs, specProvider.GetSpec(block.Header), receipt.Index); if (includeHeader) { @@ -165,7 +176,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 +185,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 5afc5f206636..4ea94824146e 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/NewPendingTransactionsSubscription.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/NewPendingTransactionsSubscription.cs index d0fafbb10c28..7176a763513f 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/NewPendingTransactionsSubscription.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/NewPendingTransactionsSubscription.cs @@ -4,6 +4,7 @@ using System; using Nethermind.Core.Specs; using Nethermind.Facade.Eth.RpcTransaction; +using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Logging; using Nethermind.TxPool; @@ -38,7 +39,7 @@ private void OnNewPending(object? sender, TxEventArgs e) ScheduleAction(async () => { using JsonRpcResult result = CreateSubscriptionMessage(_includeTransactions - ? TransactionForRpc.FromTransaction(e.Transaction, chainId: _specProvider.ChainId) + ? TransactionForRpc.FromTransaction(e.Transaction, new(_specProvider.ChainId)) : e.Transaction.Hash!); await JsonRpcDuplexClient.SendJsonRpcResult(result); if (_logger.IsTrace) _logger.Trace($"NewPendingTransactions subscription {Id} printed hash of NewPendingTransaction."); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerAddDropResponse.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerAddDropResponse.cs index 0a86ec1810dc..b18bc02d58ec 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerAddDropResponse.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerAddDropResponse.cs @@ -1,7 +1,6 @@ using System.Text.Json.Serialization; using Nethermind.JsonRpc.Modules.Admin; using Nethermind.Core.Crypto; -using Nethermind.Serialization.Json; namespace Nethermind.JsonRpc.Modules.Subscribe; @@ -19,7 +18,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 1147d50ff009..1fb71b945aa5 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerEventResponse.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerEventResponse.cs @@ -2,11 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Text.Json.Serialization; -using System.Runtime.CompilerServices; -using Nethermind.JsonRpc.Modules.Admin; -using Nethermind.Synchronization.Peers.AllocationStrategies; using Nethermind.Core.Crypto; -using Nethermind.Serialization.Json; namespace Nethermind.JsonRpc.Modules.Subscribe; @@ -14,7 +10,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 f5afa80c5035..bc2e53547aa8 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerMsgSendRecvResponse.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerMsgSendRecvResponse.cs @@ -2,8 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Text.Json.Serialization; -using System.Runtime.CompilerServices; -using Nethermind.JsonRpc.Modules.Admin; using System; namespace Nethermind.JsonRpc.Modules.Subscribe; @@ -15,9 +13,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/SubscriptionFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactory.cs index c1dfb8e22d8e..0df997ffb390 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactory.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactory.cs @@ -7,7 +7,6 @@ using Nethermind.Core.Extensions; using Nethermind.Serialization.Json; using System.Text.Json; -using Nethermind.Network; namespace Nethermind.JsonRpc.Modules.Subscribe; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactoryExtensions.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactoryExtensions.cs index 6e5aea38d92d..95a0980222bf 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactoryExtensions.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactoryExtensions.cs @@ -1,11 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // 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; @@ -17,6 +12,7 @@ using Nethermind.TxPool; namespace Nethermind.JsonRpc.Modules.Subscribe; + public static class SubscriptionFactoryExtensions { public static void RegisterNewHeadSubscription( @@ -36,7 +32,7 @@ ISpecProvider specProvider public static void RegisterLogsSubscription( this ISubscriptionFactory subscriptionFactory, IReceiptMonitor receiptMonitor, - IFilterStore? filterStore, + FilterStore? filterStore, IBlockTree? blockTree, ILogManager? logManager ) @@ -109,7 +105,7 @@ public static void RegisterStandardSubscriptions( ILogManager? logManager, ISpecProvider specProvider, IReceiptMonitor receiptMonitor, - IFilterStore? filterStore, + FilterStore? filterStore, ITxPool? txPool, IEthSyncingInfo ethSyncingInfo, IPeerPool? peerPool, @@ -130,7 +126,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/SubscriptionMethodName.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionMethodName.cs index 722e637f36c5..f3e74fca5ef5 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionMethodName.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionMethodName.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only namespace Nethermind.JsonRpc.Modules.Subscribe; + public static class SubscriptionMethodName { public const string EthSubscription = "eth_subscription"; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SyncingSubscription.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SyncingSubscription.cs index f750e8a40e08..ca80ac5543fe 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SyncingSubscription.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SyncingSubscription.cs @@ -2,8 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Text.Json.Serialization; - using Nethermind.Blockchain; using Nethermind.Core; using Nethermind.Facade.Eth; @@ -40,8 +38,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 +59,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/Modules/Trace/TraceRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceRpcModule.cs index a2d01e3562f0..8d4009085e06 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceRpcModule.cs @@ -24,7 +24,6 @@ using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Serialization.Rlp; -using Nethermind.State; namespace Nethermind.JsonRpc.Modules.Trace { @@ -41,7 +40,6 @@ public class TraceRpcModule( IOverridableEnv tracerEnv, IBlockFinder blockFinder, IJsonRpcConfig jsonRpcConfig, - IStateReader stateReader, IBlockchainBridge blockchainBridge, IBlocksConfig blocksConfig) : ITraceRpcModule @@ -60,9 +58,10 @@ public ResultWrapper trace_call(TransactionForRpc call, blockParameter ??= BlockParameter.Latest; call.EnsureDefaults(jsonRpcConfig.GasCap); - Transaction tx = call.ToTransaction(); - - return TraceTx(tx, traceTypes, blockParameter, stateOverride); + Result txResult = call.ToTransaction(validateUserInput: true); + return !txResult.Success(out Transaction? transaction, out string? error) + ? ResultWrapper.Fail(error, ErrorCodes.InvalidInput) + : TraceTx(transaction, traceTypes, blockParameter, stateOverride); } /// @@ -79,7 +78,7 @@ public ResultWrapper> trace_callMany(Transa } BlockHeader header = headerSearch.Object!; - if (!stateReader.HasStateForBlock(header)) + if (!blockchainBridge.HasStateForBlock(header)) { return GetStateFailureResult>(header); } @@ -89,7 +88,12 @@ public ResultWrapper> trace_callMany(Transa for (int i = 0; i < calls.Length; i++) { calls[i].Transaction.EnsureDefaults(jsonRpcConfig.GasCap); - Transaction tx = calls[i].Transaction.ToTransaction(); + Result txResult = calls[i].Transaction.ToTransaction(validateUserInput: true); + if (!txResult.Success(out Transaction? tx, out string? error)) + { + return ResultWrapper>.Fail(error, ErrorCodes.InvalidInput); + } + tx.Hash = new Hash256(new UInt256((ulong)i).ToValueHash()); ParityTraceTypes traceTypes = GetParityTypes(calls[i].TraceTypes); txs[i] = tx; @@ -154,7 +158,7 @@ public ResultWrapper trace_replayTransaction(Hash256 tx return ResultWrapper.Fail(parentSearch); } - if (!stateReader.HasStateForBlock(parentSearch.Object)) + if (!blockchainBridge.HasStateForBlock(parentSearch.Object)) { return GetStateFailureResult(parentSearch.Object); } @@ -181,7 +185,7 @@ public ResultWrapper> trace_replayBlockTran return ResultWrapper>.Fail(parentSearch); } - if (!stateReader.HasStateForBlock(parentSearch.Object)) + if (!blockchainBridge.HasStateForBlock(parentSearch.Object)) { return GetStateFailureResult>(parentSearch.Object); } @@ -211,7 +215,7 @@ public ResultWrapper> trace_filter(TraceFilt } Block block = blockSearch.Object; - if (!stateReader.HasStateForBlock(block?.Header)) + if (!blockchainBridge.HasStateForBlock(block?.Header)) { return GetStateFailureResult>(block.Header); } @@ -222,7 +226,7 @@ public ResultWrapper> trace_filter(TraceFilt return ResultWrapper>.Fail(parentSearch); } - if (!stateReader.HasStateForBlock(parentSearch.Object)) + if (!blockchainBridge.HasStateForBlock(parentSearch.Object)) { return GetStateFailureResult>(parentSearch.Object); } @@ -247,7 +251,7 @@ public ResultWrapper> trace_block(BlockParam Block block = blockSearch.Object!; - if (!stateReader.HasStateForBlock(block.Header)) + if (!blockchainBridge.HasStateForBlock(block.Header)) { return GetStateFailureResult>(block.Header); } @@ -257,7 +261,7 @@ public ResultWrapper> trace_block(BlockParam return ResultWrapper>.Fail(parentSearch); } - if (!stateReader.HasStateForBlock(parentSearch.Object)) + if (!blockchainBridge.HasStateForBlock(parentSearch.Object)) { return GetStateFailureResult>(parentSearch.Object); } @@ -317,7 +321,7 @@ public ResultWrapper> trace_transaction(Hash return ResultWrapper>.Fail(parentSearch); } - if (!stateReader.HasStateForBlock(parentSearch.Object)) + if (!blockchainBridge.HasStateForBlock(parentSearch.Object)) { return GetStateFailureResult>(parentSearch.Object); } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/TxPool/TransactionPoolContent.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/TxPool/TransactionPoolContent.cs index a4726b89477b..192a526ad2d1 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/TxPool/TransactionPoolContent.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/TxPool/TransactionPoolContent.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using Nethermind.Core; +using Nethermind.Facade.Eth; using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.TxPool; @@ -13,8 +14,9 @@ public class TxPoolContent { public TxPoolContent(TxPoolInfo info, ulong chainId) { - Pending = info.Pending.ToDictionary(k => k.Key, k => k.Value.ToDictionary(v => v.Key, v => TransactionForRpc.FromTransaction(v.Value, chainId: chainId))); - Queued = info.Queued.ToDictionary(k => k.Key, k => k.Value.ToDictionary(v => v.Key, v => TransactionForRpc.FromTransaction(v.Value, chainId: chainId))); + TransactionForRpcContext extraData = new(chainId); + Pending = info.Pending.ToDictionary(k => k.Key, k => k.Value.ToDictionary(v => v.Key, v => TransactionForRpc.FromTransaction(v.Value, extraData))); + Queued = info.Queued.ToDictionary(k => k.Key, k => k.Value.ToDictionary(v => v.Key, v => TransactionForRpc.FromTransaction(v.Value, extraData))); } public Dictionary> Pending { get; set; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/TxPool/TxPoolRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/TxPool/TxPoolRpcModule.cs index 67a833ccdd64..7d0eebd2b8f8 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/TxPool/TxPoolRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/TxPool/TxPoolRpcModule.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Nethermind.Core.Specs; using Nethermind.TxPool; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Web3/Web3RpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Web3/Web3RpcModule.cs index 6035edb1b4d8..60876fd75647 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Web3/Web3RpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Web3/Web3RpcModule.cs @@ -4,7 +4,6 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Logging; -using Nethermind.Network.Config; namespace Nethermind.JsonRpc.Modules.Web3; diff --git a/src/Nethermind/Nethermind.JsonRpc/Nethermind.JsonRpc.csproj b/src/Nethermind/Nethermind.JsonRpc/Nethermind.JsonRpc.csproj index 060791998f33..69b1bef297f2 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Nethermind.JsonRpc.csproj +++ b/src/Nethermind/Nethermind.JsonRpc/Nethermind.JsonRpc.csproj @@ -3,9 +3,7 @@ annotations True - - - + diff --git a/src/Nethermind/Nethermind.JsonRpc/PipeReaderExtensions.cs b/src/Nethermind/Nethermind.JsonRpc/PipeReaderExtensions.cs index 44aaa3774576..1f57e3b76117 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.JsonRpc/ResultWrapper.cs b/src/Nethermind/Nethermind.JsonRpc/ResultWrapper.cs index f728b274a69e..0f9d9a938e3d 100644 --- a/src/Nethermind/Nethermind.JsonRpc/ResultWrapper.cs +++ b/src/Nethermind/Nethermind.JsonRpc/ResultWrapper.cs @@ -17,7 +17,7 @@ public class ResultWrapper : IResultWrapper, IDisposable public int ErrorCode { get; init; } public bool IsTemporary { get; init; } - private ResultWrapper() + protected ResultWrapper() { } @@ -65,4 +65,34 @@ public void Dispose() } } } + + public class ResultWrapper : ResultWrapper, IResultWrapper, IDisposable + { + public TErrorData ErrorData { get; init; } + + object IResultWrapper.Data => ErrorData; + + + private ResultWrapper() + { + } + + public static ResultWrapper Fail(string error, int errorCode, TErrorData errorData) => + new() { ErrorCode = errorCode, ErrorData = errorData, Result = Result.Fail(error) }; + + public new static ResultWrapper Success(T data) => + new() { Data = data, ErrorData = default, Result = Result.Success }; + + public static implicit operator Task>(ResultWrapper resultWrapper) => Task.FromResult(resultWrapper); + + public new void Dispose() + { + base.Dispose(); + + if (ErrorData is IDisposable disposable) + { + disposable.Dispose(); + } + } + } } diff --git a/src/Nethermind/Nethermind.JsonRpc/RpcReport.cs b/src/Nethermind/Nethermind.JsonRpc/RpcReport.cs index f0d2112de526..483f0aca6446 100644 --- a/src/Nethermind/Nethermind.JsonRpc/RpcReport.cs +++ b/src/Nethermind/Nethermind.JsonRpc/RpcReport.cs @@ -3,19 +3,8 @@ namespace Nethermind.JsonRpc { - public readonly struct RpcReport + public readonly record struct RpcReport(string Method, long HandlingTimeMicroseconds, bool Success) { - public static readonly RpcReport Error = new RpcReport("# error #", 0, false); - - public RpcReport(string method, long handlingTimeMicroseconds, bool success) - { - Method = method; - HandlingTimeMicroseconds = handlingTimeMicroseconds; - Success = success; - } - - public string Method { get; } - public long HandlingTimeMicroseconds { get; } - public bool Success { get; } + public static readonly RpcReport Error = new("# error #", 0, false); } } diff --git a/src/Nethermind/Nethermind.KeyStore.Test/CryptoRandomTests.cs b/src/Nethermind/Nethermind.KeyStore.Test/CryptoRandomTests.cs new file mode 100644 index 000000000000..7c7bbd91f879 --- /dev/null +++ b/src/Nethermind/Nethermind.KeyStore.Test/CryptoRandomTests.cs @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Linq; +using Nethermind.Crypto; +using NUnit.Framework; + +namespace Nethermind.KeyStore.Test +{ + [TestFixture] + [Parallelizable(ParallelScope.All)] + public class CryptoRandomTests + { + [Test] + public void NextInt_ReturnsValueWithinBounds() + { + using CryptoRandom rng = new(); + + int[] bounds = { 1, 2, 10, 100, 1024 }; + foreach (int max in bounds) + { + int value = rng.NextInt(max); + Assert.That(value, Is.GreaterThanOrEqualTo(0).And.LessThan(max)); + } + } + + [Test] + public void NextInt_ZeroOrNegative_ReturnsZero() + { + using CryptoRandom rng = new(); + + Assert.That(rng.NextInt(0), Is.EqualTo(0)); + Assert.That(rng.NextInt(-1), Is.EqualTo(0)); + } + + [Test] + public void GenerateRandomBytes_FillsAndVariesBetweenCalls() + { + using CryptoRandom rng = new(); + + byte[] a = rng.GenerateRandomBytes(32); + byte[] b = rng.GenerateRandomBytes(32); + + // Ensure arrays are correct length and not all zeros + Assert.That(a.Length, Is.EqualTo(32)); + Assert.That(b.Length, Is.EqualTo(32)); + Assert.That(a.Any(x => x != 0), Is.True); + Assert.That(b.Any(x => x != 0), Is.True); + + // Extremely unlikely to fail: values should differ + Assert.That(a.SequenceEqual(b), Is.False); + } + + [Test] + public void GenerateRandomBytes_SpanFilled() + { + using CryptoRandom rng = new(); + + Span span = stackalloc byte[16]; + rng.GenerateRandomBytes(span); + + Assert.That(span.ToArray().Any(x => x != 0), Is.True); + } + } +} diff --git a/src/Nethermind/Nethermind.KeyStore/FileKeyStore.cs b/src/Nethermind/Nethermind.KeyStore/FileKeyStore.cs index c7b333ccd1da..919f115da749 100644 --- a/src/Nethermind/Nethermind.KeyStore/FileKeyStore.cs +++ b/src/Nethermind/Nethermind.KeyStore/FileKeyStore.cs @@ -140,8 +140,7 @@ public FileKeyStore( break; case "pbkdf2": int c = kdfParams.C.Value; - var deriveBytes = new Rfc2898DeriveBytes(passBytes, salt, c, HashAlgorithmName.SHA256); - derivedKey = deriveBytes.GetBytes(256); + derivedKey = Rfc2898DeriveBytes.Pbkdf2(passBytes, salt, c, HashAlgorithmName.SHA256, 256); break; default: return (null, Result.Fail($"Unsupported algorithm: {kdf}")); diff --git a/src/Nethermind/Nethermind.Logging.NLog/NLogManager.cs b/src/Nethermind/Nethermind.Logging.NLog/NLogManager.cs index 830e25fd2ab3..02060ce14faa 100644 --- a/src/Nethermind/Nethermind.Logging.NLog/NLogManager.cs +++ b/src/Nethermind/Nethermind.Logging.NLog/NLogManager.cs @@ -31,6 +31,7 @@ public NLogManager(string logFileName, string logDirectory = null, string logRul // Required since 'NLog.config' could change during runtime, we need to re-apply the configuration _logManagerOnConfigurationChanged = (sender, args) => Setup(logFileName, logDirectory, logRules); LogManager.ConfigurationChanged += _logManagerOnConfigurationChanged; + Static.LogManager = this; } private static void Setup(string logFileName, string logDirectory = null, string logRules = null) @@ -100,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); } } @@ -110,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/ILogManager.cs b/src/Nethermind/Nethermind.Logging/ILogManager.cs index 5a18863b6155..16d023e2957c 100644 --- a/src/Nethermind/Nethermind.Logging/ILogManager.cs +++ b/src/Nethermind/Nethermind.Logging/ILogManager.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Runtime.CompilerServices; namespace Nethermind.Logging diff --git a/src/Nethermind/Nethermind.Logging/ILogger.cs b/src/Nethermind/Nethermind.Logging/ILogger.cs index 6791b0a00af5..e226478b2cf1 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/LimboLogs.cs b/src/Nethermind/Nethermind.Logging/LimboLogs.cs index ca0307f5cc9b..c406f6133920 100644 --- a/src/Nethermind/Nethermind.Logging/LimboLogs.cs +++ b/src/Nethermind/Nethermind.Logging/LimboLogs.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Runtime.CompilerServices; using System.Threading; diff --git a/src/Nethermind/Nethermind.Logging/NoErrorLimboLogs.cs b/src/Nethermind/Nethermind.Logging/NoErrorLimboLogs.cs index 5617e0c13046..254a7f74c853 100644 --- a/src/Nethermind/Nethermind.Logging/NoErrorLimboLogs.cs +++ b/src/Nethermind/Nethermind.Logging/NoErrorLimboLogs.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Runtime.CompilerServices; using System.Threading; diff --git a/src/Nethermind/Nethermind.Logging/NullLogManager.cs b/src/Nethermind/Nethermind.Logging/NullLogManager.cs index e68045c5d99f..560fbd7a9661 100644 --- a/src/Nethermind/Nethermind.Logging/NullLogManager.cs +++ b/src/Nethermind/Nethermind.Logging/NullLogManager.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Runtime.CompilerServices; namespace Nethermind.Logging diff --git a/src/Nethermind/Nethermind.Logging/OneLoggerLogManager.cs b/src/Nethermind/Nethermind.Logging/OneLoggerLogManager.cs index bc92c5ca3dee..317c00230e24 100644 --- a/src/Nethermind/Nethermind.Logging/OneLoggerLogManager.cs +++ b/src/Nethermind/Nethermind.Logging/OneLoggerLogManager.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Runtime.CompilerServices; namespace Nethermind.Logging diff --git a/src/Nethermind/Nethermind.Logging/PathUtils.cs b/src/Nethermind/Nethermind.Logging/PathUtils.cs index 564d531032ad..e6ec6ac69dd4 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/Progress.cs b/src/Nethermind/Nethermind.Logging/Progress.cs index 167f4a16c6d1..0e8f223cb80f 100644 --- a/src/Nethermind/Nethermind.Logging/Progress.cs +++ b/src/Nethermind/Nethermind.Logging/Progress.cs @@ -4,6 +4,7 @@ using System; namespace Nethermind.Logging; + public static class Progress { private const int Width = 40; diff --git a/src/Nethermind/Nethermind.Logging/SimpleConsoleLogManager.cs b/src/Nethermind/Nethermind.Logging/SimpleConsoleLogManager.cs index db87f13ec7b0..14caf3dd07eb 100644 --- a/src/Nethermind/Nethermind.Logging/SimpleConsoleLogManager.cs +++ b/src/Nethermind/Nethermind.Logging/SimpleConsoleLogManager.cs @@ -1,28 +1,29 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Runtime.CompilerServices; 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.Logging/SimpleConsoleLogger.cs b/src/Nethermind/Nethermind.Logging/SimpleConsoleLogger.cs index c80b2196911e..6b732fccb1bf 100644 --- a/src/Nethermind/Nethermind.Logging/SimpleConsoleLogger.cs +++ b/src/Nethermind/Nethermind.Logging/SimpleConsoleLogger.cs @@ -34,7 +34,7 @@ public void Trace(string text) public void Error(string text, Exception ex = null) { - if (IsError) Console.Error.WriteLine(DateTime.Now.ToString(dateFormat) + text); + if (IsError) Console.Error.WriteLine(DateTime.Now.ToString(dateFormat) + text + (ex is not null ? " " + ex : string.Empty)); } private void WriteEntry(string text) diff --git a/src/Nethermind/Nethermind.Logging/StaticLoggerFactory.cs b/src/Nethermind/Nethermind.Logging/StaticLoggerFactory.cs new file mode 100644 index 000000000000..717ba5238073 --- /dev/null +++ b/src/Nethermind/Nethermind.Logging/StaticLoggerFactory.cs @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Logging; + +public static class Static +{ + public static ILogManager LogManager { get; set; } = LimboLogs.Instance; +} diff --git a/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs b/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs index 5a281b2097d5..07dd1df0b51b 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 fc3851d358c2..e7a563cc98bf 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 07e945056a22..56e2615e47bd 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 7e7d16409bcc..4d2cac616009 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa/AuRaPostMergeBlockProducerFactory.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa/AuRaPostMergeBlockProducerFactory.cs @@ -3,7 +3,6 @@ using Nethermind.Config; using Nethermind.Consensus; -using Nethermind.Consensus.Transactions; using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Logging; @@ -29,25 +28,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/BaseEngineModuleTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/BaseEngineModuleTests.cs index f82641c1263e..5f2d7df64574 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/BaseEngineModuleTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/BaseEngineModuleTests.cs @@ -10,15 +10,12 @@ using FluentAssertions; using Nethermind.Api; using Nethermind.Blockchain; -using Nethermind.Blockchain.BeaconBlockRoot; -using Nethermind.Blockchain.Blocks; using Nethermind.Blockchain.Synchronization; using Nethermind.Config; using Nethermind.Consensus; using Nethermind.Consensus.ExecutionRequests; using Nethermind.Consensus.Processing; using Nethermind.Consensus.Producers; -using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -30,7 +27,6 @@ using Nethermind.Core.Test.Modules; using Nethermind.Core.Timers; using Nethermind.Crypto; -using Nethermind.Evm.TransactionProcessing; using Nethermind.Int256; using Nethermind.JsonRpc; using Nethermind.Logging; @@ -40,7 +36,6 @@ using Nethermind.Specs; using Nethermind.Specs.ChainSpecStyle; using Nethermind.Specs.Forks; -using Nethermind.Evm.State; using Nethermind.Synchronization; using Nethermind.Synchronization.ParallelSync; using Nethermind.Synchronization.Peers; @@ -169,9 +164,9 @@ public Task WaitForImprovedBlock(Hash256? parentHash = null) { if (parentHash == null) { - return StoringBlockImprovementContextFactory!.WaitForImprovedBlockWithCondition(_cts.Token, b => true); + return StoringBlockImprovementContextFactory!.WaitForImprovedBlockWithCondition(CreateCancellationSource().Token, b => true); } - return StoringBlockImprovementContextFactory!.WaitForImprovedBlockWithCondition(_cts.Token, b => b.Header.ParentHash == parentHash); + return StoringBlockImprovementContextFactory!.WaitForImprovedBlockWithCondition(CreateCancellationSource().Token, b => b.Header.ParentHash == parentHash); } public IBeaconPivot BeaconPivot => Container.Resolve(); diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/ChainSpecBasedSpecProviderTests.TheMerge.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/ChainSpecBasedSpecProviderTests.TheMerge.cs index 19530e773108..5af17def1a2a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/ChainSpecBasedSpecProviderTests.TheMerge.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/ChainSpecBasedSpecProviderTests.TheMerge.cs @@ -35,7 +35,7 @@ public void Correctly_read_merge_block_number() [Test] public void Correctly_read_merge_parameters_from_file() { - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance); string path = Path.Combine(TestContext.CurrentContext.WorkDirectory, "Specs/test_spec.json"); var chainSpec = loader.LoadEmbeddedOrFromFile(path); @@ -69,7 +69,7 @@ public void Changing_spec_provider_in_dynamic_merge_transition() long expectedTerminalPoWBlock = 100; long newMergeBlock = 50; - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance); string path = Path.Combine(TestContext.CurrentContext.WorkDirectory, "Specs/test_spec.json"); var chainSpec = loader.LoadEmbeddedOrFromFile(path); diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs index ee22f6f11b4e..acedf80e7f09 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,11 +131,11 @@ 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(chain.SpecProvider, globalWorldState); - blockHashStore.ApplyBlockhashStateChanges(block!.Header); + var blockHashStore = new BlockhashStore(globalWorldState); + blockHashStore.ApplyBlockhashStateChanges(block!.Header, chain.SpecProvider.GetSpec(block.Header)); chain.WithdrawalProcessor?.ProcessWithdrawals(block!, chain.SpecProvider.GenesisSpec); globalWorldState.Commit(chain.SpecProvider.GenesisSpec); @@ -154,15 +154,15 @@ 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; + IWorldState globalWorldState = chain.MainWorldState; Snapshot before = globalWorldState.TakeSnapshot(); - var blockHashStore = new BlockhashStore(chain.SpecProvider, globalWorldState); - blockHashStore.ApplyBlockhashStateChanges(block!.Header); + var blockHashStore = new BlockhashStore(globalWorldState); + blockHashStore.ApplyBlockhashStateChanges(block.Header, chain.SpecProvider.GetSpec(block.Header)); chain.TxProcessor.SetBlockExecutionContext(new BlockExecutionContext(block.Header, chain.SpecProvider.GenesisSpec)); chain.MainExecutionRequestsProcessor.ProcessExecutionRequests(block!, globalWorldState, [], chain.SpecProvider.GenesisSpec); diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.PayloadProduction.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.PayloadProduction.cs index 99998678743e..1a3b6991062c 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.PayloadProduction.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.PayloadProduction.cs @@ -24,7 +24,6 @@ using Nethermind.Core.Test.Container; using Nethermind.Core.Timers; using Nethermind.Crypto; -using Nethermind.Evm.State; using Nethermind.Int256; using Nethermind.JsonRpc; using Nethermind.JsonRpc.Test; @@ -105,7 +104,8 @@ public async Task getPayloadV1_should_return_error_if_called_after_cleanup_timer } [Test] - public async Task getPayloadV1_picks_transactions_from_pool_v1() + [CancelAfter(10000)] + public async Task getPayloadV1_picks_transactions_from_pool_v1(CancellationToken cancellationToken) { using SemaphoreSlim blockImprovementLock = new(0); using MergeTestBlockchain chain = await CreateBlockchain(); @@ -123,7 +123,7 @@ public async Task getPayloadV1_picks_transactions_from_pool_v1() new PayloadAttributes() { Timestamp = 100, PrevRandao = TestItem.KeccakA, SuggestedFeeRecipient = Address.Zero }) .Result.Data.PayloadId!; - await blockImprovementLock.WaitAsync(10000); + await blockImprovementLock.WaitAsync(cancellationToken); ExecutionPayload getPayloadResult = (await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId))).Data!; getPayloadResult.StateRoot.Should().NotBe(chain.BlockTree.Genesis!.StateRoot!); @@ -186,9 +186,10 @@ public IEnumerable GetTransactions(BlockHeader parent, long gasLimi [TestCaseSource(nameof(WaitTestCases))] [Parallelizable(ParallelScope.None)] // Timing sensitive + [Retry(3)] public async Task getPayloadV1_waits_for_block_production(TimeSpan txDelay, TimeSpan improveDelay, bool hasTx) { - TaskCompletionSource yieldedTransaction = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + TaskCompletionSource yieldedTransaction = new(TaskCreationOptions.RunContinuationsAsynchronously); using MergeTestBlockchain chain = await CreateBlockchain(configurer: builder => builder .AddSingleton((producer) => new DelayBlockImprovementContextFactory(producer, TimeSpan.FromSeconds(10), improveDelay)) .AddSingleton(ConfigurePayloadPreparationService(TimeSpan.FromSeconds(10), null)) @@ -219,17 +220,16 @@ public async Task getPayloadV1_waits_for_block_production(TimeSpan txDelay, Time } TimeSpan timeBudget = PayloadPreparationService.GetPayloadWaitForNonEmptyBlockMillisecondsDelay; - int expectedTxCount = 50; - if (txDelay != TimeSpan.Zero) - { - expectedTxCount = (int)((timeBudget - improveDelay) / txDelay); - } - if (improveDelay > timeBudget) expectedTxCount = 0; + int expectedTxCount = improveDelay > timeBudget + ? 0 + : txDelay != TimeSpan.Zero ? (int)((timeBudget - improveDelay) / txDelay) : 50; + + int maxWait = Math.Max(0, (int)(timeBudget - improveDelay).TotalMilliseconds - 1); await Task.Delay(timeBudget); Assert.That(() => rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId)).Result.Data!.Transactions, - Has.Length.InRange(expectedTxCount * 0.5, expectedTxCount * 2.0)); // get payload stop block improvement so retrying does nothing here. + Has.Length.InRange(expectedTxCount * 0.5, expectedTxCount * 2.0).After(maxWait, 10)); // get payload stop block improvement so retrying does nothing here. } [Test] @@ -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 7291adf83449..642590d16cfc 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.RelayBuilder.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.RelayBuilder.cs @@ -15,7 +15,6 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; -using Nethermind.Core.Timers; using Nethermind.Facade.Proxy; using Nethermind.Int256; using Nethermind.JsonRpc; @@ -107,7 +106,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 c0a4ca897d61..1b2580b8591f 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs @@ -9,6 +9,7 @@ using FluentAssertions; using Nethermind.Blockchain; using Nethermind.Blockchain.Synchronization; +using Nethermind.Consensus.Processing; using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; @@ -19,7 +20,6 @@ using Nethermind.Logging; using Nethermind.Merge.Plugin.Data; using Nethermind.Merge.Plugin.Synchronization; -using Nethermind.Stats; using Nethermind.Synchronization; using Nethermind.Synchronization.FastBlocks; using Nethermind.Synchronization.ParallelSync; @@ -90,7 +90,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; @@ -136,7 +136,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; @@ -728,7 +728,9 @@ public async Task Blocks_from_cache_inserted_when_fast_headers_sync_finish_befor } [Test] - public async Task Maintain_correct_pointers_for_beacon_sync_in_archive_sync() + [CancelAfter(5000)] + [Retry(3)] + public async Task Maintain_correct_pointers_for_beacon_sync_in_archive_sync(CancellationToken cancellationToken) { using MergeTestBlockchain chain = await CreateBlockchain(); IEngineRpcModule rpc = chain.EngineRpcModule; @@ -815,7 +817,8 @@ public async Task Maintain_correct_pointers_for_beacon_sync_in_archive_sync() }; await chain.BlockTree.SuggestBlockAsync(bestBeaconBlock!, BlockTreeSuggestOptions.ShouldProcess | BlockTreeSuggestOptions.FillBeaconBlock); - await bestBlockProcessed.WaitAsync(); + await bestBlockProcessed.WaitAsync(cancellationToken); + await chain.BlockProcessingQueue.WaitForBlockProcessing(cancellationToken); // beacon sync should be finished, eventually bestBeaconBlockRequest = CreateBlockRequest(chain, bestBeaconBlockRequest, Address.Zero); @@ -841,7 +844,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() ?? "" }; @@ -869,7 +872,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() ?? "" }; @@ -899,7 +902,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() ?? "" }; @@ -1051,8 +1054,7 @@ private MultiSyncModeSelector CreateMultiSyncModeSelector(MergeTestBlockchain ch Substitute.For>(), Substitute.For>(), Substitute.For>(), - Substitute.For>(), - LimboLogs.Instance); + Substitute.For>()); MultiSyncModeSelector multiSyncModeSelector = new(syncProgressResolver, syncPeerPool, new SyncConfig(), No.BeaconSync, diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index 8ee688de990a..d0ba23fd16c3 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -13,6 +13,7 @@ using FluentAssertions; using Nethermind.Blockchain; using Nethermind.Blockchain.Find; +using Nethermind.Consensus.Processing; using Nethermind.Consensus.Producers; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -140,7 +141,7 @@ public async Task can_parse_forkchoiceUpdated_with_implicit_null_payloadAttribut public void ForkchoiceV1_ToString_returns_correct_results() { ForkchoiceStateV1 forkchoiceState = new(TestItem.KeccakA, TestItem.KeccakF, TestItem.KeccakC); - forkchoiceState.ToString().Should().Be("ForkChoice: 0x03783f...35b760, Safe: 0x017e66...b18f72, Finalized: 0xe61d9a...97c37a"); + forkchoiceState.ToString().Should().Be("ForkChoice: 0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760, Safe: 0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72, Finalized: 0xe61d9a3d3848fb2cdd9a2ab61e2f21a10ea431275aed628a0557f9dee697c37a"); } [Test] @@ -441,14 +442,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); @@ -458,7 +459,8 @@ public async Task executePayloadV1_result_is_fail_when_blockchainprocessor_repor [TestCase(true)] [TestCase(false)] - public virtual async Task executePayloadV1_accepts_already_known_block(bool throttleBlockProcessor) + [CancelAfter(5000)] + public virtual async Task executePayloadV1_accepts_already_known_block(bool throttleBlockProcessor, CancellationToken cancellationToken) { using MergeTestBlockchain chain = await CreateBaseBlockchain() .ThrottleBlockProcessor(throttleBlockProcessor ? 100 : 0) @@ -478,7 +480,7 @@ public virtual async Task executePayloadV1_accepts_already_known_block(bool thro }; await chain.BlockTree.SuggestBlockAsync(block!); - await bestBlockProcessed.WaitAsync(); + await bestBlockProcessed.WaitAsync(cancellationToken); ExecutionPayload blockRequest = ExecutionPayload.Create(block); ResultWrapper executePayloadResult = await rpc.engine_newPayloadV1(blockRequest); executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); @@ -667,7 +669,19 @@ public async Task forkChoiceUpdatedV1_block_still_processing() Block blockTreeHead = chain.BlockTree.Head!; Block block = Build.A.Block.WithNumber(blockTreeHead.Number + 1).WithParent(blockTreeHead).WithNonce(0).WithDifficulty(0).TestObject; - chain.ThrottleBlockProcessor(200); + chain.ThrottleBlockProcessor(1000); + ManualResetEventSlim processingStarted = new(false); + ((TestBranchProcessorInterceptor)chain.BranchProcessor).ProcessingStarted = processingStarted; + + // Directly enqueue a block to occupy the processor (bypasses the RPC semaphore), + // ensuring subsequent blocks route through the recovery queue (slow path) + Block occupyBlock = Build.A.Block.WithNumber(blockTreeHead.Number + 1).WithParent(blockTreeHead) + .WithNonce(0).WithDifficulty(0).WithStateRoot(blockTreeHead.StateRoot!).TestObject; + occupyBlock.Header.TotalDifficulty = blockTreeHead.TotalDifficulty; + _ = Task.Run(async () => await chain.BlockProcessingQueue.Enqueue( + occupyBlock, ProcessingOptions.ForceProcessing | ProcessingOptions.DoNotUpdateHead)); + processingStarted.Wait(TimeSpan.FromSeconds(5)); + ResultWrapper newPayloadV1 = await rpc.engine_newPayloadV1(ExecutionPayload.Create(block)); newPayloadV1.Data.Status.Should().Be("SYNCING"); @@ -729,7 +743,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) @@ -748,6 +762,18 @@ public async Task Invalid_block_on_processing_wont_be_accepted_if_sent_twice_in_ .TestObject; chain.ThrottleBlockProcessor(1000); // throttle the block processor enough so that the block processing queue is never empty + ManualResetEventSlim processingStarted = new(false); + ((TestBranchProcessorInterceptor)chain.BranchProcessor).ProcessingStarted = processingStarted; + + // Directly enqueue a block to occupy the processor (bypasses the RPC semaphore), + // ensuring subsequent blocks route through the recovery queue (slow path) + Block occupyBlock = Build.A.Block.WithNumber(head.Number + 1).WithParent(head) + .WithNonce(0).WithDifficulty(0).WithStateRoot(head.StateRoot!).TestObject; + occupyBlock.Header.TotalDifficulty = head.TotalDifficulty; + _ = Task.Run(async () => await chain.BlockProcessingQueue.Enqueue( + occupyBlock, ProcessingOptions.ForceProcessing | ProcessingOptions.DoNotUpdateHead)); + processingStarted.Wait(TimeSpan.FromSeconds(5)); + (await rpc.engine_newPayloadV1(ExecutionPayload.Create(block))).Data.Status.Should().Be(PayloadStatus.Syncing); (await rpc.engine_newPayloadV1(ExecutionPayload.Create(block))).Data.Status.Should().BeOneOf(PayloadStatus.Syncing); } @@ -818,7 +844,8 @@ public async Task forkchoiceUpdatedV1_should_not_accept_blocks_with_incorrect_tt } [Test] - public async Task executePayloadV1_on_top_of_terminal_block() + [CancelAfter(5000)] + public async Task executePayloadV1_on_top_of_terminal_block(CancellationToken cancellationToken) { using MergeTestBlockchain chain = await CreateBlockchain(null, new MergeConfig() { @@ -846,7 +873,7 @@ public async Task executePayloadV1_on_top_of_terminal_block() bestBlockProcessed.Release(1); }; await chain.BlockTree.SuggestBlockAsync(newBlock); - (await bestBlockProcessed.WaitAsync(TimeSpan.FromSeconds(5))).Should().Be(true); + await bestBlockProcessed.WaitAsync(cancellationToken); oneMoreTerminalBlock.CalculateHash(); await chain.BlockTree.SuggestBlockAsync(oneMoreTerminalBlock); @@ -863,7 +890,8 @@ public async Task executePayloadV1_on_top_of_terminal_block() } [Test] - public async Task executePayloadV1_on_top_of_not_processed_invalid_terminal_block() + [CancelAfter(5000)] + public async Task executePayloadV1_on_top_of_not_processed_invalid_terminal_block(CancellationToken cancellationToken) { using MergeTestBlockchain chain = await CreateBlockchain(null, new MergeConfig() { @@ -891,7 +919,7 @@ public async Task executePayloadV1_on_top_of_not_processed_invalid_terminal_bloc bestBlockProcessed.Release(1); }; await chain.BlockTree.SuggestBlockAsync(newBlock); - (await bestBlockProcessed.WaitAsync(TimeSpan.FromSeconds(5))).Should().Be(true); + await bestBlockProcessed.WaitAsync(cancellationToken); oneMoreTerminalBlock.CalculateHash(); await chain.BlockTree.SuggestBlockAsync(oneMoreTerminalBlock); @@ -1522,7 +1550,7 @@ public async Task Should_return_capabilities() [Test] public void Should_return_expected_capabilities_for_mainnet() { - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance); string path = Path.Combine(TestContext.CurrentContext.WorkDirectory, "../../../../", "Chains/foundation.json"); var chainSpec = loader.LoadEmbeddedOrFromFile(path); ChainSpecBasedSpecProvider specProvider = new(chainSpec); @@ -1551,6 +1579,10 @@ public void Should_return_expected_capabilities_for_mainnet() nameof(IEngineRpcModule.engine_getPayloadV4), nameof(IEngineRpcModule.engine_newPayloadV4), + + nameof(IEngineRpcModule.engine_getPayloadV5), + nameof(IEngineRpcModule.engine_getBlobsV2), + nameof(IEngineRpcModule.engine_getBlobsV3) }; Assert.That(result, Is.EquivalentTo(expectedMethods)); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs index 9c2d622c88c0..91bba6a5832a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs @@ -458,9 +458,9 @@ await rpc.engine_forkchoiceUpdatedV2(new ForkchoiceStateV1(executionPayload1.Blo IEnumerable payloadBodies = rpc.engine_getPayloadBodiesByRangeV1(1, 3).Result.Data; ExecutionPayloadBodyV1Result[] expected = - { + [ new(Array.Empty(), withdrawals), new(txsA, withdrawals) - }; + ]; payloadBodies.Should().BeEquivalentTo(expected, static o => o.WithStrictOrdering()); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs index 514515d9ad92..e34ec509534a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs @@ -4,7 +4,10 @@ using System; using System.Collections.Generic; using System.IO.Abstractions; +using System.IO.Pipelines; using System.Linq; +using System.Text; +using System.Text.Json; using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; @@ -14,6 +17,7 @@ using Nethermind.Blockchain.Synchronization; using Nethermind.Consensus.Producers; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; @@ -140,7 +144,7 @@ public async Task GetPayloadV3_should_return_all_the_blobs([Values(0, 1, 2, 3, 4 Assert.That(getPayloadResultBlobsBundle.Commitments!.Length, Is.EqualTo(blobTxCount)); Assert.That(getPayloadResultBlobsBundle.Proofs!.Length, Is.EqualTo(blobTxCount)); ShardBlobNetworkWrapper wrapper = new ShardBlobNetworkWrapper(getPayloadResultBlobsBundle.Blobs, - getPayloadResultBlobsBundle.Commitments, getPayloadResultBlobsBundle.Proofs, ProofVersion.V1); + getPayloadResultBlobsBundle.Commitments, getPayloadResultBlobsBundle.Proofs, ProofVersion.V0); Assert.That(IBlobProofsManager.For(ProofVersion.V0).ValidateProofs(wrapper), Is.True); } @@ -385,7 +389,7 @@ public async Task NewPayloadV3_should_verify_blob_versioned_hashes_again Substitute.For>(), Substitute.For, IEnumerable>>(), Substitute.For>>(), - Substitute.For?>>(), + Substitute.For?>>(), Substitute.For(), chain.SpecProvider, new GCKeeper(NoGCStrategy.Instance, chain.LogManager), @@ -683,6 +687,33 @@ public async Task GetBlobsV1_should_return_mix_of_blobs_and_nulls([Values(1, 2, } } + [Test] + public async Task BlobsV1DirectResponse_WriteToAsync_produces_valid_json() + { + byte[] blob = new byte[16]; + Random.Shared.NextBytes(blob); + byte[] proof = new byte[48]; + Random.Shared.NextBytes(proof); + + ArrayPoolList items = new(2); + items.Add(new BlobAndProofV1(blob, proof)); + items.Add(null); + + using BlobsV1DirectResponse response = new(items); + + Pipe pipe = new(); + await response.WriteToAsync(pipe.Writer, CancellationToken.None); + await pipe.Writer.CompleteAsync(); + + ReadResult readResult = await pipe.Reader.ReadAsync(); + string streamedJson = Encoding.UTF8.GetString(readResult.Buffer); + pipe.Reader.AdvanceTo(readResult.Buffer.End); + + string stjJson = JsonSerializer.Serialize(response, EthereumJsonSerializer.JsonOptions); + + streamedJson.Should().Be(stjJson); + } + [Test] public async Task Sync_proper_chain_when_header_fork_came_from_fcu_and_beacon_sync() { diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V4.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V4.cs index 1472dc909fd0..54112b3112ae 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V4.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V4.cs @@ -259,7 +259,7 @@ public async Task can_progress_chain_one_by_one_v4_with_requests(int count) last!.IsGenesis.Should().BeTrue(); Block? head = chain.BlockTree.Head; - head!.ExecutionRequests!.ToArray().Length.Should().Be(ExecutionRequestsProcessorMock.Requests.Length); + head!.ExecutionRequests!.Length.Should().Be(ExecutionRequestsProcessorMock.Requests.Length); } private async Task> ProduceBranchV4(IEngineRpcModule rpc, @@ -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 681110c95fc4..218014837ee0 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs @@ -3,7 +3,11 @@ using System; using System.Collections.Generic; +using System.IO.Pipelines; using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using CkzgLib; using FluentAssertions; @@ -14,6 +18,7 @@ using Nethermind.Evm; using Nethermind.JsonRpc; using Nethermind.Merge.Plugin.Data; +using Nethermind.Serialization.Json; using Nethermind.Specs.Forks; using Nethermind.TxPool; using NUnit.Framework; @@ -52,7 +57,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 +80,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 +104,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 +132,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 +167,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 +178,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(); + } } } @@ -190,4 +251,67 @@ public async Task GetBlobsV1_should_return_invalid_fork_post_osaka() result.Result.Should().BeEquivalentTo(Result.Fail(MergeErrorMessages.UnsupportedFork)); result.ErrorCode.Should().Be(MergeErrorCodes.UnsupportedFork); } + + [Test] + public async Task BlobsV2DirectResponse_WriteToAsync_produces_valid_json() + { + // Build a small list with one real entry and one null + byte[] blob = new byte[16]; + Random.Shared.NextBytes(blob); + byte[] proof1 = new byte[48]; + Random.Shared.NextBytes(proof1); + byte[] proof2 = new byte[48]; + Random.Shared.NextBytes(proof2); + + byte[]?[] blobs = [blob, null]; + ReadOnlyMemory[] proofs = [new ReadOnlyMemory([proof1, proof2]), default]; + + BlobsV2DirectResponse response = new(blobs, proofs, 2); + + // Write via streaming path + Pipe pipe = new(); + await response.WriteToAsync(pipe.Writer, CancellationToken.None); + await pipe.Writer.CompleteAsync(); + + ReadResult readResult = await pipe.Reader.ReadAsync(); + string streamedJson = Encoding.UTF8.GetString(readResult.Buffer); + pipe.Reader.AdvanceTo(readResult.Buffer.End); + + // Write via STJ for comparison + string stjJson = JsonSerializer.Serialize(response, EthereumJsonSerializer.JsonOptions); + + streamedJson.Should().Be(stjJson); + } + + [Test] + public async Task BlobsV2DirectResponse_WriteToAsync_empty_list() + { + BlobsV2DirectResponse response = new([], [], 0); + + Pipe pipe = new(); + await response.WriteToAsync(pipe.Writer, CancellationToken.None); + await pipe.Writer.CompleteAsync(); + + ReadResult readResult = await pipe.Reader.ReadAsync(); + string json = Encoding.UTF8.GetString(readResult.Buffer); + pipe.Reader.AdvanceTo(readResult.Buffer.End); + + json.Should().Be("[]"); + } + + [Test] + public void BlobsV2DirectResponse_WriteToAsync_throws_on_cancelled_token() + { + byte[] blob = new byte[131072]; // 128KB + byte[]?[] blobs = [blob]; + ReadOnlyMemory[] proofs = [new ReadOnlyMemory([new byte[48]])]; + BlobsV2DirectResponse response = new(blobs, proofs, 1); + + using CancellationTokenSource cts = new(); + cts.Cancel(); + + Pipe pipe = new(); + Func act = async () => await response.WriteToAsync(pipe.Writer, cts.Token); + act.Should().ThrowAsync(); + } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/ExecutionRequestsProcessorMock.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/ExecutionRequestsProcessorMock.cs index e1d8404c53da..8ad25a2ec4ef 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/ExecutionRequestsProcessorMock.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/ExecutionRequestsProcessorMock.cs @@ -8,7 +8,6 @@ using Nethermind.Core.ExecutionRequest; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; -using Nethermind.Evm; using Nethermind.Evm.State; namespace Nethermind.Merge.Plugin.Test; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/ExternalRpcIntegrationTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/ExternalRpcIntegrationTests.cs index 1317e380c1be..e8b1ab4c9da7 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/ExternalRpcIntegrationTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/ExternalRpcIntegrationTests.cs @@ -1,15 +1,16 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Threading.Tasks; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Facade.Eth; using Nethermind.Int256; -using Nethermind.Overseer.Test.JsonRpc; +using Nethermind.JsonRpc.Client; +using Nethermind.Logging; using Nethermind.Serialization.Json; -using Newtonsoft.Json.Linq; using NUnit.Framework; +using System; +using System.Threading.Tasks; namespace Nethermind.Merge.Plugin.Test; @@ -28,20 +29,20 @@ public async Task CanonicalTreeIsConsistent() int destinationBlockNumber = 5000; long? currentBlockNumber = null; Hash256? currentHash = null; - JsonRpcClient? client = new($"http://127.0.0.1:8545"); + BasicJsonRpcClient client = new(new Uri("http://127.0.0.1:8545"), jsonSerializer, LimboLogs.Instance); do { - string? requestedBlockNumber = currentBlockNumber is null ? "latest" : currentBlockNumber.Value.ToHexString(false); - JsonRpcResponse? requestResponse = - await client.PostAsync("eth_getBlockByNumber", new object[] { requestedBlockNumber!, false }); - BlockForRpcForTest? block = jsonSerializer.Deserialize(requestResponse.Result.ToString()); + string requestedBlockNumber = currentBlockNumber is null ? "latest" : currentBlockNumber.Value.ToHexString(false); + BlockForRpcForTest? block = + await client.Post("eth_getBlockByNumber", [requestedBlockNumber, false]); + Assert.That(block, Is.Not.Null); if (currentHash is not null) { - Assert.That(block.Hash, Is.EqualTo(currentHash), $"incorrect block hash found {block}"); + Assert.That(block!.Hash, Is.EqualTo(currentHash), $"incorrect block hash found {block}"); } - currentHash = block.ParentHash; - currentBlockNumber = block.Number!.Value - 1; + currentHash = block!.ParentHash; + currentBlockNumber = block!.Number!.Value - 1; } while (currentBlockNumber != destinationBlockNumber); } @@ -53,20 +54,20 @@ public async Task ParentTimestampIsAlwaysLowerThanChildTimestamp() int destinationBlockNumber = 5000; long? currentBlockNumber = null; UInt256? childTimestamp = null; - JsonRpcClient? client = new($"http://127.0.0.1:8545"); + BasicJsonRpcClient client = new(new Uri("http://127.0.0.1:8545"), jsonSerializer, LimboLogs.Instance); do { - string? requestedBlockNumber = currentBlockNumber is null ? "latest" : currentBlockNumber.Value.ToHexString(false); - JsonRpcResponse? requestResponse = - await client.PostAsync("eth_getBlockByNumber", [requestedBlockNumber!, false]); - BlockForRpcForTest? block = jsonSerializer.Deserialize(requestResponse.Result.ToString()); + string requestedBlockNumber = currentBlockNumber is null ? "latest" : currentBlockNumber.Value.ToHexString(false); + BlockForRpcForTest? block = + await client.Post("eth_getBlockByNumber", [requestedBlockNumber, false]); + Assert.That(block, Is.Not.Null); if (childTimestamp is not null) { - Assert.That(childTimestamp, Is.GreaterThan(block.Timestamp), $"incorrect timestamp for block {block}"); + Assert.That(childTimestamp, Is.GreaterThan(block!.Timestamp), $"incorrect timestamp for block {block}"); } - childTimestamp = block.Timestamp; - currentBlockNumber = block.Number!.Value - 1; + childTimestamp = block!.Timestamp; + currentBlockNumber = block!.Number!.Value - 1; } while (currentBlockNumber != destinationBlockNumber); } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs index de53c5116f44..3a49eb9e9358 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs @@ -1,10 +1,14 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +/* cSpell:disable */ using System; +using System.Security.Cryptography; +using System.Text; using System.Threading.Tasks; using Nethermind.Core; using Nethermind.Core.Authentication; +using Nethermind.Core.Extensions; using Nethermind.Logging; using NUnit.Framework; @@ -12,6 +16,9 @@ namespace Nethermind.Merge.Plugin.Test; public class JwtTest { + private const string HexSecret = "5166546A576E5A7234753778214125442A472D4A614E645267556B5870327335"; + private const long TestIat = 1644994971; + [Test] [TestCase("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NDQ5OTQ5NzF9.RmIbZajyYGF9fhAq7A9YrTetdf15ebHIJiSdAhX7PME", "true")] [TestCase("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NDQ5OTQ5NzV9.HfWy49SIyB12PBB_xEpy6IAiIan5mIqD6Jzeh_J1QNw", "true")] @@ -29,12 +36,135 @@ public class JwtTest [TestCase("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NDQ5OTQ5NjF9.huhtaE1cUU2JuhqKmeTrHC3wgl2Tp_1pVh7DuYkKrQo", "true")] public async Task long_key_tests(string token, bool expected) { - ManualTimestamper manualTimestamper = new() { UtcNow = DateTimeOffset.FromUnixTimeSeconds(1644994971).UtcDateTime }; - IRpcAuthentication authentication = JwtAuthentication.FromSecret("5166546A576E5A7234753778214125442A472D4A614E645267556B5870327335", manualTimestamper, LimboTraceLogger.Instance); - IRpcAuthentication authenticationWithPrefix = JwtAuthentication.FromSecret("0x5166546A576E5A7234753778214125442A472D4A614E645267556B5870327335", manualTimestamper, LimboTraceLogger.Instance); + ManualTimestamper manualTimestamper = new() { UtcNow = DateTimeOffset.FromUnixTimeSeconds(TestIat).UtcDateTime }; + IRpcAuthentication authentication = JwtAuthentication.FromSecret(HexSecret, manualTimestamper, LimboTraceLogger.Instance); + IRpcAuthentication authenticationWithPrefix = JwtAuthentication.FromSecret("0x" + HexSecret, manualTimestamper, LimboTraceLogger.Instance); bool actual = await authentication.Authenticate(token); Assert.That(actual, Is.EqualTo(expected)); actual = await authenticationWithPrefix.Authenticate(token); Assert.That(expected, Is.EqualTo(actual)); } + + // --- Guard clause tests (Authenticate entry) --- + + [Test] + public async Task Null_token_returns_false() + { + Assert.That(await CreateAuth().Authenticate(null!), Is.False); + } + + [Test] + public async Task Empty_token_returns_false() + { + Assert.That(await CreateAuth().Authenticate(""), Is.False); + } + + [Test] + public async Task Missing_bearer_prefix_returns_false() + { + // Valid JWT but without "Bearer " prefix + string jwt = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat}}}"); + Assert.That(await CreateAuth().Authenticate(jwt["Bearer ".Length..]), Is.False); + } + + // --- Alternate header format tests (TryValidateManual branches) --- + + [Test] + public async Task HeaderTypAlg_valid_token_returns_true() + { + // {"typ":"JWT","alg":"HS256"} — reversed field order, 36-char Base64Url header + string token = CreateJwt("{\"typ\":\"JWT\",\"alg\":\"HS256\"}", $"{{\"iat\":{TestIat}}}"); + Assert.That(await CreateAuth().Authenticate(token), Is.True); + } + + [Test] + public async Task HeaderAlgOnly_valid_token_returns_true() + { + // {"alg":"HS256"} — no typ field, 20-char Base64Url header + string token = CreateJwt("{\"alg\":\"HS256\"}", $"{{\"iat\":{TestIat}}}"); + Assert.That(await CreateAuth().Authenticate(token), Is.True); + } + + [Test] + public async Task HeaderAlgOnly_expired_iat_returns_false() + { + string token = CreateJwt("{\"alg\":\"HS256\"}", $"{{\"iat\":{TestIat - 200}}}"); + Assert.That(await CreateAuth().Authenticate(token), Is.False); + } + + // --- AuthenticateCore fallback (unrecognized header → library path) --- + + [Test] + public async Task Unrecognized_header_valid_token_falls_to_library() + { + // Extra "kid" field makes the Base64Url header unrecognized → AuthenticateCore path + string token = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\",\"kid\":\"1\"}", $"{{\"iat\":{TestIat}}}"); + Assert.That(await CreateAuth().Authenticate(token), Is.True); + } + + [Test] + public async Task Unrecognized_header_expired_iat_returns_false() + { + string token = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\",\"kid\":\"1\"}", $"{{\"iat\":{TestIat - 200}}}"); + Assert.That(await CreateAuth().Authenticate(token), Is.False); + } + + // --- Cache path tests (TryLastValidationFromCache) --- + + [Test] + public async Task Cache_hit_returns_true_on_repeated_call() + { + IRpcAuthentication auth = CreateAuth(); + string token = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat}}}"); + + Assert.That(await auth.Authenticate(token), Is.True); + // Second call with same token should hit cache and still return true + Assert.That(await auth.Authenticate(token), Is.True); + } + + [Test] + public async Task Cache_eviction_when_iat_expires() + { + ManualTimestamper ts = new() { UtcNow = DateTimeOffset.FromUnixTimeSeconds(TestIat).UtcDateTime }; + IRpcAuthentication auth = JwtAuthentication.FromSecret(HexSecret, ts, LimboTraceLogger.Instance); + string token = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat}}}"); + + Assert.That(await auth.Authenticate(token), Is.True); + + // Advance time beyond TTL — cache should evict, iat check should fail + ts.UtcNow = DateTimeOffset.FromUnixTimeSeconds(TestIat + 61).UtcDateTime; + Assert.That(await auth.Authenticate(token), Is.False); + } + + [Test] + public async Task Cache_miss_different_token_revalidates() + { + IRpcAuthentication auth = CreateAuth(); + string token1 = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat}}}"); + string token2 = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat + 1}}}"); + + Assert.That(await auth.Authenticate(token1), Is.True); + // Different token — cache miss, must revalidate + Assert.That(await auth.Authenticate(token2), Is.True); + } + + // --- Helpers --- + + private static IRpcAuthentication CreateAuth(long nowUnixSeconds = TestIat) + { + ManualTimestamper ts = new() { UtcNow = DateTimeOffset.FromUnixTimeSeconds(nowUnixSeconds).UtcDateTime }; + return JwtAuthentication.FromSecret(HexSecret, ts, LimboTraceLogger.Instance); + } + + private static string CreateJwt(string headerJson, string payloadJson) + { + byte[] secret = Bytes.FromHexString(HexSecret); + string header = Base64UrlEncode(Encoding.UTF8.GetBytes(headerJson)); + string payload = Base64UrlEncode(Encoding.UTF8.GetBytes(payloadJson)); + byte[] sig = HMACSHA256.HashData(secret, Encoding.ASCII.GetBytes($"{header}.{payload}")); + return $"Bearer {header}.{payload}.{Base64UrlEncode(sig)}"; + } + + private static string Base64UrlEncode(byte[] data) + => Convert.ToBase64String(data).TrimEnd('=').Replace('+', '-').Replace('/', '_'); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/MergeFinalizedStateProviderTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/MergeFinalizedStateProviderTests.cs new file mode 100644 index 000000000000..552547d37688 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/MergeFinalizedStateProviderTests.cs @@ -0,0 +1,217 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Find; +using Nethermind.Consensus; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Merge.Plugin.Handlers; +using Nethermind.Trie.Pruning; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Merge.Plugin.Test; + +[TestFixture] +[Parallelizable(ParallelScope.Self)] +public class MergeFinalizedStateProviderTests +{ + private IPoSSwitcher _poSSwitcher = null!; + private IBlockTree _blockTree = null!; + private IFinalizedStateProvider _baseFinalizedStateProvider = null!; + private MergeFinalizedStateProvider _provider = null!; + private IBlockCacheService _blockCacheService; + + [SetUp] + public void Setup() + { + _poSSwitcher = Substitute.For(); + _blockTree = Substitute.For(); + _baseFinalizedStateProvider = Substitute.For(); + _blockCacheService = Substitute.For(); + _provider = new MergeFinalizedStateProvider(_poSSwitcher, _blockCacheService, _blockTree, _baseFinalizedStateProvider); + } + + [Test] + public void FinalizedBlockNumber_BeforeTransition_DelegatesToBaseProvider() + { + // Arrange + long expectedBlockNumber = 100; + _poSSwitcher.TransitionFinished.Returns(false); + _baseFinalizedStateProvider.FinalizedBlockNumber.Returns(expectedBlockNumber); + + // Act + long result = _provider.FinalizedBlockNumber; + + // Assert + result.Should().Be(expectedBlockNumber); + _ = _baseFinalizedStateProvider.Received(1).FinalizedBlockNumber; + _blockTree.DidNotReceive().FindHeader(Arg.Any()); + } + + [Test] + public void FinalizedBlockNumber_AfterTransition_WithBlockTreeFinalizedHash_ReturnsHeaderNumber() + { + // Arrange + long expectedBlockNumber = 200; + Hash256 finalizedHash = TestItem.KeccakA; + BlockHeader finalizedHeader = Build.A.BlockHeader.WithNumber(expectedBlockNumber).WithHash(finalizedHash).TestObject; + _poSSwitcher.TransitionFinished.Returns(true); + _blockTree.FinalizedHash.Returns(finalizedHash); + _blockTree.FindHeader(finalizedHash, BlockTreeLookupOptions.None).Returns(finalizedHeader); + + // Act + long result = _provider.FinalizedBlockNumber; + + // Assert + result.Should().Be(expectedBlockNumber); + _blockTree.Received(1).FindHeader(finalizedHash, BlockTreeLookupOptions.None); + } + + [Test] + public void FinalizedBlockNumber_AfterTransition_WithBlockCacheFinalizedHash_ReturnsHeaderNumber() + { + // Arrange + long expectedBlockNumber = 250; + Hash256 finalizedHash = TestItem.KeccakB; + BlockHeader finalizedHeader = Build.A.BlockHeader.WithNumber(expectedBlockNumber).WithHash(finalizedHash).TestObject; + _poSSwitcher.TransitionFinished.Returns(true); + _blockTree.FinalizedHash.Returns((Hash256?)null); + _blockCacheService.FinalizedHash.Returns(finalizedHash); + _blockTree.FindHeader(finalizedHash).Returns(finalizedHeader); + + // Act + long result = _provider.FinalizedBlockNumber; + + // Assert + result.Should().Be(expectedBlockNumber); + _blockTree.Received(1).FindHeader(finalizedHash); + } + + [Test] + public void FinalizedBlockNumber_AfterTransition_BlockCacheHasHigherNumber_ReturnsBlockCacheNumber() + { + // Arrange + long blockTreeBlockNumber = 200; + long blockCacheBlockNumber = 250; + Hash256 blockTreeHash = TestItem.KeccakA; + Hash256 blockCacheHash = TestItem.KeccakB; + BlockHeader blockTreeHeader = Build.A.BlockHeader.WithNumber(blockTreeBlockNumber).WithHash(blockTreeHash).TestObject; + BlockHeader blockCacheHeader = Build.A.BlockHeader.WithNumber(blockCacheBlockNumber).WithHash(blockCacheHash).TestObject; + + _poSSwitcher.TransitionFinished.Returns(true); + _blockTree.FinalizedHash.Returns(blockTreeHash); + _blockTree.FindHeader(blockTreeHash, BlockTreeLookupOptions.None).Returns(blockTreeHeader); + _blockCacheService.FinalizedHash.Returns(blockCacheHash); + _blockTree.FindHeader(blockCacheHash).Returns(blockCacheHeader); + + // Act + long result = _provider.FinalizedBlockNumber; + + // Assert + result.Should().Be(blockCacheBlockNumber); + } + + [Test] + public void FinalizedBlockNumber_AfterTransition_BlockTreeHasHigherNumber_ReturnsBlockTreeNumber() + { + // Arrange + long blockTreeBlockNumber = 300; + long blockCacheBlockNumber = 250; + Hash256 blockTreeHash = TestItem.KeccakA; + Hash256 blockCacheHash = TestItem.KeccakB; + BlockHeader blockTreeHeader = Build.A.BlockHeader.WithNumber(blockTreeBlockNumber).WithHash(blockTreeHash).TestObject; + BlockHeader blockCacheHeader = Build.A.BlockHeader.WithNumber(blockCacheBlockNumber).WithHash(blockCacheHash).TestObject; + + _poSSwitcher.TransitionFinished.Returns(true); + _blockTree.FinalizedHash.Returns(blockTreeHash); + _blockTree.FindHeader(blockTreeHash, BlockTreeLookupOptions.None).Returns(blockTreeHeader); + _blockCacheService.FinalizedHash.Returns(blockCacheHash); + _blockTree.FindHeader(blockCacheHash).Returns(blockCacheHeader); + + // Act + long result = _provider.FinalizedBlockNumber; + + // Assert + result.Should().Be(blockTreeBlockNumber); + } + + [Test] + public void FinalizedBlockNumber_AfterTransition_BlockCacheHeaderNotFound_UsesOnlyBlockTree() + { + // Arrange + long expectedBlockNumber = 200; + Hash256 blockTreeHash = TestItem.KeccakA; + Hash256 blockCacheHash = TestItem.KeccakB; + BlockHeader blockTreeHeader = Build.A.BlockHeader.WithNumber(expectedBlockNumber).WithHash(blockTreeHash).TestObject; + + _poSSwitcher.TransitionFinished.Returns(true); + _blockTree.FinalizedHash.Returns(blockTreeHash); + _blockTree.FindHeader(blockTreeHash, BlockTreeLookupOptions.None).Returns(blockTreeHeader); + _blockCacheService.FinalizedHash.Returns(blockCacheHash); + _blockTree.FindHeader(blockCacheHash).Returns((BlockHeader?)null); + + // Act + long result = _provider.FinalizedBlockNumber; + + // Assert + result.Should().Be(expectedBlockNumber); + } + + [Test] + public void FinalizedBlockNumber_AfterTransition_NoFinalizedHeaders_DelegatesToBaseProvider() + { + // Arrange + long expectedBlockNumber = 150; + _poSSwitcher.TransitionFinished.Returns(true); + _blockTree.FinalizedHash.Returns((Hash256?)null); + _blockCacheService.FinalizedHash.Returns((Hash256?)null); + _baseFinalizedStateProvider.FinalizedBlockNumber.Returns(expectedBlockNumber); + + // Act + long result = _provider.FinalizedBlockNumber; + + // Assert + result.Should().Be(expectedBlockNumber); + _ = _baseFinalizedStateProvider.Received(1).FinalizedBlockNumber; + } + + [Test] + public void GetFinalizedStateRootAt_ReturnsNull_WhenBlockNumberExceedsFinalizedBlock() + { + // Arrange + long finalizedBlockNumber = 100; + long blockNumber = 150; + _poSSwitcher.TransitionFinished.Returns(false); + _baseFinalizedStateProvider.FinalizedBlockNumber.Returns(finalizedBlockNumber); + + // Act + Hash256? result = _provider.GetFinalizedStateRootAt(blockNumber); + + // Assert + result.Should().BeNull(); + _baseFinalizedStateProvider.DidNotReceive().GetFinalizedStateRootAt(Arg.Any()); + } + + [Test] + public void GetFinalizedStateRootAt_DelegatesToBaseProvider_WhenBlockNumberIsFinalized() + { + // Arrange + long finalizedBlockNumber = 100; + long blockNumber = 50; + Hash256 expectedStateRoot = TestItem.KeccakA; + _poSSwitcher.TransitionFinished.Returns(false); + _baseFinalizedStateProvider.FinalizedBlockNumber.Returns(finalizedBlockNumber); + _baseFinalizedStateProvider.GetFinalizedStateRootAt(blockNumber).Returns(expectedStateRoot); + + // Act + Hash256? result = _provider.GetFinalizedStateRootAt(blockNumber); + + // Assert + result.Should().Be(expectedStateRoot); + _baseFinalizedStateProvider.Received(1).GetFinalizedStateRootAt(blockNumber); + } +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs index b5c7709135fa..3739ab6d7c85 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; using System.Threading.Tasks; using Autofac; using FluentAssertions; @@ -10,10 +12,8 @@ using Nethermind.Config; using Nethermind.Consensus.Clique; using Nethermind.Consensus.Processing; -using Nethermind.Consensus.Producers; using Nethermind.Core; using Nethermind.Core.Exceptions; -using Nethermind.Db; using Nethermind.HealthChecks; using Nethermind.JsonRpc; using Nethermind.JsonRpc.Modules; @@ -31,6 +31,17 @@ namespace Nethermind.Merge.Plugin.Test; public class MergePluginTests { + private sealed class SourceGenProbe + { + public int Value { get; set; } + } + + private sealed class ThrowingProbeResolver : IJsonTypeInfoResolver + { + public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options) => + type == typeof(SourceGenProbe) ? throw new InvalidOperationException("probe resolver was used") : null; + } + private ChainSpec _chainSpec = null!; private MergeConfig _mergeConfig = null!; private IJsonRpcConfig _jsonRpcConfig = null!; @@ -104,6 +115,15 @@ public void Init_merge_plugin_does_not_throw_exception(bool enabled) Assert.DoesNotThrow(() => _plugin.InitBlockProducer(_consensusPlugin!)); } + [Test] + public void AddTypeInfoResolver_updates_existing_serializer_instances() + { + EthereumJsonSerializer serializer = new(); + EthereumJsonSerializer.AddTypeInfoResolver(new ThrowingProbeResolver()); + + Assert.Throws(() => serializer.Serialize(new SourceGenProbe { Value = 1 })); + } + [Test] public async Task Initializes_correctly() { diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/Nethermind.Merge.Plugin.Test.csproj b/src/Nethermind/Nethermind.Merge.Plugin.Test/Nethermind.Merge.Plugin.Test.csproj index 25f4cddc1a05..353b11485688 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/Nethermind.Merge.Plugin.Test.csproj +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/Nethermind.Merge.Plugin.Test.csproj @@ -14,7 +14,6 @@ - diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/PoSSwitcherTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/PoSSwitcherTests.cs index 50299b08b045..7b5d470c957c 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/PoSSwitcherTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/PoSSwitcherTests.cs @@ -38,7 +38,7 @@ public void Read_TTD_from_chainspec_if_not_specified_in_merge_config() UInt256 expectedTtd = 10; IBlockTree blockTree = Substitute.For(); - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance); string path = Path.Combine(TestContext.CurrentContext.WorkDirectory, "Specs/test_spec.json"); var chainSpec = loader.LoadEmbeddedOrFromFile(path); diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/ProcessedTransactionsDbCleanerTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/ProcessedTransactionsDbCleanerTests.cs index 852e422d4bbf..8ad968b4305e 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/ProcessedTransactionsDbCleanerTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/ProcessedTransactionsDbCleanerTests.cs @@ -5,6 +5,7 @@ using FluentAssertions; using Nethermind.Blockchain; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; @@ -40,9 +41,10 @@ Transaction GetTx(PrivateKey sender) IColumnsDb columnsDb = new MemColumnsDb(BlobTxsColumns.ProcessedTxs); BlobTxStorage blobTxStorage = new(columnsDb); - Transaction[] txs = { GetTx(TestItem.PrivateKeyA), GetTx(TestItem.PrivateKeyB) }; - - blobTxStorage.AddBlobTransactionsFromBlock(blockOfTxs, txs); + using (ArrayPoolListRef txs = new(2, GetTx(TestItem.PrivateKeyA), GetTx(TestItem.PrivateKeyB))) + { + blobTxStorage.AddBlobTransactionsFromBlock(blockOfTxs, txs); + } blobTxStorage.TryGetBlobTransactionsFromBlock(blockOfTxs, out Transaction[]? returnedTxs).Should().BeTrue(); returnedTxs!.Length.Should().Be(2); diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/BeaconHeadersSyncTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/BeaconHeadersSyncTests.cs index 78ccb2a059ea..6676f4ef83b2 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/BeaconHeadersSyncTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/BeaconHeadersSyncTests.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Linq; using System.Threading.Tasks; using FluentAssertions; @@ -150,7 +149,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 +179,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 +213,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 +301,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 08b3311202eb..99c271e5c2df 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 f9e6227d24be..969bc1bb607b 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.Test/TestBlockProcessorInterceptor.Setup.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/TestBlockProcessorInterceptor.Setup.cs index 693cf57e4dfd..5f6716e42aef 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/TestBlockProcessorInterceptor.Setup.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/TestBlockProcessorInterceptor.Setup.cs @@ -6,25 +6,20 @@ using System.Threading; using Nethermind.Consensus.Processing; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Evm.Tracing; namespace Nethermind.Merge.Plugin.Test; -public class TestBranchProcessorInterceptor : IBranchProcessor +public class TestBranchProcessorInterceptor(IBranchProcessor baseBlockProcessor, int delayMs) : IBranchProcessor { - private readonly IBranchProcessor _blockProcessorImplementation; - public int DelayMs { get; set; } + public int DelayMs { get; set; } = delayMs; public Exception? ExceptionToThrow { get; set; } - - public TestBranchProcessorInterceptor(IBranchProcessor baseBlockProcessor, int delayMs) - { - _blockProcessorImplementation = baseBlockProcessor; - DelayMs = delayMs; - } + public ManualResetEventSlim? ProcessingStarted { get; set; } public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlocks, ProcessingOptions processingOptions, IBlockTracer blockTracer, CancellationToken token) { + ProcessingStarted?.Set(); + if (DelayMs > 0) { Thread.Sleep(DelayMs); @@ -35,24 +30,24 @@ public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlo throw ExceptionToThrow; } - return _blockProcessorImplementation.Process(baseBlock, suggestedBlocks, processingOptions, blockTracer, token); + return baseBlockProcessor.Process(baseBlock, suggestedBlocks, processingOptions, blockTracer, token); } public event EventHandler? BlocksProcessing { - add => _blockProcessorImplementation.BlocksProcessing += value; - remove => _blockProcessorImplementation.BlocksProcessing -= value; + add => baseBlockProcessor.BlocksProcessing += value; + remove => baseBlockProcessor.BlocksProcessing -= value; } public event EventHandler? BlockProcessing { - add => _blockProcessorImplementation.BlockProcessing += value; - remove => _blockProcessorImplementation.BlockProcessing -= value; + add => baseBlockProcessor.BlockProcessing += value; + remove => baseBlockProcessor.BlockProcessing -= value; } public event EventHandler? BlockProcessed { - add => _blockProcessorImplementation.BlockProcessed += value; - remove => _blockProcessorImplementation.BlockProcessed -= value; + add => baseBlockProcessor.BlockProcessed += value; + remove => baseBlockProcessor.BlockProcessed -= value; } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/NoBlockImprovementContext.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/NoBlockImprovementContext.cs index a9076dbe7a14..100d6f01e6ce 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/NoBlockImprovementContext.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/NoBlockImprovementContext.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Threading; using System.Threading.Tasks; using Nethermind.Consensus.Producers; using Nethermind.Core; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PostMergeBlockProducer.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PostMergeBlockProducer.cs index 434cc15f4308..2eb07971634d 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PostMergeBlockProducer.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PostMergeBlockProducer.cs @@ -15,40 +15,33 @@ namespace Nethermind.Merge.Plugin.BlockProduction { - public class PostMergeBlockProducer : BlockProducerBase + public class PostMergeBlockProducer( + ITxSource txSource, + IBlockchainProcessor processor, + IBlockTree blockTree, + IWorldState stateProvider, + IGasLimitCalculator gasLimitCalculator, + ISealEngine sealEngine, + ITimestamper timestamper, + ISpecProvider specProvider, + ILogManager logManager, + IBlocksConfig? blocksConfig) + : BlockProducerBase(txSource, + processor, + sealEngine, + blockTree, + stateProvider, + gasLimitCalculator, + timestamper, + specProvider, + logManager, + ConstantDifficulty.Zero, + blocksConfig) { - public PostMergeBlockProducer( - ITxSource txSource, - IBlockchainProcessor processor, - IBlockTree blockTree, - IWorldState stateProvider, - IGasLimitCalculator gasLimitCalculator, - ISealEngine sealEngine, - ITimestamper timestamper, - ISpecProvider specProvider, - ILogManager logManager, - IBlocksConfig? miningConfig) - : base( - txSource, - processor, - sealEngine, - blockTree, - stateProvider, - gasLimitCalculator, - timestamper, - specProvider, - logManager, - ConstantDifficulty.Zero, - miningConfig - ) - { - } - protected override BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAttributes? payloadAttributes = null) { BlockHeader blockHeader = base.PrepareBlockHeader(parent, payloadAttributes); - blockHeader.ExtraData = _blocksConfig.GetExtraDataBytes(); blockHeader.IsPostMerge = true; IReleaseSpec spec = _specProvider.GetSpec(blockHeader); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV1DirectResponse.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV1DirectResponse.cs new file mode 100644 index 000000000000..5135babb86eb --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV1DirectResponse.cs @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Core.Collections; +using Nethermind.JsonRpc; +using Nethermind.Serialization.Json; + +namespace Nethermind.Merge.Plugin.Data; + +/// +/// Wraps an of and writes JSON +/// directly into a , bypassing +/// to avoid extra buffer copies for large blob payloads. +/// +public sealed class BlobsV1DirectResponse : IStreamableResult, IEnumerable, IDisposable +{ + private readonly ArrayPoolList _items; + + public BlobsV1DirectResponse(ArrayPoolList items) + { + _items = items; + } + + public async ValueTask WriteToAsync(PipeWriter writer, CancellationToken cancellationToken) + { + writer.Write("["u8); + + int count = _items.Count; + for (int i = 0; i < count; i++) + { + if (i > 0) writer.Write(","u8); + + BlobAndProofV1? item = _items[i]; + if (item is null) + { + writer.Write("null"u8); + } + else + { + writer.Write("{\"blob\":\"0x"u8); + HexWriter.WriteHexChunked(writer, item.Blob); + writer.Write("\",\"proof\":\"0x"u8); + HexWriter.WriteHexSmall(writer, item.Proof); + writer.Write("\"}"u8); + } + + // Flush after each entry for backpressure + FlushResult flushResult = await writer.FlushAsync(cancellationToken); + if (flushResult.IsCompleted || flushResult.IsCanceled) + return; + } + + writer.Write("]"u8); + } + + public IEnumerator GetEnumerator() => _items.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public void Dispose() => _items.Dispose(); +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV2DirectResponse.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV2DirectResponse.cs new file mode 100644 index 000000000000..ad4048f31294 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV2DirectResponse.cs @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.JsonRpc; +using Nethermind.Serialization.Json; + +namespace Nethermind.Merge.Plugin.Data; + +/// +/// Wraps parallel arrays of blobs and proofs and writes JSON directly into a +/// , bypassing +/// to avoid extra buffer copies for large blob payloads. +/// +public sealed class BlobsV2DirectResponse : IStreamableResult, IEnumerable +{ + private readonly byte[]?[] _blobs; + private readonly ReadOnlyMemory[] _proofs; + private readonly int _count; + + public BlobsV2DirectResponse(byte[]?[] blobs, ReadOnlyMemory[] proofs, int count) + { + Debug.Assert(count <= blobs.Length && count <= proofs.Length, + "count must not exceed array lengths"); + _blobs = blobs; + _proofs = proofs; + _count = count; + } + + public async ValueTask WriteToAsync(PipeWriter writer, CancellationToken cancellationToken) + { + writer.Write("["u8); + + for (int i = 0; i < _count; i++) + { + if (i > 0) writer.Write(","u8); + + byte[]? blob = _blobs[i]; + if (blob is null) + { + writer.Write("null"u8); + } + else + { + writer.Write("{\"blob\":\"0x"u8); + HexWriter.WriteHexChunked(writer, blob); + writer.Write("\",\"proofs\":["u8); + + ReadOnlySpan proofs = _proofs[i].Span; + for (int p = 0; p < proofs.Length; p++) + { + if (p > 0) writer.Write(","u8); + writer.Write("\"0x"u8); + HexWriter.WriteHexSmall(writer, proofs[p]); + writer.Write("\""u8); + } + + writer.Write("]}"u8); + } + + // Flush after each entry for backpressure + FlushResult flushResult = await writer.FlushAsync(cancellationToken); + if (flushResult.IsCompleted || flushResult.IsCanceled) + return; + } + + writer.Write("]"u8); + } + + // Explicit interface implementation: only used by tests via IEnumerable cast. + // Production serialization goes through IStreamableResult.WriteToAsync. + IEnumerator IEnumerable.GetEnumerator() + { + for (int i = 0; i < _count; i++) + { + byte[]? blob = _blobs[i]; + yield return blob is null ? null : new BlobAndProofV2(blob, _proofs[i].ToArray()); + } + } + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/EngineApiJsonContext.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/EngineApiJsonContext.cs new file mode 100644 index 000000000000..912c2421b1fd --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/EngineApiJsonContext.cs @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json.Serialization; +using Nethermind.Consensus.Producers; +using Nethermind.Merge.Plugin.Handlers; + +namespace Nethermind.Merge.Plugin.Data; + +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Metadata, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + IncludeFields = true)] +[JsonSerializable(typeof(ExecutionPayload))] +[JsonSerializable(typeof(ExecutionPayloadV3))] +[JsonSerializable(typeof(PayloadStatusV1))] +[JsonSerializable(typeof(byte[][]))] +[JsonSerializable(typeof(ForkchoiceStateV1))] +[JsonSerializable(typeof(ForkchoiceUpdatedV1Result))] +[JsonSerializable(typeof(PayloadAttributes))] +[JsonSerializable(typeof(BlobAndProofV1))] +[JsonSerializable(typeof(BlobAndProofV2))] +[JsonSerializable(typeof(BlobsBundleV1))] +[JsonSerializable(typeof(BlobsBundleV2))] +[JsonSerializable(typeof(GetPayloadV2Result))] +[JsonSerializable(typeof(GetPayloadV3Result))] +[JsonSerializable(typeof(GetPayloadV4Result))] +[JsonSerializable(typeof(GetPayloadV5Result))] +[JsonSerializable(typeof(GetBlobsHandlerV2Request))] +[JsonSerializable(typeof(ExecutionPayloadBodyV1Result))] +[JsonSerializable(typeof(TransitionConfigurationV1))] +[JsonSerializable(typeof(ClientVersionV1))] +internal partial class EngineApiJsonContext : JsonSerializerContext; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs index d0eae608228a..93ba25cde5b7 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs @@ -137,6 +137,7 @@ public byte[][] Transactions /// true if block created successfully; otherwise, false. public virtual BlockDecodingResult TryGetBlock(UInt256? totalDifficulty = null) { + byte[][] encodedTransactions = Transactions; TransactionDecodingResult transactions = TryGetTransactions(); if (transactions.Error is not null) { @@ -164,11 +165,15 @@ public virtual BlockDecodingResult TryGetBlock(UInt256? totalDifficulty = null) Author = FeeRecipient, IsPostMerge = true, TotalDifficulty = totalDifficulty, - TxRoot = TxTrie.CalculateRoot(transactions.Transactions), + TxRoot = TxTrie.CalculateRoot(encodedTransactions), WithdrawalsRoot = BuildWithdrawalsRoot(), }; - return new BlockDecodingResult(new Block(header, transactions.Transactions, Array.Empty(), Withdrawals)); + Block block = new(header, transactions.Transactions, Array.Empty(), Withdrawals) + { + EncodedTransactions = encodedTransactions + }; + return new BlockDecodingResult(block); } protected virtual Hash256? BuildWithdrawalsRoot() diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceStateV1.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceStateV1.cs index 546e2fe4df9d..1eb47a3018ff 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceStateV1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceStateV1.cs @@ -10,33 +10,26 @@ namespace Nethermind.Merge.Plugin.Data; /// /// /// -public class ForkchoiceStateV1 +public class ForkchoiceStateV1(Hash256 headBlockHash, Hash256 finalizedBlockHash, Hash256 safeBlockHash) { - public ForkchoiceStateV1(Hash256 headBlockHash, Hash256 finalizedBlockHash, Hash256 safeBlockHash) - { - HeadBlockHash = headBlockHash; - FinalizedBlockHash = finalizedBlockHash; - SafeBlockHash = safeBlockHash; - } - /// /// Hash of the head of the canonical chain. /// - public Hash256 HeadBlockHash { get; set; } + public Hash256 HeadBlockHash { get; set; } = headBlockHash; /// /// Safe block hash of the canonical chain under certain synchrony and honesty assumptions. This value MUST be either equal to or an ancestor of headBlockHash. /// /// Can be when transition block is not finalized yet. - public Hash256 SafeBlockHash { get; set; } + public Hash256 SafeBlockHash { get; set; } = safeBlockHash; /// /// Hash of the most recent finalized block /// /// Can be when transition block is not finalized yet. - public Hash256 FinalizedBlockHash { get; set; } + public Hash256 FinalizedBlockHash { get; set; } = finalizedBlockHash; - public override string ToString() => $"ForkChoice: {HeadBlockHash.ToShortString()}, Safe: {SafeBlockHash.ToShortString()}, Finalized: {FinalizedBlockHash.ToShortString()}"; + public override string ToString() => $"ForkChoice: {HeadBlockHash}, Safe: {SafeBlockHash}, Finalized: {FinalizedBlockHash}"; public string ToString(long? headNumber, long? safeNumber, long? finalizedNumber) => headNumber is null || safeNumber is null || finalizedNumber is null ? ToString() diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceUpdatedV1Result.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceUpdatedV1Result.cs index 70fac33c3fbe..9a00701af1a6 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 319924d88368..1932720394e6 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; @@ -101,7 +101,7 @@ public ValidationResult ValidateParams(IReleaseSpec spec, int version, out strin return ValidationResult.Fail; } - executionPayload.ParentBeaconBlockRoot = new Hash256(parentBeaconBlockRoot); + executionPayload.ParentBeaconBlockRoot = parentBeaconBlockRoot; error = null; return ValidationResult.Success; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Osaka.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Osaka.cs index fe52ec00e629..fcc4877246c9 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 8ffde88cf208..59989242ae3b 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 126011ca6b83..c72cccbb3ab1 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 ee951fa0a52b..9d0edf55a74d 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs @@ -34,7 +34,7 @@ public EngineRpcModule( IHandler transitionConfigurationHandler, IHandler, IEnumerable> capabilitiesHandler, IAsyncHandler> getBlobsHandler, - IAsyncHandler?> getBlobsHandlerV2, + IAsyncHandler?> getBlobsHandlerV2, IEngineRequestsTracker engineRequestsTracker, ISpecProvider specProvider, GCKeeper gcKeeper, diff --git a/src/Nethermind/Nethermind.Merge.Plugin/GC/GCKeeper.cs b/src/Nethermind/Nethermind.Merge.Plugin/GC/GCKeeper.cs index a581d7e6e05d..6062bbb67ba0 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 42d8012f590e..d71d267db7fb 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 3a92c3e366c0..043b09109d8a 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 fcc3ea994b0b..3aee165f6e62 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 123ddd1a6f34..00e60cf50262 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs @@ -53,6 +53,7 @@ public EngineRpcCapabilitiesProvider(ISpecProvider specProvider) // 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); } return _capabilities; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeTransitionConfigurationV1Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeTransitionConfigurationV1Handler.cs index 5d19260b0084..e00f06fe20d3 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/GetBlobsHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandler.cs index 4a0dcf8b7e87..05cc9936d00a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandler.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Nethermind.Core.Collections; using Nethermind.Core.Specs; using Nethermind.JsonRpc; using Nethermind.Merge.Plugin.Data; @@ -27,35 +28,41 @@ public class GetBlobsHandler(ITxPool txPool, IChainHeadSpecProvider chainHeadSpe return ResultWrapper>.Fail(error, MergeErrorCodes.TooLargeRequest); } - return ResultWrapper>.Success(GetBlobsAndProofs(request)); - } - - private IEnumerable GetBlobsAndProofs(byte[][] request) - { bool allBlobsAvailable = true; Metrics.NumberOfRequestedBlobs += request.Length; - foreach (byte[] requestedBlobVersionedHash in request) + ArrayPoolList response = new(request.Length); + try { - if (txPool.TryGetBlobAndProofV0(requestedBlobVersionedHash, out byte[]? blob, out byte[]? proof)) + foreach (byte[] requestedBlobVersionedHash in request) + { + if (txPool.TryGetBlobAndProofV0(requestedBlobVersionedHash, out byte[]? blob, out byte[]? proof)) + { + Metrics.NumberOfSentBlobs++; + response.Add(new BlobAndProofV1(blob, proof)); + } + else + { + allBlobsAvailable = false; + response.Add(null); + } + } + + if (allBlobsAvailable) { - Metrics.NumberOfSentBlobs++; - yield return new BlobAndProofV1(blob, proof); + Metrics.GetBlobsRequestsSuccessTotal++; } else { - allBlobsAvailable = false; - yield return null; + Metrics.GetBlobsRequestsFailureTotal++; } - } - if (allBlobsAvailable) - { - Metrics.GetBlobsRequestsSuccessTotal++; + return ResultWrapper>.Success(new BlobsV1DirectResponse(response)); } - else + catch { - Metrics.GetBlobsRequestsFailureTotal++; + response.Dispose(); + throw; } } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs index c04978430f36..341fb547a5f6 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs @@ -1,72 +1,53 @@ // 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 Nethermind.Core.Collections; using Nethermind.JsonRpc; using Nethermind.Merge.Plugin.Data; using Nethermind.TxPool; 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); + string error = $"The number of requested blobs must not exceed {MaxRequest}"; + return ResultWrapper?>.Fail(error, MergeErrorCodes.TooLargeRequest); } - Metrics.GetBlobsRequestsTotal += request.Length; + Metrics.GetBlobsRequestsTotal += request.BlobVersionedHashes.Length; + + int n = request.BlobVersionedHashes.Length; + byte[]?[] blobs = new byte[n][]; + ReadOnlyMemory[] proofs = new ReadOnlyMemory[n]; + int count = txPool.TryGetBlobsAndProofsV1(request.BlobVersionedHashes, blobs, proofs); - var count = txPool.GetBlobCounts(request); 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 != n) { return ReturnEmptyArray(); } - ArrayPoolList response = new(request.Length); - - try - { - foreach (byte[] requestedBlobVersionedHash in request) - { - if (txPool.TryGetBlobAndProofV1(requestedBlobVersionedHash, out byte[]? blob, out byte[][]? cellProofs)) - { - response.Add(new BlobAndProofV2(blob, cellProofs)); - } - else - { - // fail if we were not able to collect full blob data - response.Dispose(); - return ReturnEmptyArray(); - } - } - - Metrics.GetBlobsRequestsSuccessTotal++; - return ResultWrapper?>.Success(response); - } - catch - { - response.Dispose(); - throw; - } + Metrics.GetBlobsRequestsSuccessTotal++; + return ResultWrapper?>.Success(new BlobsV2DirectResponse(blobs, proofs, n)); } - private Task?>> 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/GetPayloadHandlerBase.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadHandlerBase.cs index f358092c5bcd..7a8891d213f1 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadHandlerBase.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadHandlerBase.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Nethermind.Consensus.Processing.CensorshipDetector; using Nethermind.Consensus.Producers; using Nethermind.Core; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs index 7d32ef7f27ae..b45f8b3f88b2 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs @@ -49,7 +49,7 @@ public sealed class NewPayloadHandler : IAsyncHandler? _latestBlocks; + private readonly LruCache? _latestBlocks; private readonly ProcessingOptions _defaultProcessingOptions; private readonly TimeSpan _timeout; @@ -161,7 +161,7 @@ public async Task> HandleAsync(ExecutionPayload r { if (!_blockValidator.ValidateOrphanedBlock(block!, out string? error)) { - if (_logger.IsWarn) _logger.Warn(InvalidBlockHelper.GetMessage(block, "orphaned block is invalid")); + if (_logger.IsWarn) _logger.Warn(InvalidBlockHelper.GetMessage(block, $"orphaned block is invalid: {error}")); return NewPayloadV1Result.Invalid(null, $"Invalid block without parent: {error}."); } @@ -363,7 +363,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 }; @@ -386,6 +386,11 @@ ValidationResult TryCacheResult(ValidationResult result, string? errorMessage) await _processingQueue.Enqueue(block, processingOptions); (result, validationMessage) = await blockProcessed.Task.TimeoutOn(timeoutTask, cts); } + else + { + // Already known block with known processing result, cancel the timeout task + cts.Cancel(); + } } catch (TimeoutException) { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Osaka.cs b/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Osaka.cs index b2cd12fe96fb..bd05aa9e0c49 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 c737e54c232b..d2b5ce16536e 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 220a25c97055..f5a82770d3d0 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/MergeFinalizedStateProvider.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergeFinalizedStateProvider.cs new file mode 100644 index 000000000000..f2484cc65761 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergeFinalizedStateProvider.cs @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Consensus; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Merge.Plugin.Handlers; +using Nethermind.Trie.Pruning; + +namespace Nethermind.Merge.Plugin; + +public class MergeFinalizedStateProvider(IPoSSwitcher poSSwitcher, IBlockCacheService blockCacheService, IBlockTree blockTree, IFinalizedStateProvider baseFinalizedStateProvider) : IFinalizedStateProvider +{ + public long FinalizedBlockNumber + { + get + { + if (poSSwitcher.TransitionFinished) + { + BlockHeader? currentFinalized = null; + if (blockTree.FinalizedHash is { } blockTreeFinalizedHash) + { + currentFinalized = blockTree.FindHeader(blockTreeFinalizedHash, BlockTreeLookupOptions.None); + } + + // Finalized hash from blocktree is not updated until it is processed, which is a problem for long + // catchup. So we use from blockCacheService as a backup. + if (blockCacheService.FinalizedHash is { } blockCacheFinalizedHash) + { + BlockHeader? fromBlockCache = blockTree.FindHeader(blockCacheFinalizedHash); + if (fromBlockCache is not null) + { + if (currentFinalized is null || fromBlockCache.Number > currentFinalized.Number) + { + currentFinalized = fromBlockCache; + } + } + } + + return currentFinalized?.Number ?? baseFinalizedStateProvider.FinalizedBlockNumber; + } + + return baseFinalizedStateProvider.FinalizedBlockNumber; + } + } + + public Hash256? GetFinalizedStateRootAt(long blockNumber) + { + if (FinalizedBlockNumber < blockNumber) return null; + return baseFinalizedStateProvider.GetFinalizedStateRootAt(blockNumber); + } +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergeHeaderValidator.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergeHeaderValidator.cs index fcd4bf923cfd..cf108162ce76 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergeHeaderValidator.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergeHeaderValidator.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Blockchain; -using Nethermind.Blockchain.Find; using Nethermind.Consensus; using Nethermind.Consensus.Validators; using Nethermind.Core; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index 92d6a2961e0b..756e9d571add 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net.Http; using System.Threading.Tasks; using Autofac; using Autofac.Core; @@ -22,7 +21,6 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Exceptions; -using Nethermind.Core.Timers; using Nethermind.Db; using Nethermind.Facade.Proxy; using Nethermind.HealthChecks; @@ -42,6 +40,7 @@ using Nethermind.State; using Nethermind.Synchronization; using Nethermind.Synchronization.ParallelSync; +using Nethermind.Trie.Pruning; using Nethermind.TxPool; namespace Nethermind.Merge.Plugin; @@ -70,6 +69,7 @@ public partial class MergePlugin(ChainSpec chainSpec, IMergeConfig mergeConfig) public virtual Task Init(INethermindApi nethermindApi) { _api = nethermindApi; + EthereumJsonSerializer.AddTypeInfoResolver(EngineApiJsonContext.Default); _syncConfig = nethermindApi.Config(); _blocksConfig = nethermindApi.Config(); _txPoolConfig = nethermindApi.Config(); @@ -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); } @@ -308,6 +308,8 @@ protected override void Load(ContainerBuilder builder) .AddDecorator() + .AddDecorator() + .AddKeyedSingleton(ITxValidator.HeadTxValidatorKey, new HeadTxValidator()) // Engine rpc related @@ -328,7 +330,7 @@ protected override void Load(ContainerBuilder builder) .AddSingleton, IEnumerable>, ExchangeCapabilitiesHandler>() .AddSingleton() .AddSingleton>, GetBlobsHandler>() - .AddSingleton?>, GetBlobsHandlerV2>() + .AddSingleton?>, GetBlobsHandlerV2>() .AddSingleton() .AddSingleton((ctx) => @@ -340,6 +342,7 @@ protected override void Load(ContainerBuilder builder) : NoGCStrategy.Instance, ctx.Resolve()); }) + .AddSingleton() ; } @@ -355,11 +358,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 752459cfcd32..809c2c29137b 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/NoEngineRequestTracker.cs b/src/Nethermind/Nethermind.Merge.Plugin/NoEngineRequestTracker.cs new file mode 100644 index 000000000000..e0d1b606bd30 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/NoEngineRequestTracker.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading.Tasks; +using Nethermind.Api; + +namespace Nethermind.Merge.Plugin; + +public class NoEngineRequestsTracker : IEngineRequestsTracker +{ + public void OnForkchoiceUpdatedCalled() { } + + public void OnNewPayloadCalled() { } + + public Task StartAsync() + => Task.CompletedTask; +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/BeaconHeadersSyncFeed.cs b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/BeaconHeadersSyncFeed.cs index eaf122d7f2a5..4a5ed73063b5 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.Merge.Plugin/Synchronization/BeaconPivot.cs b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/BeaconPivot.cs index 412d0f2d4449..f99553be0c2e 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/BeaconPivot.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/BeaconPivot.cs @@ -10,7 +10,6 @@ using Nethermind.Core.Crypto; using Nethermind.Crypto; using Nethermind.Db; -using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Serialization.Rlp; using Nethermind.Synchronization; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/MergeBlockDownloader.cs b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/MergeBlockDownloader.cs index 5b3338f55067..a951e0a952f2 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/MergeBlockDownloader.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/MergeBlockDownloader.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Nethermind.Blockchain; using Nethermind.Blockchain.Receipts; using Nethermind.Blockchain.Synchronization; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/StartingSyncPivotUpdater.cs b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/StartingSyncPivotUpdater.cs index b06239d528e2..ba17aeb0284a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/StartingSyncPivotUpdater.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/StartingSyncPivotUpdater.cs @@ -4,13 +4,11 @@ using System; using System.Threading; using System.Threading.Tasks; -using Autofac.Features.AttributeFilters; using Nethermind.Blockchain; using Nethermind.Blockchain.Synchronization; using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Db; using Nethermind.Logging; using Nethermind.Merge.Plugin.Handlers; using Nethermind.State.Snap; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/UnsafeStartingSyncPivotUpdater.cs b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/UnsafeStartingSyncPivotUpdater.cs index 07da003c1893..3ab5abeb501e 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/UnsafeStartingSyncPivotUpdater.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/UnsafeStartingSyncPivotUpdater.cs @@ -9,7 +9,6 @@ using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; -using Nethermind.Db; using Nethermind.Logging; using Nethermind.Merge.Plugin.Handlers; using Nethermind.Synchronization; diff --git a/src/Nethermind/Nethermind.Merkleization/Merkle.cs b/src/Nethermind/Nethermind.Merkleization/Merkle.cs index a16074877f38..9d70ff0da3d3 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 f5f0c4f73174..aef4d5bd102c 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.Mining.Test/MiningConfigTest.cs b/src/Nethermind/Nethermind.Mining.Test/MiningConfigTest.cs deleted file mode 100644 index a2a70e877404..000000000000 --- a/src/Nethermind/Nethermind.Mining.Test/MiningConfigTest.cs +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Text; -using Nethermind.Config; -using Nethermind.Core.Exceptions; -using NUnit.Framework; - -namespace Nethermind.Mining.Test; - -public class MiningConfigTest -{ - [TestCase] - [TestCase("")] - [TestCase("1, 2, 3, 4, 5")] - [TestCase("Other Extra data")] - - public void Test(string data = "Nethermind") - { - IBlocksConfig config = new BlocksConfig(); - byte[] dataBytes = Encoding.UTF8.GetBytes(data); - config.ExtraData = data; - - Assert.That(data, Is.EqualTo(config.ExtraData)); - Assert.That(dataBytes, Is.EqualTo(config.GetExtraDataBytes())); - } - - [Test] - public void TestTooLongExtraData() - { - string data = "1234567890" + - "1234567890" + - "1234567890" + - "1234567890"; - - IBlocksConfig config = new BlocksConfig(); - string defaultData = config.ExtraData; - byte[] defaultDataBytes = Encoding.UTF8.GetBytes(defaultData); - byte[] dataBytes = Encoding.UTF8.GetBytes(data); - - Assert.That(dataBytes.Length, Is.GreaterThan(32)); - Assert.Throws(() => config.ExtraData = data); //throw on update - Assert.That(defaultData, Is.EqualTo(config.ExtraData)); // Keep previous one - Assert.That(defaultDataBytes, Is.EqualTo(config.GetExtraDataBytes())); - } -} diff --git a/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs b/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs index 3da65ed50807..e21d3b810dfa 100644 --- a/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs +++ b/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs @@ -8,6 +8,8 @@ using System.Linq; using System.Reflection; using System.Runtime.Serialization; +using System.Threading; +using System.Threading.Tasks; using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Attributes; @@ -43,13 +45,16 @@ 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; [System.ComponentModel.Description("A test description")] [DetailedMetric] public static long DetailedMetric { get; set; } + + [DetailedMetricOnFlag] + public static bool DetailedMetricsEnabled { get; set; } } public enum SomeEnum @@ -131,6 +136,7 @@ public void Load_DetailedMetric(bool enableDetailedMetric) Dictionary updater = metricsController._individualUpdater; string metricName = "TestMetrics.DetailedMetric"; Assert.That(updater.ContainsKey(metricName), Is.EqualTo(enableDetailedMetric)); + Assert.That(TestMetrics.DetailedMetricsEnabled, Is.EqualTo(enableDetailedMetric)); } [Test] @@ -172,6 +178,45 @@ public void Register_and_update_metrics_should_not_throw_exception() }); } + [Test] + public void UpdateAllMetrics_does_not_throw_when_registration_is_concurrent() + { + MetricsConfig metricsConfig = new() { Enabled = true }; + MetricsController metricsController = new(metricsConfig); + + using CancellationTokenSource cts = new(TimeSpan.FromSeconds(2)); + CancellationToken ct = cts.Token; + + // Continuously call UpdateAllMetrics on one thread while registering metrics on another + Task updater = Task.Run(() => + { + while (!ct.IsCancellationRequested) + { + metricsController.UpdateAllMetrics(); + } + }); + + Task registrar = Task.Run(() => + { + Type[] types = + [ + typeof(TestMetrics), + typeof(Blockchain.Metrics), + typeof(Evm.Metrics), + typeof(Network.Metrics), + typeof(Db.Metrics), + ]; + + for (int i = 0; !ct.IsCancellationRequested; i++) + { + metricsController.RegisterMetrics(types[i % types.Length]); + metricsController.AddMetricsUpdateAction(() => { }); + } + }); + + Assert.DoesNotThrowAsync(() => Task.WhenAll(updater, registrar)); + } + [Test] public void All_config_items_have_descriptions() { @@ -202,13 +247,14 @@ private static void ForEachProperty(Action verifier) PropertyInfo[] properties = metricsType.GetProperties(BindingFlags.Static | BindingFlags.Public); foreach (PropertyInfo property in properties) { + if (property.GetCustomAttribute() is not null) continue; try { verifier(property); } - catch (Exception e) + catch (AssertionException e) { - throw new Exception(property.Name, e); + throw new AssertionException($"{property.Name}: {e.Message}", e); } } } diff --git a/src/Nethermind/Nethermind.Monitoring/Config/IMetricsConfig.cs b/src/Nethermind/Nethermind.Monitoring/Config/IMetricsConfig.cs index 83ac5bcd7e50..0eb30fade851 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 77e194cc7f85..c9dc00e6f7df 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 f8d13fc417f4..d023c62503a7 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 a5ed5783c919..e9a2ab9ee212 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 e2a53d15ba91..1f8661324615 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,10 @@ 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(); + private volatile IMetricUpdater[][] _updaterValues = []; // Largely for testing reason internal readonly Dictionary _individualUpdater = new(); @@ -41,7 +41,7 @@ public partial class MetricsController : IMetricsController private readonly bool _useCounters; private readonly bool _enableDetailedMetric; - private readonly List _callbacks = new(); + private volatile Action[] _callbacks = []; public interface IMetricUpdater { @@ -89,13 +89,21 @@ public void Update() break; case ITuple keyAsTuple: { - using ArrayPoolList labels = new ArrayPoolList(keyAsTuple.Length, keyAsTuple.Length); - for (int i = 0; i < keyAsTuple.Length; i++) + ArrayPoolListRef labels = new(keyAsTuple.Length, keyAsTuple.Length); + try { - labels[i] = keyAsTuple[i]!.ToString()!; + for (int i = 0; i < keyAsTuple.Length; i++) + { + labels[i] = keyAsTuple[i]!.ToString()!; + } + + Update(value, labels.AsSpan()); + } + finally + { + labels.Dispose(); } - Update(value, labels.AsSpan()); break; } default: @@ -149,14 +157,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) @@ -174,7 +174,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); } @@ -188,7 +188,7 @@ private static Gauge CreateMemberInfoMetricsGauge(MemberInfo member, params stri { nameof(ProductInfo.Version), ProductInfo.Version }, { nameof(ProductInfo.Commit), ProductInfo.Commit }, { nameof(ProductInfo.Runtime), ProductInfo.Runtime }, - { nameof(ProductInfo.BuildTimestamp), ProductInfo.BuildTimestamp.ToUnixTimeSeconds().ToString() }, + { nameof(ProductInfo.SourceDate), ProductInfo.SourceDate.ToUnixTimeSeconds().ToString() }, }; private static ObservableInstrument CreateDiagnosticsMetricsObservableGauge(Meter meter, MemberInfo member, Func observer) @@ -206,11 +206,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)) { @@ -222,8 +222,13 @@ 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) + { + (member as PropertyInfo)?.SetValue(null, _enableDetailedMetric); + continue; + } if (member.GetCustomAttribute() is not null && !_enableDetailedMetric) continue; if (TryCreateMetricUpdater(type, meter, member, out IMetricUpdater updater)) { @@ -231,6 +236,7 @@ private void EnsurePropertiesCached(Type type) } } _metricUpdaters[type] = metricUpdaters.ToArray(); + _updaterValues = [.. _metricUpdaters.Values]; } } @@ -327,55 +333,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 _updaterValues) { - 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 = [.. _callbacks, 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 1ee91ac12dba..eac30b0f2fee 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/NethermindKestrelMetricServer.cs b/src/Nethermind/Nethermind.Monitoring/NethermindKestrelMetricServer.cs index f5c1aa3964c9..412a543e0056 100644 --- a/src/Nethermind/Nethermind.Monitoring/NethermindKestrelMetricServer.cs +++ b/src/Nethermind/Nethermind.Monitoring/NethermindKestrelMetricServer.cs @@ -8,7 +8,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core; @@ -69,67 +68,57 @@ public NethermindKestrelMetricServer(KestrelMetricServerOptions options) protected override Task StartServer(CancellationToken cancel) { - var s = _certificate is not null ? "s" : ""; + var s = _certificate is null ? string.Empty : "s"; var hostAddress = $"http{s}://{_hostname}:{_port}"; // If the caller needs to customize any of this, they can just set up their own web host and inject the middleware. - var builder = new WebHostBuilder() - // Explicitly build from UseKestrelCore rather than UseKestrel to - // not add additional transports that we don't use e.g. msquic as that - // adds a lot of additional idle threads to the process. - .UseKestrelCore() - .UseKestrelHttpsConfiguration() - .Configure(app => + var host = new HostBuilder() + .ConfigureWebHost(builder => { - app.UseOutputCache(); - app.UseMetricServer(_configureExporter, _url); - - // If there is any URL prefix, we just redirect people going to root URL to our prefix. - if (!string.IsNullOrWhiteSpace(_url.Trim('/'))) - { - app.MapWhen(context => context.Request.Path.Value?.Trim('/') == "", - configuration => + builder + // Explicitly build from UseKestrelCore rather than UseKestrel to + // not add additional transports that we don't use e.g. msquic as that + // adds a lot of additional idle threads to the process. + .UseKestrelCore() + .UseKestrelHttpsConfiguration() + .Configure(app => + { + app.UseOutputCache(); + app.UseMetricServer(_configureExporter, _url); + + // If there is any URL prefix, we just redirect people going to root URL to our prefix. + if (!string.IsNullOrWhiteSpace(_url.Trim('/'))) { - configuration.Use((HttpContext context, RequestDelegate next) => - { - context.Response.Redirect(_url); - return Task.CompletedTask; - }); - }); + app.MapWhen( + context => string.IsNullOrEmpty(context.Request.Path.Value?.Trim('/')), + appBuilder => appBuilder.Use((HttpContext context, RequestDelegate _) => + { + context.Response.Redirect(_url); + return Task.CompletedTask; + }) + ); + } + }); + + builder.ConfigureServices(services => + services.AddOutputCache(options => + options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromSeconds(1))) + )); + + if (_certificate is not null) + { + builder.ConfigureServices(services => + services.Configure(options => + options.Listen(IPAddress.Any, _port, listenOptions => listenOptions.UseHttps(_certificate)) + )); } - }); - - builder.ConfigureServices(services => - { - services.AddOutputCache(options => - options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromSeconds(1)))); - }); - - if (_certificate is not null) - { - builder.ConfigureServices(services => - { - services.Configure(options => + else { - options.Listen( - IPAddress.Any, - _port, - listenOptions => listenOptions.UseHttps(_certificate) - ); - }); - }); - } - else - { - builder.UseUrls(hostAddress); - } - - IWebHost webHost = builder.Build(); - - // This is what changed - // webHost.Start(); - // return webHost.WaitForShutdownAsync(cancel); + builder.UseUrls(hostAddress); + } + }) + .Build(); - return webHost.RunAsync(cancel); + return host.RunAsync(cancel); } } diff --git a/src/Nethermind/Nethermind.Monitoring/NoopMonitoringService.cs b/src/Nethermind/Nethermind.Monitoring/NoopMonitoringService.cs new file mode 100644 index 000000000000..46f29b552d3c --- /dev/null +++ b/src/Nethermind/Nethermind.Monitoring/NoopMonitoringService.cs @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading.Tasks; + +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.Benchmark/EcdhAgreementBenchmarks.cs b/src/Nethermind/Nethermind.Network.Benchmark/EcdhAgreementBenchmarks.cs index 03389770877c..8f9734c15e06 100644 --- a/src/Nethermind/Nethermind.Network.Benchmark/EcdhAgreementBenchmarks.cs +++ b/src/Nethermind/Nethermind.Network.Benchmark/EcdhAgreementBenchmarks.cs @@ -3,7 +3,6 @@ extern alias BouncyCastle; using System; -using System.Linq; using BenchmarkDotNet.Attributes; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; diff --git a/src/Nethermind/Nethermind.Network.Benchmark/Eth62ProtocolHandlerBenchmarks.cs b/src/Nethermind/Nethermind.Network.Benchmark/Eth62ProtocolHandlerBenchmarks.cs index 1e1572376228..32af78ca82d8 100644 --- a/src/Nethermind/Nethermind.Network.Benchmark/Eth62ProtocolHandlerBenchmarks.cs +++ b/src/Nethermind/Nethermind.Network.Benchmark/Eth62ProtocolHandlerBenchmarks.cs @@ -15,8 +15,6 @@ using Nethermind.Core.Test.Builders; using Nethermind.Core.Timers; using Nethermind.Crypto; -using Nethermind.Db; -using Nethermind.Evm; using Nethermind.Evm.State; using Nethermind.Logging; using Nethermind.Network.P2P; @@ -25,10 +23,8 @@ using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; using Nethermind.Network.Rlpx; using Nethermind.Specs; -using Nethermind.State; using Nethermind.Stats; using Nethermind.Synchronization; -using Nethermind.Trie.Pruning; using Nethermind.TxPool; using NSubstitute; diff --git a/src/Nethermind/Nethermind.Network.Benchmark/InFlowBenchmarks.cs b/src/Nethermind/Nethermind.Network.Benchmark/InFlowBenchmarks.cs index 1a06dbf9cfcc..e9a29e1c87ca 100644 --- a/src/Nethermind/Nethermind.Network.Benchmark/InFlowBenchmarks.cs +++ b/src/Nethermind/Nethermind.Network.Benchmark/InFlowBenchmarks.cs @@ -19,9 +19,9 @@ namespace Nethermind.Network.Benchmarks { public class InFlowBenchmarks { - private static byte[] _input = Bytes.FromHexString("96cb6a910a279cab5bf0e0cd0432c7d1d28f76b794402bd97ba5a7dfa1b0e163fc75cd604bbc43d3c888618db2453158b06548d9bdb1b8208b70e0075d675bbe80268afff07570005454467c0c3e85a3468e3225015521fecb9a0c1a2092fd445f14552e33d4ebb4d7b533606989210b4c702662b4d2417f293b2701ee4336624916cddefb4c3777d1a144e1df66162b60bcaed3319becbbe19062f7e3715505dc84fa23a531d7dfd35b9ad95042a85faa643a40365d52d0b57e67cc428676cc364567a6c8cf7fda48bd4c54fd2c8fad1e504f3c2451f538793c022fcdfaef9d8fdafae2c989a7e61dba88879cc9062b774bc3f999b0cdece9aab3d34fafc1513e2de18e6c297d07a5992cc68adb27ce3de39c290884e2f6f0f8b24645c622b2cd9d396498e689a44dc775c22f4baa6b5c1685c19b020110c5788b0e61ebf1794f05df9237d61ca95f49f00fa0217c6a0935f5674c32f8eb7b9c5fe351d429ba0383761d980b53b1187a9a367f80075c020fcc71a75689a4a9e74b8c6137ab267ce6e697008ff5f8c29af53fd4f97018da197f990181f112be738fd0f57bcfc72a78dda731b64aecdd3f83586f1fcfdbdf2c58eff42b38fed19730a1fd0dd21d248d78a396689a508561590543dbe62b44475bec724f2c13678dc877d6cc15099634c10ee4206b339e45c4829c8d81537c3b57fab792929f79a391c29d4f10f6db8318d9f2bffffdafb3038f005ab5e9ce4bf46960f7cf078cebc4287da0330f6a02555912ce7db52a893e4739af43dd64eb9a70e8b4460da9955c39e5c24d2d35b29999b4c9979ba5acd16abd09427b57ad78db5076b0671fa4bf90a48b863db8a372c99e28a21213246f15cf36470f3a397b9e3cd1b0e4ce4e9ff1f95c5bc4c63c691a960d12e45f452adc7961eaca8dd24a3f51b16c6b15c930500b3c9b9e99638eedbf18332de4f5fa3e948872f46bbe2594de8bf32a1ca36a80a284dc1e055379543bded00cf110bdf5da82f0dc5e5a410c5aa925dd4a7dd2408796f05d3f1ab43c5b30a17e7e392212b0a61eacdfb200a1788e99f85a896e77fe7a86a17e61fdf00da6d9b2aac1935068bb6271a265e6b41aca2662b9beca7005bbbcbb371bb7daf60bf3be282e67ba17cb7d120015a8f7867391197c457e913a9558382f4dfbdd1e8693babf5835707c0142be013ec92869415c772dfc4581c5bb0aaaf0f7dabee7d488ea7e8a8954507e7f04f942429be3dda2317f719f8cf8e68720a91cce20d2406f35347cadb24e4ce3024a70c7938fa14179a9434996f73d666bd9fcb9b0dc02492fdef10a52c39a976058d611d854e6cad01cf85ff380499909638f7ca8f7397cb52b7d3cd9401b5905838b6134e6dbf4a03dff05551043532dbe9833502bba97839964806d55655804eef2317f68caf724f28625bcae7b761fac920d256c78e3843529e4ce7d25861db489128112002df79ee13cdcf5776e0416bdb36c022deba8d5329900da5c9a3bac6d39284691ca2ee684ee7fc9335c5183542b3751199587e9a0a0cad9b7e5365703ceda47d5959548a6a097fe8ff0f1fe8c59ad377db05f11002bacf555d14e99d7ae8cfd7960967384d912ecc3e11cc24cf9849d2592b834fa04ce12e0cb75eb4668ad6d3e43f16cf0dc2c9f54d77877014859b3b74b2bf9489b06682e0bd8f868a7fc34d8e7ff7d2c03435c5a15cc61a0a0791db38a9cc205e9f18e28e1445bfe982e25aeaa196d8dd14d25b65add73760726e15a1f54a414fd9377d2f87d33f87e117731fa6d04377686f33578c8b5481580fd81a74a17e4fb011bdcd9a10ee65ca37ae9eb5e606fde06d95d50880f4732ea6cb81f3d9adc88d43aa15aeadd4e790973f88b0dcdbe18cab73a9309f9e151c21d4025aa1af0fb37fe31ce25ce39c51fdb4e932b45c168111f54398fea71dd3e82ab01499db695e36308bc800246f17393d218a241191086fa02a4d70514e5bb3c5ff229a8320e53e0cd2b605c674dd6006b74a394fcfcf4d8fe0b61c3768b9e53b6601854ebe5dcd210b3db16adf68065a4f60a7dbd3c79776a21f6440cdc263aaa423b11954795d9424e3543b27efbe8dd76e002c30889e872c6d12d82c48933c382347ee89e69cf5d0120b342969312f98a19a8e972a8ef4f78a23701ac80deaf90cbc6b13fddea674790d6e9fe46d054f900b569e624bd50f8c8b20d3cdb29117f0cd28cb3a15c8530fb4827ebd3af739e872b59e6566c928933"); + private static readonly byte[] _input = Bytes.FromHexString("96cb6a910a279cab5bf0e0cd0432c7d1d28f76b794402bd97ba5a7dfa1b0e163fc75cd604bbc43d3c888618db2453158b06548d9bdb1b8208b70e0075d675bbe80268afff07570005454467c0c3e85a3468e3225015521fecb9a0c1a2092fd445f14552e33d4ebb4d7b533606989210b4c702662b4d2417f293b2701ee4336624916cddefb4c3777d1a144e1df66162b60bcaed3319becbbe19062f7e3715505dc84fa23a531d7dfd35b9ad95042a85faa643a40365d52d0b57e67cc428676cc364567a6c8cf7fda48bd4c54fd2c8fad1e504f3c2451f538793c022fcdfaef9d8fdafae2c989a7e61dba88879cc9062b774bc3f999b0cdece9aab3d34fafc1513e2de18e6c297d07a5992cc68adb27ce3de39c290884e2f6f0f8b24645c622b2cd9d396498e689a44dc775c22f4baa6b5c1685c19b020110c5788b0e61ebf1794f05df9237d61ca95f49f00fa0217c6a0935f5674c32f8eb7b9c5fe351d429ba0383761d980b53b1187a9a367f80075c020fcc71a75689a4a9e74b8c6137ab267ce6e697008ff5f8c29af53fd4f97018da197f990181f112be738fd0f57bcfc72a78dda731b64aecdd3f83586f1fcfdbdf2c58eff42b38fed19730a1fd0dd21d248d78a396689a508561590543dbe62b44475bec724f2c13678dc877d6cc15099634c10ee4206b339e45c4829c8d81537c3b57fab792929f79a391c29d4f10f6db8318d9f2bffffdafb3038f005ab5e9ce4bf46960f7cf078cebc4287da0330f6a02555912ce7db52a893e4739af43dd64eb9a70e8b4460da9955c39e5c24d2d35b29999b4c9979ba5acd16abd09427b57ad78db5076b0671fa4bf90a48b863db8a372c99e28a21213246f15cf36470f3a397b9e3cd1b0e4ce4e9ff1f95c5bc4c63c691a960d12e45f452adc7961eaca8dd24a3f51b16c6b15c930500b3c9b9e99638eedbf18332de4f5fa3e948872f46bbe2594de8bf32a1ca36a80a284dc1e055379543bded00cf110bdf5da82f0dc5e5a410c5aa925dd4a7dd2408796f05d3f1ab43c5b30a17e7e392212b0a61eacdfb200a1788e99f85a896e77fe7a86a17e61fdf00da6d9b2aac1935068bb6271a265e6b41aca2662b9beca7005bbbcbb371bb7daf60bf3be282e67ba17cb7d120015a8f7867391197c457e913a9558382f4dfbdd1e8693babf5835707c0142be013ec92869415c772dfc4581c5bb0aaaf0f7dabee7d488ea7e8a8954507e7f04f942429be3dda2317f719f8cf8e68720a91cce20d2406f35347cadb24e4ce3024a70c7938fa14179a9434996f73d666bd9fcb9b0dc02492fdef10a52c39a976058d611d854e6cad01cf85ff380499909638f7ca8f7397cb52b7d3cd9401b5905838b6134e6dbf4a03dff05551043532dbe9833502bba97839964806d55655804eef2317f68caf724f28625bcae7b761fac920d256c78e3843529e4ce7d25861db489128112002df79ee13cdcf5776e0416bdb36c022deba8d5329900da5c9a3bac6d39284691ca2ee684ee7fc9335c5183542b3751199587e9a0a0cad9b7e5365703ceda47d5959548a6a097fe8ff0f1fe8c59ad377db05f11002bacf555d14e99d7ae8cfd7960967384d912ecc3e11cc24cf9849d2592b834fa04ce12e0cb75eb4668ad6d3e43f16cf0dc2c9f54d77877014859b3b74b2bf9489b06682e0bd8f868a7fc34d8e7ff7d2c03435c5a15cc61a0a0791db38a9cc205e9f18e28e1445bfe982e25aeaa196d8dd14d25b65add73760726e15a1f54a414fd9377d2f87d33f87e117731fa6d04377686f33578c8b5481580fd81a74a17e4fb011bdcd9a10ee65ca37ae9eb5e606fde06d95d50880f4732ea6cb81f3d9adc88d43aa15aeadd4e790973f88b0dcdbe18cab73a9309f9e151c21d4025aa1af0fb37fe31ce25ce39c51fdb4e932b45c168111f54398fea71dd3e82ab01499db695e36308bc800246f17393d218a241191086fa02a4d70514e5bb3c5ff229a8320e53e0cd2b605c674dd6006b74a394fcfcf4d8fe0b61c3768b9e53b6601854ebe5dcd210b3db16adf68065a4f60a7dbd3c79776a21f6440cdc263aaa423b11954795d9424e3543b27efbe8dd76e002c30889e872c6d12d82c48933c382347ee89e69cf5d0120b342969312f98a19a8e972a8ef4f78a23701ac80deaf90cbc6b13fddea674790d6e9fe46d054f900b569e624bd50f8c8b20d3cdb29117f0cd28cb3a15c8530fb4827ebd3af739e872b59e6566c928933"); - private IByteBuffer _decoderBuffer = PooledByteBufferAllocator.Default.Buffer(1024 * 1024); + private readonly IByteBuffer _decoderBuffer = PooledByteBufferAllocator.Default.Buffer(1024 * 1024); private NewBlockMessage _outputMessage; private NewBlockMessageSerializer _newBlockMessageSerializer; @@ -57,8 +57,8 @@ public void Setup() public void IterationSetup() { _secrets = NetTestVectors.GetSecretsPair(); - FrameCipher frameCipher = new FrameCipher(_secrets.B.AesSecret); - FrameMacProcessor frameMacProcessor = new FrameMacProcessor(TestItem.IgnoredPublicKey, _secrets.B); + FrameCipher frameCipher = new(_secrets.B.AesSecret); + FrameMacProcessor frameMacProcessor = new(TestItem.IgnoredPublicKey, _secrets.B); _zeroDecoder = new TestZeroDecoder(frameCipher, frameMacProcessor); } @@ -79,7 +79,8 @@ private void SetupAll(bool useLimboOutput = false) ResourceLeakDetector.Level = ResourceLeakDetector.DetectionLevel.Paranoid; } - private class TestZeroDecoder : ZeroFrameDecoder + private class TestZeroDecoder(IFrameCipher frameCipher, FrameMacProcessor frameMacProcessor) + : ZeroFrameDecoder(frameCipher, frameMacProcessor) { public IByteBuffer Decode(IByteBuffer input) { @@ -87,20 +88,10 @@ public IByteBuffer Decode(IByteBuffer input) base.Decode(null, input, result); return (IByteBuffer)result[0]; } - - public TestZeroDecoder(IFrameCipher frameCipher, FrameMacProcessor frameMacProcessor) - : base(frameCipher, frameMacProcessor, LimboLogs.Instance) - { - } } - private class TestZeroMerger : Rlpx.ZeroFrameMerger + private class TestZeroMerger() : ZeroFrameMerger(LimboLogs.Instance) { - public TestZeroMerger() - : base(LimboLogs.Instance) - { - } - public IByteBuffer Decode(IByteBuffer input) { var result = new List(); diff --git a/src/Nethermind/Nethermind.Network.Benchmark/OutFlowBenchmarks.cs b/src/Nethermind/Nethermind.Network.Benchmark/OutFlowBenchmarks.cs index 220e1601ff9c..1188d0d8a9a1 100644 --- a/src/Nethermind/Nethermind.Network.Benchmark/OutFlowBenchmarks.cs +++ b/src/Nethermind/Nethermind.Network.Benchmark/OutFlowBenchmarks.cs @@ -68,26 +68,17 @@ private void SetupAll(bool useLimboOutput = false) ResourceLeakDetector.Level = ResourceLeakDetector.DetectionLevel.Paranoid; } - private class TestZeroEncoder : ZeroFrameEncoder + private class TestZeroEncoder(IFrameCipher frameCipher, IFrameMacProcessor frameMacProcessor) + : ZeroFrameEncoder(frameCipher, frameMacProcessor) { public void Encode(IByteBuffer message, IByteBuffer buffer) { base.Encode(null, message, buffer); } - - public TestZeroEncoder(IFrameCipher frameCipher, IFrameMacProcessor frameMacProcessor) - : base(frameCipher, frameMacProcessor, LimboLogs.Instance) - { - } } private class TestZeroSplitter : ZeroPacketSplitter { - public TestZeroSplitter() - : base(LimboLogs.Instance) - { - } - public void Encode(IByteBuffer input, IByteBuffer output) { base.Encode(null, input, output); diff --git a/src/Nethermind/Nethermind.Network.Contract/Config/INetworkConfig.cs b/src/Nethermind/Nethermind.Network.Contract/Config/INetworkConfig.cs index 2db4e6ddb019..34b286f388d6 100644 --- a/src/Nethermind/Nethermind.Network.Contract/Config/INetworkConfig.cs +++ b/src/Nethermind/Nethermind.Network.Contract/Config/INetworkConfig.cs @@ -76,7 +76,7 @@ public interface INetworkConfig : IConfig uint MaxNettyArenaCount { get; set; } [ConfigItem(DefaultValue = "", Description = "A comma-separated enode list to be used as boot nodes.")] - string Bootnodes { get; set; } + NetworkNode[] Bootnodes { get; set; } [ConfigItem(DefaultValue = "false", Description = "Whether to enable automatic port forwarding via UPnP.")] bool EnableUPnP { get; set; } diff --git a/src/Nethermind/Nethermind.Network.Contract/Messages/PooledTransactionRequestMessage.cs b/src/Nethermind/Nethermind.Network.Contract/Messages/PooledTransactionRequestMessage.cs new file mode 100644 index 000000000000..eb6c45ac5e12 --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Contract/Messages/PooledTransactionRequestMessage.cs @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Crypto; + +namespace Nethermind.Network.Contract.Messages; + +public readonly struct PooledTransactionRequestMessage : INew +{ + public ValueHash256 TxHash { get; init; } + + public static PooledTransactionRequestMessage New(ValueHash256 txHash) => new() { TxHash = txHash }; +} diff --git a/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs b/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs index 4175cc638a81..63f19c0cd136 100644 --- a/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs +++ b/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs @@ -1,45 +1,40 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -namespace Nethermind.Network.Contract.P2P +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"; + /// + /// 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 000000000000..5f2e4e274bbd --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Contract/P2P/ProtocolParser.cs @@ -0,0 +1,81 @@ +// 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 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; + } + 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/DiscoveryManagerTests.cs b/src/Nethermind/Nethermind.Network.Discovery.Test/DiscoveryManagerTests.cs index 02634ee53c36..d5df1bd8876a 100644 --- a/src/Nethermind/Nethermind.Network.Discovery.Test/DiscoveryManagerTests.cs +++ b/src/Nethermind/Nethermind.Network.Discovery.Test/DiscoveryManagerTests.cs @@ -10,6 +10,7 @@ using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; using Nethermind.Core.Timers; using Nethermind.Crypto; @@ -85,10 +86,9 @@ public async Task OnPingMessageTest() //receiving ping IPEndPoint address = new(IPAddress.Parse(Host), Port); _discoveryManager.OnIncomingMsg(new PingMsg(_publicKey, GetExpirationTime(), address, _nodeTable.MasterNode!.Address, new byte[32]) { FarAddress = address }); - await Task.Delay(500); // expecting to send pong - await _msgSender.Received(1).SendMsg(Arg.Is(static m => m.FarAddress!.Address.ToString() == Host && m.FarAddress.Port == Port)); + Assert.That(() => _msgSender.ReceivedCallsMatching(s => s.SendMsg(Arg.Is(static m => m.FarAddress!.Address.ToString() == Host && m.FarAddress.Port == Port))), Is.True.After(500, 10)); // send pings to new node await _msgSender.Received().SendMsg(Arg.Is(static m => m.FarAddress!.Address.ToString() == Host && m.FarAddress.Port == Port)); @@ -179,8 +179,7 @@ public async Task OnNeighborsMessageTest() _discoveryManager.OnIncomingMsg(msg); //expecting to send 3 pings to both nodes - await Task.Delay(600); - await _msgSender.Received(3).SendMsg(Arg.Is(m => m.FarAddress!.Address.ToString() == _nodes[0].Host && m.FarAddress.Port == _nodes[0].Port)); + Assert.That(() => _msgSender.ReceivedCallsMatching(s => s.SendMsg(Arg.Is(m => m.FarAddress!.Address.ToString() == _nodes[0].Host && m.FarAddress.Port == _nodes[0].Port)), 3), Is.True.After(600, 10)); await _msgSender.Received(3).SendMsg(Arg.Is(m => m.FarAddress!.Address.ToString() == _nodes[1].Host && m.FarAddress.Port == _nodes[1].Port)); } diff --git a/src/Nethermind/Nethermind.Network.Discovery.Test/DiscoveryV5AppTests.cs b/src/Nethermind/Nethermind.Network.Discovery.Test/DiscoveryV5AppTests.cs new file mode 100644 index 000000000000..30d7ca59761e --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Discovery.Test/DiscoveryV5AppTests.cs @@ -0,0 +1,122 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Lantern.Discv5.Enr; +using Lantern.Discv5.Enr.Entries; +using Lantern.Discv5.Enr.Identity.V4; +using Nethermind.Config; +using Nethermind.Core.Test.Builders; +using Nethermind.Core.Test.Modules; +using Nethermind.Crypto; +using Nethermind.Db; +using Nethermind.Logging; +using Nethermind.Network.Config; +using Nethermind.Network.Discovery.Discv5; +using Nethermind.Serialization.Rlp; +using NUnit.Framework; +using System.Collections.Generic; +using System.Net; +using ENR = Lantern.Discv5.Enr.Enr; + +namespace Nethermind.Network.Discovery.Test; + +[Parallelizable(ParallelScope.Self)] +[TestFixture] +public class DiscoveryV5AppTests +{ + private MemDb _discoveryDb = null!; + private MemDb _legacyDiscoveryDb = null!; + private IdentityVerifierV4 _identityVerifier = null!; + private DiscoveryV5App _discoveryV5App = null!; + + [OneTimeSetUp] + public void OneTimeSetup() + { + Rlp.RegisterDecoder(typeof(NetworkNode), new NetworkNodeDecoder()); + } + + [SetUp] + public void Setup() + { + _discoveryDb = new MemDb(); + _legacyDiscoveryDb = new MemDb(); + _identityVerifier = new IdentityVerifierV4(); + NetworkConfig networkConfig = new() + { + Bootnodes = [], + ExternalIp = IPAddress.Loopback.ToString() + }; + _discoveryV5App = new DiscoveryV5App( + new InsecureProtectedPrivateKey(TestItem.PrivateKeyF), + new FixedIpResolver(networkConfig), + networkConfig, + new DiscoveryConfig { }, + _discoveryDb, + _legacyDiscoveryDb, + LimboLogs.Instance + ); + } + + [TearDown] + public void Teardown() + { + _discoveryDb.Dispose(); + _legacyDiscoveryDb.Dispose(); + } + + private ENR CreateTestEnrBytes(Nethermind.Crypto.PrivateKey privateKey) + { + IdentitySignerV4 signer = new(privateKey.KeyBytes); + + ENR enr = new EnrBuilder() + .WithIdentityScheme(_identityVerifier, signer) + .WithEntry(EnrEntryKey.Id, new EntryId("v4")) + .WithEntry(EnrEntryKey.Ip, new EntryIp(IPAddress.Loopback)) + .WithEntry(EnrEntryKey.Secp256K1, new EntrySecp256K1(signer.PublicKey)) + .WithEntry(EnrEntryKey.Tcp, new EntryTcp(30303)) + .WithEntry(EnrEntryKey.Udp, new EntryUdp(30303)) + .Build(); + + return enr; + } + + [Test] + public void Should_Migrate_Correctly() + { + PrivateKey testPrivateKey1 = TestItem.PrivateKeyA; + ENR enr1 = CreateTestEnrBytes(testPrivateKey1); + _legacyDiscoveryDb[enr1.NodeId] = enr1.EncodeRecord(); + + PrivateKey testPrivateKey2 = TestItem.PrivateKeyB; + ENR enr2 = CreateTestEnrBytes(testPrivateKey2); + _legacyDiscoveryDb[enr2.NodeId] = enr2.EncodeRecord(); + + List loadedEnrs = _discoveryV5App.LoadStoredEnrs(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(loadedEnrs, Has.Count.EqualTo(2), "Should get all records"); + Assert.That(_legacyDiscoveryDb, Has.Count.EqualTo(0), "Legacy DB should be empty"); + Assert.That(_discoveryDb, Has.Count.EqualTo(2), "DB should contain all items migrated"); + } + } + + [Test] + public void Should_Stop_Migration_From_V4_DB() + { + NetworkNode enode1 = new(TestItem.PublicKeyA, IPAddress.Loopback.ToString(), 1, 1); + _legacyDiscoveryDb[enode1.NodeId.Bytes] = Rlp.Encode(enode1).Bytes; + + NetworkNode enode2 = new(TestItem.PublicKeyB, IPAddress.Loopback.ToString(), 1, 1); + _legacyDiscoveryDb[enode2.NodeId.Bytes] = Rlp.Encode(enode2).Bytes; + + List loadedEnrs = _discoveryV5App.LoadStoredEnrs(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(loadedEnrs, Has.Count.EqualTo(0), "Should not load any nodes if legacy DB contains enodes"); + Assert.That(_legacyDiscoveryDb, Has.Count.EqualTo(2), "Legacy DB should not be changed"); + Assert.That(_discoveryDb, Has.Count.EqualTo(0), "DB should not load any records"); + } + } +} diff --git a/src/Nethermind/Nethermind.Network.Discovery.Test/E2EDiscoveryTests.cs b/src/Nethermind/Nethermind.Network.Discovery.Test/E2EDiscoveryTests.cs index 478aa8b2b9b6..7d0ff87eda8a 100644 --- a/src/Nethermind/Nethermind.Network.Discovery.Test/E2EDiscoveryTests.cs +++ b/src/Nethermind/Nethermind.Network.Discovery.Test/E2EDiscoveryTests.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System; @@ -26,17 +26,18 @@ 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 /// private IContainer CreateNode(PrivateKey nodeKey, IEnode? bootEnode = null) { - IConfigProvider configProvider = new ConfigProvider(); - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + ConfigProvider configProvider = new(); + var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance); ChainSpec spec = loader.LoadEmbeddedOrFromFile("chainspec/foundation.json"); spec.Bootnodes = []; + if (bootEnode is not null) { spec.Bootnodes = [new(bootEnode.PublicKey, bootEnode.HostIp.ToString(), bootEnode.Port)]; @@ -69,6 +70,8 @@ private int AssignDiscoveryIp() } [Test] + [Retry(3)] + [Parallelizable(ParallelScope.None)] public async Task TestDiscovery() { if (discoveryVersion == DiscoveryVersion.V5) Assert.Ignore("DiscV5 does not seems to work."); @@ -96,7 +99,8 @@ public async Task TestDiscovery() HashSet expectedKeys = new HashSet(nodeKeys); expectedKeys.Remove(node.Resolve().PublicKey); - Assert.That(() => pool.Peers.Values.Select((p) => p.Node.Id).ToHashSet(), Is.EquivalentTo(expectedKeys).After(1000, 100)); + Assert.That(() => pool.Peers.Values.Select((p) => p.Node.Id).ToHashSet(), + Is.EquivalentTo(expectedKeys).After(5000, 100)); } } } diff --git a/src/Nethermind/Nethermind.Network.Discovery.Test/NettyDiscoveryHandlerTests.cs b/src/Nethermind/Nethermind.Network.Discovery.Test/NettyDiscoveryHandlerTests.cs index 554a31ecbde5..50872657b884 100644 --- a/src/Nethermind/Nethermind.Network.Discovery.Test/NettyDiscoveryHandlerTests.cs +++ b/src/Nethermind/Nethermind.Network.Discovery.Test/NettyDiscoveryHandlerTests.cs @@ -1,8 +1,8 @@ // 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; using System.Threading.Tasks; using DotNetty.Buffers; @@ -10,7 +10,6 @@ using DotNetty.Transport.Bootstrapping; using DotNetty.Transport.Channels; using DotNetty.Transport.Channels.Sockets; -using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Test.Builders; using Nethermind.Core.Test.Modules; @@ -137,7 +136,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 +145,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.Test/NettyDiscoveryV5HandlerTests.cs b/src/Nethermind/Nethermind.Network.Discovery.Test/NettyDiscoveryV5HandlerTests.cs index b6d57d2be50d..943aff94cfca 100644 --- a/src/Nethermind/Nethermind.Network.Discovery.Test/NettyDiscoveryV5HandlerTests.cs +++ b/src/Nethermind/Nethermind.Network.Discovery.Test/NettyDiscoveryV5HandlerTests.cs @@ -70,7 +70,7 @@ public async Task ForwardsReceivedMessageToReader() _handler.ChannelRead(ctx, new DatagramPacket(Unpooled.WrappedBuffer(data), from, to)); - (await enumerator.MoveNextAsync(cancellationSource.Token)).Should().BeTrue(); + (await enumerator.MoveNextAsync()).Should().BeTrue(); UdpReceiveResult forwardedPacket = enumerator.Current; forwardedPacket.Should().NotBeNull(); @@ -99,9 +99,9 @@ public async Task SkipsMessagesOfInvalidSize(int size) _handler.ChannelRead(ctx, new DatagramPacket(Unpooled.WrappedBuffer((byte[])invalidData.Clone()), from, to)); _handler.Close(); - (await enumerator.MoveNextAsync(cancellationSource.Token)).Should().BeTrue(); + (await enumerator.MoveNextAsync()).Should().BeTrue(); enumerator.Current.Buffer.Should().BeEquivalentTo(data); - (await enumerator.MoveNextAsync(cancellationSource.Token)).Should().BeFalse(); + (await enumerator.MoveNextAsync()).Should().BeFalse(); } } } diff --git a/src/Nethermind/Nethermind.Network.Discovery/DiscoveryApp.cs b/src/Nethermind/Nethermind.Network.Discovery/DiscoveryApp.cs index 5baf1803e812..7474d2a2ad39 100644 --- a/src/Nethermind/Nethermind.Network.Discovery/DiscoveryApp.cs +++ b/src/Nethermind/Nethermind.Network.Discovery/DiscoveryApp.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.Net.NetworkInformation; @@ -267,7 +267,8 @@ private async Task OnChannelActivated(CancellationToken cancellationToken) private async Task InitializeBootnodes(CancellationToken cancellationToken) { - NetworkNode[] bootnodes = NetworkNode.ParseNodes(_discoveryConfig.Bootnodes, _logger); + NetworkNode[] bootnodes = _networkConfig.Bootnodes; + if (bootnodes.Length == 0) { if (_logger.IsWarn) _logger.Warn("No bootnodes specified in configuration"); @@ -278,6 +279,13 @@ private async Task InitializeBootnodes(CancellationToken cancellationToken for (int i = 0; i < bootnodes.Length; i++) { NetworkNode bootnode = bootnodes[i]; + + if (!bootnode.IsEnode) + { + if (_logger.IsTrace) _logger.Trace($"Ignoring ENR in discovery V4: {bootnode}"); + continue; + } + if (bootnode.NodeId is null) { _logger.Warn($"Bootnode ignored because of missing node ID: {bootnode}"); diff --git a/src/Nethermind/Nethermind.Network.Discovery/DiscoveryConfig.cs b/src/Nethermind/Nethermind.Network.Discovery/DiscoveryConfig.cs index 680393ddce11..96dd847ceeb6 100644 --- a/src/Nethermind/Nethermind.Network.Discovery/DiscoveryConfig.cs +++ b/src/Nethermind/Nethermind.Network.Discovery/DiscoveryConfig.cs @@ -1,6 +1,8 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Config; + namespace Nethermind.Network.Discovery; public class DiscoveryConfig : IDiscoveryConfig @@ -39,7 +41,9 @@ public class DiscoveryConfig : IDiscoveryConfig public int MaxOutgoingMessagePerSecond { get; set; } = 100; - public string Bootnodes { get; set; } = string.Empty; + public NetworkNode[] Bootnodes { get; set; } = []; + + public bool UseDefaultDiscv5Bootnodes { get; set; } = true; public DiscoveryVersion DiscoveryVersion { get; set; } = DiscoveryVersion.V4; } diff --git a/src/Nethermind/Nethermind.Network.Discovery/Discv5/DiscoveryV5App.cs b/src/Nethermind/Nethermind.Network.Discovery/Discv5/DiscoveryV5App.cs index 55746e2c8505..9202764a9e3f 100644 --- a/src/Nethermind/Nethermind.Network.Discovery/Discv5/DiscoveryV5App.cs +++ b/src/Nethermind/Nethermind.Network.Discovery/Discv5/DiscoveryV5App.cs @@ -1,10 +1,6 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Diagnostics.CodeAnalysis; -using System.Net; -using System.Runtime.CompilerServices; -using System.Threading.Channels; using Autofac.Features.AttributeFilters; using DotNetty.Transport.Channels; using Lantern.Discv5.Enr; @@ -18,8 +14,8 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using NBitcoin.Secp256k1; -using Nethermind.Config; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.ServiceStopper; using Nethermind.Crypto; @@ -27,32 +23,43 @@ using Nethermind.Logging; using Nethermind.Network.Config; using Nethermind.Stats.Model; +using System.Diagnostics.CodeAnalysis; +using System.Net; +using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Threading.Channels; +using ENR = Lantern.Discv5.Enr.Enr; + +[assembly: InternalsVisibleTo("Nethermind.Network.Discovery.Test")] namespace Nethermind.Network.Discovery.Discv5; -public class DiscoveryV5App : IDiscoveryApp +public sealed class DiscoveryV5App : IDiscoveryApp { private readonly IDiscv5Protocol _discv5Protocol; private readonly Logging.ILogger _logger; private readonly IDb _discoveryDb; + private readonly IDb _legacyDiscoveryDb; + private readonly ILogManager _logManager; private readonly CancellationTokenSource _appShutdownSource = new(); - private readonly DiscoveryReport? _discoveryReport; + private DiscoveryV5Report? _discoveryReport; private readonly IServiceProvider _serviceProvider; private readonly SessionOptions _sessionOptions; + private readonly EnrFactory _enrFactory; public DiscoveryV5App( [KeyFilter(IProtectedPrivateKey.NodeKey)] IProtectedPrivateKey nodeKey, - IIPResolver? ipResolver, + IIPResolver ipResolver, INetworkConfig networkConfig, IDiscoveryConfig discoveryConfig, - [KeyFilter(DbNames.DiscoveryNodes)] IDb discoveryDb, + [KeyFilter(DbNames.DiscoveryV5Nodes)] IDb discoveryDb, + [KeyFilter(DbNames.DiscoveryNodes)] IDb legacyDiscoveryDb, ILogManager logManager) { - ArgumentNullException.ThrowIfNull(ipResolver); - _logger = logManager.GetClassLogger(); _discoveryDb = discoveryDb; - + _legacyDiscoveryDb = legacyDiscoveryDb; + _logManager = logManager; IdentityVerifierV4 identityVerifier = new(); PrivateKey privateKey = nodeKey.Unprotect(); @@ -63,34 +70,17 @@ public DiscoveryV5App( SessionKeys = new SessionKeys(privateKey.KeyBytes), }; - string[] bootstrapNodes = [.. (discoveryConfig.Bootnodes ?? "").Split(",", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Distinct()]; - IServiceCollection services = new ServiceCollection() .AddSingleton() .AddSingleton(_sessionOptions.Verifier) .AddSingleton(_sessionOptions.Signer); - EnrFactory enrFactory = new(new EnrEntryRegistry()); + _enrFactory = new EnrFactory(new EnrEntryRegistry()); - Lantern.Discv5.Enr.Enr[] bootstrapEnrs = [ - .. bootstrapNodes.Where(e => e.StartsWith("enode:")) - .Select(e => new Enode(e)) - .Select(GetEnr), - .. bootstrapNodes.Where(e => e.StartsWith("enr:")).Select(enr => enrFactory.CreateFromString(enr, identityVerifier)), - // TODO: Move to routing table's UpdateFromEnr - .. _discoveryDb.GetAllValues().Select(enr => - { - try - { - return enrFactory.CreateFromBytes(enr, identityVerifier); - } - catch (Exception e) - { - if (_logger.IsWarn) _logger.Warn($"unable to decode enr {e}"); - return null; - } - }) - .Where(enr => enr != null)! + ENR[] bootstrapEnrs = [ + .. networkConfig.Bootnodes.Select(bn => bn.ToEnr(_sessionOptions.Verifier, _sessionOptions.Signer)), + .. discoveryConfig.UseDefaultDiscv5Bootnodes ? GetDefaultDiscv5Bootnodes().Select(ToEnr) : [], + .. LoadStoredEnrs(), ]; EnrBuilder enrBuilder = new EnrBuilder() @@ -120,8 +110,22 @@ .. _discoveryDb.GetAllValues().Select(enr => _discv5Protocol = NetworkHelper.HandlePortTakenError(discv5Builder.Build, networkConfig.DiscoveryPort); _serviceProvider = discv5Builder.GetServiceProvider(); - _discoveryReport = new DiscoveryReport(_discv5Protocol, logManager, _appShutdownSource.Token); } + private static string[] GetDefaultDiscv5Bootnodes() => + JsonSerializer.Deserialize(typeof(DiscoveryV5App).Assembly.GetManifestResourceStream("Nethermind.Network.Discovery.Discv5.discv5-bootnodes.json")!) ?? []; + + private ENR ToEnr(string enrString) => _enrFactory.CreateFromString(enrString, _sessionOptions.Verifier!); + + private ENR ToEnr(byte[] enrBytes) => _enrFactory.CreateFromBytes(enrBytes, _sessionOptions.Verifier!); + + private ENR ToEnr(Node node) => new EnrBuilder() + .WithIdentityScheme(_sessionOptions.Verifier!, _sessionOptions.Signer!) + .WithEntry(EnrEntryKey.Id, new EntryId("v4")) + .WithEntry(EnrEntryKey.Ip, new EntryIp(node.Address.Address)) + .WithEntry(EnrEntryKey.Secp256K1, new EntrySecp256K1(node.Id.PrefixedBytes)) + .WithEntry(EnrEntryKey.Tcp, new EntryTcp(node.Address.Port)) + .WithEntry(EnrEntryKey.Udp, new EntryUdp(node.Address.Port)) + .Build(); private bool TryGetNodeFromEnr(IEnr enr, [NotNullWhen(true)] out Node? node) { @@ -170,23 +174,56 @@ private bool TryGetNodeFromEnr(IEnr enr, [NotNullWhen(true)] out Node? node) return true; } - private Lantern.Discv5.Enr.Enr GetEnr(Enode node) => new EnrBuilder() - .WithIdentityScheme(_sessionOptions.Verifier!, _sessionOptions.Signer!) - .WithEntry(EnrEntryKey.Id, new EntryId("v4")) - .WithEntry(EnrEntryKey.Ip, new EntryIp(node.HostIp)) - .WithEntry(EnrEntryKey.Secp256K1, new EntrySecp256K1(Context.Instance.CreatePubKey(node.PublicKey.PrefixedBytes).ToBytes(false))) - .WithEntry(EnrEntryKey.Tcp, new EntryTcp(node.Port)) - .WithEntry(EnrEntryKey.Udp, new EntryUdp(node.DiscoveryPort)) - .Build(); + internal List LoadStoredEnrs() + { + List enrs = [.. _discoveryDb.GetAllValues().Select(ToEnr)]; - private Lantern.Discv5.Enr.Enr GetEnr(Node node) => new EnrBuilder() - .WithIdentityScheme(_sessionOptions.Verifier!, _sessionOptions.Signer!) - .WithEntry(EnrEntryKey.Id, new EntryId("v4")) - .WithEntry(EnrEntryKey.Ip, new EntryIp(node.Address.Address)) - .WithEntry(EnrEntryKey.Secp256K1, new EntrySecp256K1(node.Id.PrefixedBytes)) - .WithEntry(EnrEntryKey.Tcp, new EntryTcp(node.Address.Port)) - .WithEntry(EnrEntryKey.Udp, new EntryUdp(node.Address.Port)) - .Build(); + if (enrs.Count is not 0) + { + return enrs; + } + + IWriteBatch? migrateBatch = null; + IWriteBatch? deleteBatch = null; + + try + { + foreach (KeyValuePair kv in _legacyDiscoveryDb.GetAll()) + { + if (kv.Value is null) + { + continue; + } + + try + { + ENR enr = ToEnr(kv.Value); + + if (enrs.Count is 0) + { + migrateBatch = _discoveryDb.StartWriteBatch(); + deleteBatch = _legacyDiscoveryDb.StartWriteBatch(); + } + + enrs.Add(enr); + migrateBatch![enr.NodeId] = kv.Value; + deleteBatch![kv.Key] = null; + } + catch + { + // The database has enodes only + return []; + } + } + } + finally + { + migrateBatch?.Dispose(); + deleteBatch?.Dispose(); + } + + return enrs; + } public event EventHandler? NodeRemoved { add { } remove { } } @@ -200,67 +237,80 @@ public void InitializeChannel(IChannel channel) public async Task StartAsync() { await _discv5Protocol.InitAsync(); + if (_logger.IsDebug) _logger.Debug($"Initially discovered {_discv5Protocol.GetActiveNodes.Count()} active peers, {_discv5Protocol.GetAllNodes.Count()} in total."); + + _discoveryReport = new DiscoveryV5Report(_discv5Protocol, _logManager, _appShutdownSource.Token); } 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(); } } } @@ -272,32 +322,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; } @@ -314,6 +373,7 @@ public async Task StopAsync() _discoveryDb.Clear(); IWriteBatch? batch = null; + try { foreach (IEnr enr in activeNodeEnrs) @@ -327,15 +387,15 @@ public async Task StopAsync() batch?.Dispose(); } - try { await _discv5Protocol.StopAsync(); } catch (Exception ex) { - if (_logger.IsWarn) _logger.Warn($"Err stopping discv5: {ex}"); + if (_logger.IsWarn) _logger.Warn($"Error when attempting to stop discv5: {ex}"); } + await _appShutdownSource.CancelAsync(); } @@ -343,7 +403,7 @@ public async Task StopAsync() public void AddNodeToDiscovery(Node node) { - var routingTable = _serviceProvider.GetRequiredService(); - routingTable.UpdateFromEnr(GetEnr(node)); + IRoutingTable routingTable = _serviceProvider.GetRequiredService(); + routingTable.UpdateFromEnr(ToEnr(node)); } } diff --git a/src/Nethermind/Nethermind.Network.Discovery/Discv5/DiscoveryReport.cs b/src/Nethermind/Nethermind.Network.Discovery/Discv5/DiscoveryV5Report.cs similarity index 75% rename from src/Nethermind/Nethermind.Network.Discovery/Discv5/DiscoveryReport.cs rename to src/Nethermind/Nethermind.Network.Discovery/Discv5/DiscoveryV5Report.cs index 49f8adc05722..ed9da22a9d58 100644 --- a/src/Nethermind/Nethermind.Network.Discovery/Discv5/DiscoveryReport.cs +++ b/src/Nethermind/Nethermind.Network.Discovery/Discv5/DiscoveryV5Report.cs @@ -1,18 +1,19 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using Lantern.Discv5.WireProtocol; using Nethermind.Logging; namespace Nethermind.Network.Discovery.Discv5; -internal class DiscoveryReport + +internal class DiscoveryV5Report { int RecentlyChecked = 0; int TotalChecked = 0; - public DiscoveryReport(IDiscv5Protocol discv5Protocol, ILogManager logManager, CancellationToken token) + public DiscoveryV5Report(IDiscv5Protocol discv5Protocol, ILogManager logManager, CancellationToken token) { - ILogger logger = logManager.GetClassLogger(); + ILogger logger = logManager.GetClassLogger(); if (!logger.IsDebug) { return; diff --git a/src/Nethermind/Nethermind.Network.Discovery/Discv5/NettyDiscoveryV5Handler.cs b/src/Nethermind/Nethermind.Network.Discovery/Discv5/NettyDiscoveryV5Handler.cs index ea7d25a16f64..1cb6e9a4e910 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/Discv5/NetworkNodeExtensions.cs b/src/Nethermind/Nethermind.Network.Discovery/Discv5/NetworkNodeExtensions.cs new file mode 100644 index 000000000000..62053cb01754 --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Discovery/Discv5/NetworkNodeExtensions.cs @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Lantern.Discv5.Enr; +using Lantern.Discv5.Enr.Entries; +using Lantern.Discv5.Enr.Identity; +using NBitcoin.Secp256k1; +using Nethermind.Config; + +namespace Nethermind.Network.Discovery.Discv5; + +public static class NetworkNodeExtensions +{ + public static Lantern.Discv5.Enr.Enr ToEnr(this NetworkNode node, IIdentityVerifier verifier, IIdentitySigner signer) + { + if (node.IsEnr) return node.Enr; + + Enode enode = node.Enode; + return new EnrBuilder() + .WithIdentityScheme(verifier, signer) + .WithEntry(EnrEntryKey.Id, new EntryId("v4")) + .WithEntry(EnrEntryKey.Ip, new EntryIp(enode.HostIp)) + .WithEntry(EnrEntryKey.Secp256K1, new EntrySecp256K1(Context.Instance.CreatePubKey(enode.PublicKey.PrefixedBytes).ToBytes(false))) + .WithEntry(EnrEntryKey.Tcp, new EntryTcp(enode.Port)) + .WithEntry(EnrEntryKey.Udp, new EntryUdp(enode.DiscoveryPort)) + .Build(); + } +} diff --git a/src/Nethermind/Nethermind.Network.Discovery/Discv5/discv5-bootnodes.json b/src/Nethermind/Nethermind.Network.Discovery/Discv5/discv5-bootnodes.json new file mode 100644 index 000000000000..3612dfd4de8d --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Discovery/Discv5/discv5-bootnodes.json @@ -0,0 +1,17 @@ +[ + "enr:-KG4QMOEswP62yzDjSwWS4YEjtTZ5PO6r65CPqYBkgTTkrpaedQ8uEUo1uMALtJIvb2w_WWEVmg5yt1UAuK1ftxUU7QDhGV0aDKQu6TalgMAAAD__________4JpZIJ2NIJpcIQEnfA2iXNlY3AyNTZrMaEDfol8oLr6XJ7FsdAYE7lpJhKMls4G_v6qQOGKJUWGb_uDdGNwgiMog3VkcIIjKA", + "enr:-KG4QF4B5WrlFcRhUU6dZETwY5ZzAXnA0vGC__L1Kdw602nDZwXSTs5RFXFIFUnbQJmhNGVU6OIX7KVrCSTODsz1tK4DhGV0aDKQu6TalgMAAAD__________4JpZIJ2NIJpcIQExNYEiXNlY3AyNTZrMaECQmM9vp7KhaXhI-nqL_R0ovULLCFSFTa9CPPSdb1zPX6DdGNwgiMog3VkcIIjKA", + "enr:-Ku4QImhMc1z8yCiNJ1TyUxdcfNucje3BGwEHzodEZUan8PherEo4sF7pPHPSIB1NNuSg5fZy7qFsjmUKs2ea1Whi0EBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQOVphkDqal4QzPMksc5wnpuC3gvSC8AfbFOnZY_On34wIN1ZHCCIyg", + "enr:-Ku4QP2xDnEtUXIjzJ_DhlCRN9SN99RYQPJL92TMlSv7U5C1YnYLjwOQHgZIUXw6c-BvRg2Yc2QsZxxoS_pPRVe0yK8Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMeFF5GrS7UZpAH2Ly84aLK-TyvH-dRo0JM1i8yygH50YN1ZHCCJxA", + "enr:-Ku4QPp9z1W4tAO8Ber_NQierYaOStqhDqQdOPY3bB3jDgkjcbk6YrEnVYIiCBbTxuar3CzS528d2iE7TdJsrL-dEKoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMw5fqqkw2hHC4F5HZZDPsNmPdB1Gi8JPQK7pRc9XHh-oN1ZHCCKvg", + "enr:-Le4QPUXJS2BTORXxyx2Ia-9ae4YqA_JWX3ssj4E_J-3z1A-HmFGrU8BpvpqhNabayXeOZ2Nq_sbeDgtzMJpLLnXFgAChGV0aDKQtTA_KgEAAAAAIgEAAAAAAIJpZIJ2NIJpcISsaa0Zg2lwNpAkAIkHAAAAAPA8kv_-awoTiXNlY3AyNTZrMaEDHAD2JKYevx89W0CcFJFiskdcEzkH_Wdv9iW42qLK79ODdWRwgiMohHVkcDaCI4I", + "enr:-Le4QLHZDSvkLfqgEo8IWGG96h6mxwe_PsggC20CL3neLBjfXLGAQFOPSltZ7oP6ol54OvaNqO02Rnvb8YmDR274uq8ChGV0aDKQtTA_KgEAAAAAIgEAAAAAAIJpZIJ2NIJpcISLosQxg2lwNpAqAX4AAAAAAPA8kv_-ax65iXNlY3AyNTZrMaEDBJj7_dLFACaxBfaI8KZTh_SSJUjhyAyfshimvSqo22WDdWRwgiMohHVkcDaCI4I", + "enr:-Le4QH6LQrusDbAHPjU_HcKOuMeXfdEB5NJyXgHWFadfHgiySqeDyusQMvfphdYWOzuSZO9Uq2AMRJR5O4ip7OvVma8BhGV0aDKQtTA_KgEAAAAAIgEAAAAAAIJpZIJ2NIJpcISLY9ncg2lwNpAkAh8AgQIBAAAAAAAAAAmXiXNlY3AyNTZrMaECDYCZTZEksF-kmgPholqgVt8IXr-8L7Nu7YrZ7HUpgxmDdWRwgiMohHVkcDaCI4I", + "enr:-Le4QIqLuWybHNONr933Lk0dcMmAB5WgvGKRyDihy1wHDIVlNuuztX62W51voT4I8qD34GcTEOTmag1bcdZ_8aaT4NUBhGV0aDKQtTA_KgEAAAAAIgEAAAAAAIJpZIJ2NIJpcISLY04ng2lwNpAkAh8AgAIBAAAAAAAAAA-fiXNlY3AyNTZrMaEDscnRV6n1m-D9ID5UsURk0jsoKNXt1TIrj8uKOGW6iluDdWRwgiMohHVkcDaCI4I", + "enr:-Ku4QHqVeJ8PPICcWk1vSn_XcSkjOkNiTg6Fmii5j6vUQgvzMc9L1goFnLKgXqBJspJjIsB91LTOleFmyWWrFVATGngBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhAMRHkWJc2VjcDI1NmsxoQKLVXFOhp2uX6jeT0DvvDpPcU8FWMjQdR4wMuORMhpX24N1ZHCCIyg", + "enr:-Ku4QG-2_Md3sZIAUebGYT6g0SMskIml77l6yR-M_JXc-UdNHCmHQeOiMLbylPejyJsdAPsTHJyjJB2sYGDLe0dn8uYBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhBLY-NyJc2VjcDI1NmsxoQORcM6e19T1T9gi7jxEZjk_sjVLGFscUNqAY9obgZaxbIN1ZHCCIyg", + "enr:-Ku4QPn5eVhcoF1opaFEvg1b6JNFD2rqVkHQ8HApOKK61OIcIXD127bKWgAtbwI7pnxx6cDyk_nI88TrZKQaGMZj0q0Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDayLMaJc2VjcDI1NmsxoQK2sBOLGcUb4AwuYzFuAVCaNHA-dy24UuEKkeFNgCVCsIN1ZHCCIyg", + "enr:-Ku4QEWzdnVtXc2Q0ZVigfCGggOVB2Vc1ZCPEc6j21NIFLODSJbvNaef1g4PxhPwl_3kax86YPheFUSLXPRs98vvYsoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDZBrP2Jc2VjcDI1NmsxoQM6jr8Rb1ktLEsVcKAPa08wCsKUmvoQ8khiOl_SLozf9IN1ZHCCIyg", + "enr:-LK4QA8FfhaAjlb_BXsXxSfiysR7R52Nhi9JBt4F8SPssu8hdE1BXQQEtVDC3qStCW60LSO7hEsVHv5zm8_6Vnjhcn0Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhAN4aBKJc2VjcDI1NmsxoQJerDhsJ-KxZ8sHySMOCmTO6sHM3iCFQ6VMvLTe948MyYN0Y3CCI4yDdWRwgiOM", + "enr:-LK4QKWrXTpV9T78hNG6s8AM6IO4XH9kFT91uZtFg1GcsJ6dKovDOr1jtAAFPnS2lvNltkOGA9k29BUN7lFh_sjuc9QBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhANAdd-Jc2VjcDI1NmsxoQLQa6ai7y9PMN5hpLe5HmiJSlYzMuzP7ZhwRiwHvqNXdoN0Y3CCI4yDdWRwgiOM" +] diff --git a/src/Nethermind/Nethermind.Network.Discovery/IDiscoveryConfig.cs b/src/Nethermind/Nethermind.Network.Discovery/IDiscoveryConfig.cs index 0dd3d41c207a..5b619ee61509 100644 --- a/src/Nethermind/Nethermind.Network.Discovery/IDiscoveryConfig.cs +++ b/src/Nethermind/Nethermind.Network.Discovery/IDiscoveryConfig.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Config; @@ -83,7 +83,7 @@ public interface IDiscoveryConfig : IConfig /// /// Boot nodes connection details /// - string Bootnodes { get; set; } + NetworkNode[] Bootnodes { get; set; } /// /// Timeout for closing UDP channel in milliseconds @@ -111,4 +111,11 @@ public interface IDiscoveryConfig : IConfig [ConfigItem(Description = "Discovery version(s) to enable", DefaultValue = "All", HiddenFromDocs = true)] DiscoveryVersion DiscoveryVersion { get; set; } + + /// + /// When discv5 is enabled, use well known discv5 bootnodes, in addition to provided by user. + /// See Nethermind.Network.Discovery/Discv5/discv5-bootnodes.json + /// + [ConfigItem(Description = "Whether to use well-known discv5 bootnodes, in addition to those provided by the user.", DefaultValue = "true")] + public bool UseDefaultDiscv5Bootnodes { get; set; } } diff --git a/src/Nethermind/Nethermind.Network.Discovery/Nethermind.Network.Discovery.csproj b/src/Nethermind/Nethermind.Network.Discovery/Nethermind.Network.Discovery.csproj index ee01ab7f32e1..63a3cddbf7a8 100644 --- a/src/Nethermind/Nethermind.Network.Discovery/Nethermind.Network.Discovery.csproj +++ b/src/Nethermind/Nethermind.Network.Discovery/Nethermind.Network.Discovery.csproj @@ -5,6 +5,11 @@ enable + + + + + @@ -15,6 +20,7 @@ + diff --git a/src/Nethermind/Nethermind.Network.Discovery/NettyDiscoveryBaseHandler.cs b/src/Nethermind/Nethermind.Network.Discovery/NettyDiscoveryBaseHandler.cs index 41b7f0de9843..d733d4fe6a17 100644 --- a/src/Nethermind/Nethermind.Network.Discovery/NettyDiscoveryBaseHandler.cs +++ b/src/Nethermind/Nethermind.Network.Discovery/NettyDiscoveryBaseHandler.cs @@ -1,29 +1,28 @@ // 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; namespace Nethermind.Network.Discovery; -public abstract class NettyDiscoveryBaseHandler : SimpleChannelInboundHandler +public abstract class NettyDiscoveryBaseHandler(ILogManager? logManager) : SimpleChannelInboundHandler { - private readonly ILogger _logger; + private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); // https://github.com/ethereum/devp2p/blob/master/discv4.md#wire-protocol // https://github.com/ethereum/devp2p/blob/master/discv5/discv5-wire.md#udp-communication protected const int MaxPacketSize = 1280; - protected NettyDiscoveryBaseHandler(ILogManager? logManager) - { - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - } - 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.Discovery/Serializers/IDiscoveryMsgSerializersProvider.cs b/src/Nethermind/Nethermind.Network.Discovery/Serializers/IDiscoveryMsgSerializersProvider.cs deleted file mode 100644 index ea094cec0d7e..000000000000 --- a/src/Nethermind/Nethermind.Network.Discovery/Serializers/IDiscoveryMsgSerializersProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Network.Discovery.Serializers; - -public interface IDiscoveryMsgSerializersProvider -{ - void RegisterDiscoverySerializers(); -} diff --git a/src/Nethermind/Nethermind.Network.Discovery/Serializers/NeighborsMsgSerializer.cs b/src/Nethermind/Nethermind.Network.Discovery/Serializers/NeighborsMsgSerializer.cs index 2fac4ab14313..46c95bedcc2f 100644 --- a/src/Nethermind/Nethermind.Network.Discovery/Serializers/NeighborsMsgSerializer.cs +++ b/src/Nethermind/Nethermind.Network.Discovery/Serializers/NeighborsMsgSerializer.cs @@ -12,13 +12,17 @@ namespace Nethermind.Network.Discovery.Serializers; -public class NeighborsMsgSerializer : DiscoveryMsgSerializerBase, IZeroInnerMessageSerializer +public class NeighborsMsgSerializer( + IEcdsa ecdsa, + [KeyFilter(IProtectedPrivateKey.NodeKey)] + IPrivateKeyGenerator nodeKey, + INodeIdResolver nodeIdResolver) + : DiscoveryMsgSerializerBase(ecdsa, nodeKey, nodeIdResolver), IZeroInnerMessageSerializer { private static readonly Func _decodeItem = static ctx => { int lastPosition = ctx.ReadSequenceLength() + ctx.Position; int count = ctx.PeekNumberOfItemsRemaining(lastPosition); - ReadOnlySpan ip = ctx.DecodeByteArraySpan(); IPEndPoint address = GetAddress(ip, ctx.DecodeInt()); if (count > 3) @@ -30,12 +34,6 @@ public class NeighborsMsgSerializer : DiscoveryMsgSerializerBase, IZeroInnerMess return new Node(new PublicKey(id), address); }; - public NeighborsMsgSerializer(IEcdsa ecdsa, - [KeyFilter(IProtectedPrivateKey.NodeKey)] IPrivateKeyGenerator nodeKey, - INodeIdResolver nodeIdResolver) : base(ecdsa, nodeKey, nodeIdResolver) - { - } - public void Serialize(IByteBuffer byteBuffer, NeighborsMsg msg) { (int totalLength, int contentLength, int nodesContentLength) = GetLength(msg); diff --git a/src/Nethermind/Nethermind.Network.Discovery/Serializers/PongMsgSerializer.cs b/src/Nethermind/Nethermind.Network.Discovery/Serializers/PongMsgSerializer.cs index 3b6f58174241..4bc929cee9c4 100644 --- a/src/Nethermind/Nethermind.Network.Discovery/Serializers/PongMsgSerializer.cs +++ b/src/Nethermind/Nethermind.Network.Discovery/Serializers/PongMsgSerializer.cs @@ -40,9 +40,9 @@ public void Serialize(IByteBuffer byteBuffer, PongMsg msg) public PongMsg Deserialize(IByteBuffer msgBytes) { - (PublicKey FarPublicKey, _, IByteBuffer Data) = PrepareForDeserialization(msgBytes); + (PublicKey farPublicKey, _, IByteBuffer data) = PrepareForDeserialization(msgBytes); - NettyRlpStream rlp = new(Data); + NettyRlpStream rlp = new(data); rlp.ReadSequenceLength(); rlp.ReadSequenceLength(); @@ -54,7 +54,7 @@ public PongMsg Deserialize(IByteBuffer msgBytes) byte[] token = rlp.DecodeByteArray(); long expirationTime = rlp.DecodeLong(); - PongMsg msg = new(FarPublicKey, expirationTime, token); + PongMsg msg = new(farPublicKey, expirationTime, token); return msg; } diff --git a/src/Nethermind/Nethermind.Network.Discovery/TalkReqAndRespHandler.cs b/src/Nethermind/Nethermind.Network.Discovery/TalkReqAndRespHandler.cs index 5761acb52b66..60e64ce05e6d 100644 --- a/src/Nethermind/Nethermind.Network.Discovery/TalkReqAndRespHandler.cs +++ b/src/Nethermind/Nethermind.Network.Discovery/TalkReqAndRespHandler.cs @@ -2,13 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using Lantern.Discv5.WireProtocol.Messages; -using Lantern.Discv5.WireProtocol.Messages.Requests; -using Lantern.Discv5.WireProtocol.Messages.Responses; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Nethermind.Network.Discovery; diff --git a/src/Nethermind/Nethermind.Network.Dns.Test/EnrDiscoveryTests.cs b/src/Nethermind/Nethermind.Network.Dns.Test/EnrDiscoveryTests.cs index b0a622ee1027..071dd2bc9697 100644 --- a/src/Nethermind/Nethermind.Network.Dns.Test/EnrDiscoveryTests.cs +++ b/src/Nethermind/Nethermind.Network.Dns.Test/EnrDiscoveryTests.cs @@ -12,7 +12,6 @@ using Nethermind.Network.Config; using Nethermind.Network.Enr; using Nethermind.Stats.Model; -using NSubstitute; using NUnit.Framework; namespace Nethermind.Network.Dns.Test; @@ -48,9 +47,9 @@ 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")) + await foreach (string record in crawler.SearchTree("all.mainnet.ethdisco.net", default)) { NodeRecord nodeRecord = parser.ParseRecord(record); if (!nodeRecord.Snap) diff --git a/src/Nethermind/Nethermind.Network.Dns.Test/EnrTreeParserTests.cs b/src/Nethermind/Nethermind.Network.Dns.Test/EnrTreeParserTests.cs index 2131e3e2c4d4..bd7ff19ab824 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.Dns/EnrDiscovery.cs b/src/Nethermind/Nethermind.Network.Dns/EnrDiscovery.cs index 7fca222bcb85..6c14966aa33f 100644 --- a/src/Nethermind/Nethermind.Network.Dns/EnrDiscovery.cs +++ b/src/Nethermind/Nethermind.Network.Dns/EnrDiscovery.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Diagnostics.CodeAnalysis; using System.Net; using System.Runtime.CompilerServices; using DnsClient; @@ -35,7 +34,7 @@ public async IAsyncEnumerable DiscoverNodes([EnumeratorCancellation] Cance if (string.IsNullOrWhiteSpace(_domain)) yield break; IByteBuffer buffer = NethermindBuffers.Default.Buffer(); - await using ConfiguredCancelableAsyncEnumerable.Enumerator enumerator = _crawler.SearchTree(_domain) + await using ConfiguredCancelableAsyncEnumerable.Enumerator enumerator = _crawler.SearchTree(_domain, cancellationToken) .WithCancellation(cancellationToken) .GetAsyncEnumerator(); diff --git a/src/Nethermind/Nethermind.Network.Dns/EnrTreeCrawler.cs b/src/Nethermind/Nethermind.Network.Dns/EnrTreeCrawler.cs index 7b2e626fce5d..a4894a540e19 100644 --- a/src/Nethermind/Nethermind.Network.Dns/EnrTreeCrawler.cs +++ b/src/Nethermind/Nethermind.Network.Dns/EnrTreeCrawler.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Runtime.CompilerServices; using Nethermind.Logging; namespace Nethermind.Network.Dns; @@ -13,7 +14,7 @@ public EnrTreeCrawler(ILogger logger) { _logger = logger; } - public IAsyncEnumerable SearchTree(string domain) + public IAsyncEnumerable SearchTree(string domain, CancellationToken cancellationToken = default) { if (domain.StartsWith("enrtree://", StringComparison.OrdinalIgnoreCase)) { @@ -33,33 +34,34 @@ public IAsyncEnumerable SearchTree(string domain) } DnsClient client = new(domain); SearchContext searchContext = new(string.Empty); - return SearchTree(client, searchContext); + return SearchTree(client, searchContext, cancellationToken); } - private async IAsyncEnumerable SearchTree(DnsClient client, SearchContext searchContext) + private async IAsyncEnumerable SearchTree(DnsClient client, SearchContext searchContext, [EnumeratorCancellation] CancellationToken cancellationToken = default) { while (searchContext.RefsToVisit.Count > 0) { + cancellationToken.ThrowIfCancellationRequested(); string reference = searchContext.RefsToVisit.Dequeue(); - await foreach (string nodeRecordText in SearchNode(client, reference, searchContext)) + await foreach (string nodeRecordText in SearchNode(client, reference, searchContext, cancellationToken).WithCancellation(cancellationToken)) { yield return nodeRecordText; } } } - private async IAsyncEnumerable SearchNode(IDnsClient client, string query, SearchContext searchContext) + private async IAsyncEnumerable SearchNode(IDnsClient client, string query, SearchContext searchContext, [EnumeratorCancellation] CancellationToken cancellationToken = default) { if (searchContext.VisitedRefs.Add(query)) { - IEnumerable lookupResult = await client.Lookup(query); + IEnumerable lookupResult = await client.Lookup(query, cancellationToken); foreach (string node in lookupResult) { EnrTreeNode treeNode = EnrTreeParser.ParseNode(node); foreach (string link in treeNode.Links) { DnsClient linkedTreeLookup = new(link); - await foreach (string nodeRecordText in SearchTree(linkedTreeLookup, searchContext)) + await foreach (string nodeRecordText in SearchTree(linkedTreeLookup, searchContext, cancellationToken).WithCancellation(cancellationToken)) { yield return nodeRecordText; } diff --git a/src/Nethermind/Nethermind.Network.Dns/IDnsClient.cs b/src/Nethermind/Nethermind.Network.Dns/IDnsClient.cs index f0cd4cec44f5..903432477b95 100644 --- a/src/Nethermind/Nethermind.Network.Dns/IDnsClient.cs +++ b/src/Nethermind/Nethermind.Network.Dns/IDnsClient.cs @@ -8,7 +8,7 @@ namespace Nethermind.Network.Dns; public interface IDnsClient { - Task> Lookup(string query); + Task> Lookup(string query, CancellationToken cancellationToken = default); } public class DnsClient : IDnsClient @@ -22,7 +22,7 @@ public DnsClient(string domain) _client = new(); } - public async Task> Lookup(string query) + public async Task> Lookup(string query, CancellationToken cancellationToken = default) { if (_client.NameServers.Count == 0) { @@ -31,7 +31,7 @@ public async Task> Lookup(string query) string queryString = $"{(string.IsNullOrWhiteSpace(query) ? string.Empty : (query + "."))}{_domain}"; DnsQuestion rootQuestion = new(queryString, QueryType.TXT); - IDnsQueryResponse response = await _client.QueryAsync(rootQuestion, CancellationToken.None); + IDnsQueryResponse response = await _client.QueryAsync(rootQuestion, cancellationToken); return response.Answers.OfType().Select(static txt => string.Join(string.Empty, txt.Text)); } } diff --git a/src/Nethermind/Nethermind.Network.Enr/EthEntry.cs b/src/Nethermind/Nethermind.Network.Enr/EthEntry.cs index a3c1e80976a3..6cf94537d1cf 100644 --- a/src/Nethermind/Nethermind.Network.Enr/EthEntry.cs +++ b/src/Nethermind/Nethermind.Network.Enr/EthEntry.cs @@ -8,10 +8,8 @@ namespace Nethermind.Network.Enr; /// /// https://github.com/ethereum/devp2p/blob/master/enr-entries/eth.md /// -public class EthEntry : EnrContentEntry +public class EthEntry(byte[] forkHash, long nextBlock) : EnrContentEntry(new ForkId(forkHash, nextBlock)) { - public EthEntry(byte[] forkHash, long nextBlock) : base(new ForkId(forkHash, nextBlock)) { } - public override string Key => EnrContentKey.Eth; protected override int GetRlpLengthOfValue() diff --git a/src/Nethermind/Nethermind.Network.Enr/NodeRecord.cs b/src/Nethermind/Nethermind.Network.Enr/NodeRecord.cs index cb2ad4565d99..3524c5b78dd5 100644 --- a/src/Nethermind/Nethermind.Network.Enr/NodeRecord.cs +++ b/src/Nethermind/Nethermind.Network.Enr/NodeRecord.cs @@ -22,7 +22,7 @@ public class NodeRecord private Hash256? _contentHash; - private SortedDictionary Entries { get; } = new(); + private SortedDictionary Entries { get; } = new(System.StringComparer.Ordinal); /// /// This field is used when this is deserialized and an unknown entry is encountered. @@ -185,7 +185,7 @@ private void EncodeContent(RlpStream rlpStream) int contentLength = GetContentLengthWithoutSignature(); rlpStream.StartSequence(contentLength); rlpStream.Encode(EnrSequence); - foreach ((_, EnrContentEntry contentEntry) in Entries.OrderBy(static e => e.Key)) + foreach ((_, EnrContentEntry contentEntry) in Entries) { contentEntry.Encode(rlpStream); } @@ -216,7 +216,7 @@ public void Encode(RlpStream rlpStream) rlpStream.StartSequence(contentLength); rlpStream.Encode(Signature!.Bytes); rlpStream.Encode(EnrSequence); // a different sequence here (not RLP sequence) - foreach ((_, EnrContentEntry contentEntry) in Entries.OrderBy(static e => e.Key)) + foreach ((_, EnrContentEntry contentEntry) in Entries) { contentEntry.Encode(rlpStream); } diff --git a/src/Nethermind/Nethermind.Network.Enr/NodeRecordSigner.cs b/src/Nethermind/Nethermind.Network.Enr/NodeRecordSigner.cs index 94840f38ec1a..b5610d3d0008 100644 --- a/src/Nethermind/Nethermind.Network.Enr/NodeRecordSigner.cs +++ b/src/Nethermind/Nethermind.Network.Enr/NodeRecordSigner.cs @@ -45,7 +45,7 @@ public NodeRecord Deserialize(RlpStream rlpStream) throw new NetworkingException("RLP received for ENR is bigger than 300 bytes", NetworkExceptionType.Discovery); NodeRecord nodeRecord = new(); - ReadOnlySpan sigBytes = rlpStream.DecodeByteArraySpan(); + ReadOnlySpan sigBytes = rlpStream.DecodeByteArraySpan(RlpLimit.L65); Signature signature = new(sigBytes, 0); bool canVerify = true; diff --git a/src/Nethermind/Nethermind.Network.Stats/Model/CapabilityConverter.cs b/src/Nethermind/Nethermind.Network.Stats/Model/CapabilityConverter.cs index f769b62934fb..03a09399d7ae 100644 --- a/src/Nethermind/Nethermind.Network.Stats/Model/CapabilityConverter.cs +++ b/src/Nethermind/Nethermind.Network.Stats/Model/CapabilityConverter.cs @@ -10,7 +10,8 @@ using System.Text; using System.Text.Json; using System.Text.Json.Serialization; -using Nethermind.Network.Contract.P2P; + +#nullable enable namespace Nethermind.Stats.Model { @@ -20,7 +21,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 +33,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 +94,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 +121,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 345eb95dd786..c14265ea159f 100644 --- a/src/Nethermind/Nethermind.Network.Stats/Model/DisconnectReason.cs +++ b/src/Nethermind/Nethermind.Network.Stats/Model/DisconnectReason.cs @@ -53,8 +53,9 @@ public enum DisconnectReason : byte InvalidReceiptRoot, EthSyncException, InvalidBlockRangeUpdate, + MessageLimitsBreached, - // These are from EthDisconnectReason which does not necessarily used in Nethermind. + // These are from EthDisconnectReason that does not necessarily use in Nethermind. EthDisconnectRequested, TcpSubSystemError, BreachOfProtocol, @@ -67,7 +68,7 @@ public enum DisconnectReason : byte ReceiveMessageTimeout, MultipleHeaderDependencies, - // Try not to use this. Instead create a new one. + // Try not to use this. Instead, create a new one. Other, } @@ -91,10 +92,11 @@ 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, + DisconnectReason.MessageLimitsBreached => EthDisconnectReason.BreachOfProtocol, DisconnectReason.UselessPeer => EthDisconnectReason.UselessPeer, DisconnectReason.AlreadyConnected => EthDisconnectReason.AlreadyConnected, DisconnectReason.NullNodeIdentityReceived => EthDisconnectReason.NullNodeIdentityReceived, diff --git a/src/Nethermind/Nethermind.Network.Stats/Model/Node.cs b/src/Nethermind/Nethermind.Network.Stats/Model/Node.cs index ebb4fc91baba..24d057d69612 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 69c6e6520496..ab8df133b21f 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.Stats/NodeStatsLight.cs b/src/Nethermind/Nethermind.Network.Stats/NodeStatsLight.cs index 4b34c2d20125..1380487b5240 100644 --- a/src/Nethermind/Nethermind.Network.Stats/NodeStatsLight.cs +++ b/src/Nethermind/Nethermind.Network.Stats/NodeStatsLight.cs @@ -10,7 +10,6 @@ using FastEnumUtility; using Nethermind.Core; using Nethermind.Stats.Model; -using Nethermind.Stats.SyncLimits; namespace Nethermind.Stats; diff --git a/src/Nethermind/Nethermind.Network.Stats/StatsParameters.cs b/src/Nethermind/Nethermind.Network.Stats/StatsParameters.cs index f88948362ed2..001cae24f657 100644 --- a/src/Nethermind/Nethermind.Network.Stats/StatsParameters.cs +++ b/src/Nethermind/Nethermind.Network.Stats/StatsParameters.cs @@ -35,7 +35,8 @@ private StatsParameters() { DisconnectReason.UnexpectedIdentity, (TimeSpan.FromMinutes(15), -10000) }, { DisconnectReason.IncompatibleP2PVersion, (TimeSpan.FromMinutes(15), -10000) }, { DisconnectReason.UselessPeer, (TimeSpan.FromMinutes(15), -10000) }, - { DisconnectReason.BreachOfProtocol, (TimeSpan.FromMinutes(15), -10000) } + { DisconnectReason.BreachOfProtocol, (TimeSpan.FromMinutes(15), -10000) }, + { DisconnectReason.MessageLimitsBreached, (TimeSpan.FromMinutes(15), -10000) } }; public Dictionary RemoteDisconnectParams { get; } = new() diff --git a/src/Nethermind/Nethermind.Network.Stats/SyncLimits/NethermindSyncLimits.cs b/src/Nethermind/Nethermind.Network.Stats/SyncLimits/NethermindSyncLimits.cs index 9e60940887d9..26d6d229443f 100644 --- a/src/Nethermind/Nethermind.Network.Stats/SyncLimits/NethermindSyncLimits.cs +++ b/src/Nethermind/Nethermind.Network.Stats/SyncLimits/NethermindSyncLimits.cs @@ -5,9 +5,10 @@ namespace Nethermind.Stats.SyncLimits { public static class NethermindSyncLimits { - public const int MaxHeaderFetch = 512; // Amount of block headers to be fetched per retrieval request - public const int MaxBodyFetch = 256; // Amount of block bodies to be fetched per retrieval request - public const int MaxReceiptFetch = 256; // Amount of transaction receipts to allow fetching per request - public const int MaxCodeFetch = 1024; // Amount of contract codes to allow fetching per request + public const int MaxHeaderFetch = 512; // Number of block headers to be fetched per retrieval request + public const int MaxBodyFetch = 256; // Number of block bodies to be fetched per retrieval request + public const int MaxReceiptFetch = 256; // Number of transaction receipts to allow fetching per request + public const int MaxCodeFetch = 1024; // Number of contract codes to allow fetching per request + public const int MaxHashesFetch = 16384; // Number of hashes to allow fetching per request } } diff --git a/src/Nethermind/Nethermind.Network.Test/ForkInfoTests.cs b/src/Nethermind/Nethermind.Network.Test/ForkInfoTests.cs index 0cb76c6a3e77..2e7ba69a2afe 100644 --- a/src/Nethermind/Nethermind.Network.Test/ForkInfoTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/ForkInfoTests.cs @@ -56,8 +56,14 @@ public class ForkInfoTests [TestCase(15_051_000, 1_710_338_134ul, "0xdce96c2d", 1_710_338_135ul, "Future Shanghai timestamp")] [TestCase(15_051_000, 1_710_338_135ul, "0x9f3d2254", 1_746_612_311ul, "First Cancun timestamp")] [TestCase(15_051_000, 1_746_612_310ul, "0x9f3d2254", 1_746_612_311ul, "Future Cancun timestamp")] - [TestCase(15_051_000, 1_746_612_311ul, "0xc376cf8b", 0ul, "First Prague timestamp")] - [TestCase(15_051_000, 1_846_612_311ul, "0xc376cf8b", 0ul, "Future Prague timestamp")] + [TestCase(15_051_000, 1_746_612_311ul, "0xc376cf8b", 1_764_798_551ul, "First Prague timestamp")] + [TestCase(15_051_000, 1_764_798_550ul, "0xc376cf8b", 1_764_798_551ul, "Future Prague timestamp")] + [TestCase(15_051_000, 1_764_798_551ul, "0x5167e2a6", 1_765_290_071ul, "First Osaka timestamp")] + [TestCase(15_051_000, 1_765_290_070ul, "0x5167e2a6", 1_765_290_071ul, "Future Osaka timestamp")] + [TestCase(15_051_000, 1_765_290_071ul, "0xcba2a1c0", 1_767_747_671ul, "First BPO1 timestamp")] + [TestCase(15_051_000, 1_767_747_670ul, "0xcba2a1c0", 1_767_747_671ul, "Future BPO1 timestamp")] + [TestCase(15_051_000, 1_767_747_671ul, "0x07c9462e", 0ul, "First BPO2 timestamp")] + [TestCase(15_051_000, 1_867_747_671ul, "0x07c9462e", 0ul, "Future BPO2 timestamp")] public void Fork_id_and_hash_as_expected(long head, ulong headTimestamp, string forkHashHex, ulong next, string description) { Test(head, headTimestamp, KnownHashes.MainnetGenesis, forkHashHex, next, description, MainnetSpecProvider.Instance, "foundation.json"); @@ -101,35 +107,16 @@ public void Fork_id_and_hash_as_expected_with_timestamps(long head, ulong headTi [TestCase(21_811_000, 0ul, "0x3f5fd195", 1681338455UL, "Future Merge Fork Id test")] public void Fork_id_and_hash_as_expected_with_merge_fork_id(long head, ulong headTimestamp, string forkHashHex, ulong next, string description) { - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + ChainSpecFileLoader loader = new(new EthereumJsonSerializer(), LimboLogs.Instance); ChainSpec spec = loader.LoadEmbeddedOrFromFile("../../../../Chains/foundation.json"); spec.Parameters.MergeForkIdTransition = 21_000_000L; spec.MergeForkIdBlockNumber = 21_000_000L; - ChainSpecBasedSpecProvider provider = new ChainSpecBasedSpecProvider(spec); + ChainSpecBasedSpecProvider provider = new(spec); Test(head, headTimestamp, KnownHashes.MainnetGenesis, forkHashHex, next, description, provider); } - [TestCase(0, 0ul, "0xc61a6098", 1_696_000_704ul, "Unsynced")] - [TestCase(1, 1_696_000_703ul, "0xc61a6098", 1_696_000_704ul, "Last genesis spec block")] - [TestCase(2, 1_696_000_704ul, "0xfd4f016b", 1_707_305_664ul, "First Shanghai block")] - [TestCase(3, 1_707_305_663ul, "0xfd4f016b", 1_707_305_664ul, "Future Shanghai timestamp")] - [TestCase(4, 1_707_305_664ul, "0x9b192ad0", 1_740_434_112ul, "First Cancun timestamp")] - [TestCase(5, 1_717_305_664ul, "0x9b192ad0", 1_740_434_112ul, "Future Cancun timestamp")] - [TestCase(5, 1_740_434_112ul, "0xdfbd9bed", 1_759_308_480ul, "First Prague timestamp")] - [TestCase(5, 1_750_434_112ul, "0xdfbd9bed", 1_759_308_480ul, "Future Prague timestamp")] - [TestCase(6, 1_759_308_480ul, "0x783def52", 1_759_800_000ul, "First Osaka timestamp")] - [TestCase(6, 1_759_309_480ul, "0x783def52", 1_759_800_000ul, "Future Osaka timestamp")] - [TestCase(7, 1_759_800_000ul, "0xa280a45c", 1_760_389_824ul, "First BPO1 timestamp")] - [TestCase(7, 1_759_801_000ul, "0xa280a45c", 1_760_389_824ul, "Future BPO1 timestamp")] - [TestCase(8, 1_760_389_824ul, "0x9bc6cb31", 0ul, "First BPO2 timestamp")] - [TestCase(8, 1_770_389_824ul, "0x9bc6cb31", 0ul, "Future BPO2 timestamp")] - public void Fork_id_and_hash_as_expected_on_holesky(long head, ulong headTimestamp, string forkHashHex, ulong next, string description) - { - Test(head, headTimestamp, KnownHashes.HoleskyGenesis, forkHashHex, next, description, HoleskySpecProvider.Instance, "holesky.json"); - } - [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")] @@ -164,14 +151,16 @@ 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) { - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + ChainSpecFileLoader loader = new(new EthereumJsonSerializer(), LimboLogs.Instance); ChainSpec spec = loader.LoadEmbeddedOrFromFile("../../../../Chains/gnosis.json"); - ChainSpecBasedSpecProvider provider = new ChainSpecBasedSpecProvider(spec); + ChainSpecBasedSpecProvider provider = new(spec); Test(head, headTimestamp, KnownHashes.GnosisGenesis, forkHashHex, next, description, provider); } @@ -179,20 +168,23 @@ public void Fork_id_and_hash_as_expected_on_gnosis(long head, ulong headTimestam [TestCase(3945317, ChiadoSpecProvider.ShanghaiTimestamp, "0xa15a4252", ChiadoSpecProvider.CancunTimestamp, "First Shanghai timestamp")] [TestCase(4_000_000, ChiadoSpecProvider.CancunTimestamp, "0x5fbc16bc", 1741254220ul, "First Cancun timestamp")] [TestCase(5_000_000, 1741254219u, "0x5fbc16bc", 1741254220ul, "Future Cancun timestamp")] - [TestCase(5_000_000, 1741254220u, "0x8BA51786", 0ul, "First Prague timestamp")] - [TestCase(5_000_000, 1741254420u, "0x8BA51786", 0ul, "Future Prague timestamp")] + [TestCase(5_000_000, 1741254220u, "0x8ba51786", 0ul, "First Prague timestamp")] + [TestCase(5_000_000, 1741254420u, "0x8ba51786", 0ul, "Future Prague timestamp")] + // [TestCase(5_000_000, 1741254420u, "0x8ba51786", 1761037900ul, "Future Prague timestamp")] + // [TestCase(5_000_000, 1761037900u, "0x82612523", 0ul, "First Osaka timestamp")] + // [TestCase(5_000_000, 1761038000u, "0x82612523", 0ul, "Future Osaka timestamp")] public void Fork_id_and_hash_as_expected_on_chiado(long head, ulong headTimestamp, string forkHashHex, ulong next, string description) { - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + ChainSpecFileLoader loader = new(new EthereumJsonSerializer(), LimboLogs.Instance); ChainSpec spec = loader.LoadEmbeddedOrFromFile("../../../../Chains/chiado.json"); - ChainSpecBasedSpecProvider provider = new ChainSpecBasedSpecProvider(spec); + ChainSpecBasedSpecProvider provider = new(spec); Test(head, headTimestamp, KnownHashes.ChiadoGenesis, forkHashHex, next, description, provider); } - [TestCase(0L, HoodiSpecProvider.CancunTimestamp, "0xBEF71D30", HoodiSpecProvider.PragueTimestamp, "First Cancun timestamp")] - [TestCase(5_000_000, HoodiSpecProvider.PragueTimestamp - 1, "0xBEF71D30", HoodiSpecProvider.PragueTimestamp, "Future Cancun timestamp")] - [TestCase(5_000_000, HoodiSpecProvider.PragueTimestamp, "0x929E24E", HoodiSpecProvider.OsakaTimestamp, "First Prague timestamp")] - [TestCase(5_000_000, HoodiSpecProvider.OsakaTimestamp - 1, "0x929E24E", HoodiSpecProvider.OsakaTimestamp, "Future Prague timestamp")] + [TestCase(0L, HoodiSpecProvider.CancunTimestamp, "0xbef71d30", HoodiSpecProvider.PragueTimestamp, "First Cancun timestamp")] + [TestCase(5_000_000, HoodiSpecProvider.PragueTimestamp - 1, "0xbef71d30", HoodiSpecProvider.PragueTimestamp, "Future Cancun timestamp")] + [TestCase(5_000_000, HoodiSpecProvider.PragueTimestamp, "0x929e24e", HoodiSpecProvider.OsakaTimestamp, "First Prague timestamp")] + [TestCase(5_000_000, HoodiSpecProvider.OsakaTimestamp - 1, "0x929e24e", HoodiSpecProvider.OsakaTimestamp, "Future Prague timestamp")] [TestCase(6_000_000, HoodiSpecProvider.OsakaTimestamp, "0xe7e0e7ff", HoodiSpecProvider.BPO1Timestamp, "First Osaka timestamp")] [TestCase(6_000_000, HoodiSpecProvider.BPO1Timestamp - 1, "0xe7e0e7ff", HoodiSpecProvider.BPO1Timestamp, "Future Osaka timestamp")] [TestCase(7_000_000, HoodiSpecProvider.BPO1Timestamp, "0x3893353e", HoodiSpecProvider.BPO2Timestamp, "First BPO1 timestamp")] @@ -201,9 +193,9 @@ public void Fork_id_and_hash_as_expected_on_chiado(long head, ulong headTimestam [TestCase(8_000_000, HoodiSpecProvider.BPO2Timestamp + 100000, "0x23aa1351", 0ul, "Future BPO2 timestamp")] public void Fork_id_and_hash_as_expected_on_hoodi(long head, ulong headTimestamp, string forkHashHex, ulong next, string description) { - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + ChainSpecFileLoader loader = new(new EthereumJsonSerializer(), LimboLogs.Instance); ChainSpec spec = loader.LoadEmbeddedOrFromFile("../../../../Chains/hoodi.json"); - ChainSpecBasedSpecProvider provider = new ChainSpecBasedSpecProvider(spec); + ChainSpecBasedSpecProvider provider = new(spec); Test(head, headTimestamp, KnownHashes.HoodiGenesis, forkHashHex, next, description, provider); } @@ -348,7 +340,7 @@ public void Test_fork_id_validation_mainnet(long headNumber, ulong headTimestamp ISpecProvider specProvider = MainnetSpecProvider.Instance; if (UseTimestampSpec) { - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance); ChainSpec spec = loader.LoadEmbeddedOrFromFile($"../../../../{Assembly.GetExecutingAssembly().GetName().Name}/TimestampForkIdTest.json"); specProvider = new ChainSpecBasedSpecProvider(spec); } @@ -367,7 +359,7 @@ public void Test_fork_id_validation_mainnet(long headNumber, ulong headTimestamp [TestCase(null, null, 1ul, 1ul)] public void Chain_id_and_network_id_have_proper_default_values(ulong? specNetworkId, ulong? specChainId, ulong expectedNetworkId, ulong expectedChainId) { - ChainSpecLoader loader = new(new EthereumJsonSerializer()); + ChainSpecLoader loader = new(new EthereumJsonSerializer(), NullLogManager.Instance); string chainspec = $"{{\"params\":{{\"networkID\":{specNetworkId?.ToString() ?? "null"},\"chainId\":{specChainId?.ToString() ?? "null"}}},\"engine\":{{\"NethDev\":{{}}}}}}"; using MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(chainspec)); @@ -380,15 +372,15 @@ 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); + var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance); ChainSpec spec = loader.LoadEmbeddedOrFromFile(Path.Combine(path, chainSpec)); ChainSpecBasedSpecProvider provider = new ChainSpecBasedSpecProvider(spec); Test(head, headTimestamp, genesisHash, forkHashHex, next, description, provider); @@ -396,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); @@ -454,8 +446,7 @@ public void Test_no_fork_is_created_before_genesis_time() }, Genesis = new ChainSpecGenesisJson() { - Timestamp = 1, - + Timestamp = 1 } }); diff --git a/src/Nethermind/Nethermind.Network.Test/MessageQueueTests.cs b/src/Nethermind/Nethermind.Network.Test/MessageQueueTests.cs new file mode 100644 index 000000000000..fe653da76271 --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Test/MessageQueueTests.cs @@ -0,0 +1,168 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using FluentAssertions; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; +using Nethermind.Network.P2P; +using Nethermind.Network.P2P.Subprotocols; +using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Network.Test; + +public class MessageQueueTests +{ + private readonly List _recordedSends = new(); + private MessageQueue> _queue; + + [SetUp] + public void Setup() + { + _recordedSends.Clear(); + _queue = new((message) => _recordedSends.Add(message)); + } + + [Test] + public void Send_first_request_is_sent_immediately() + { + Request> request = CreateRequest(); + + _queue.Send(request); + + _recordedSends.Count.Should().Be(1); + request.CompletionSource.Task.IsCompleted.Should().BeFalse(); + } + + [Test] + public void Send_second_request_is_queued() + { + Request> request1 = CreateRequest(); + Request> request2 = CreateRequest(); + + _queue.Send(request1); + _queue.Send(request2); + + _recordedSends.Count.Should().Be(1); + request2.CompletionSource.Task.IsCompleted.Should().BeFalse(); + } + + [Test] + public void Handle_completes_current_request_and_sends_next() + { + Request> request1 = CreateRequest(); + Request> request2 = CreateRequest(); + + IOwnedReadOnlyList response = new[] { Build.A.BlockHeader.TestObject }.ToPooledList(); + + _queue.Send(request1); + _queue.Send(request2); + + _queue.Handle(response, 100); + + request1.CompletionSource.Task.IsCompleted.Should().BeTrue(); + request1.CompletionSource.Task.Result.Should().BeSameAs(response); + _recordedSends.Count.Should().Be(2); + } + + [Test] + public void Handle_throws_when_no_current_request() + { + using IOwnedReadOnlyList response = new[] { Build.A.BlockHeader.TestObject }.ToPooledList(); + + _queue.Invoking(q => q.Handle(response, 100)) + .Should() + .Throw(); + } + + [Test] + public void Handle_disposes_data_when_no_current_request() + { + IOwnedReadOnlyList response = Substitute.For>(); + + _queue.Invoking(q => q.Handle(response, 100)) + .Should() + .Throw(); + + response.Received().Dispose(); + } + + [Test] + public void Handle_does_not_throw_when_completion_source_already_cancelled() + { + Request> request = CreateRequest(); + + _queue.Send(request); + + // Simulate timeout cancelling the CompletionSource + request.CompletionSource.TrySetCanceled(); + + using IOwnedReadOnlyList response = new[] { Build.A.BlockHeader.TestObject }.ToPooledList(); + + // Should not throw — this is the core regression test + _queue.Invoking(q => q.Handle(response, 100)) + .Should() + .NotThrow(); + } + + [Test] + public void Handle_disposes_data_when_completion_source_already_cancelled() + { + Request> request = CreateRequest(); + + _queue.Send(request); + + // Simulate timeout cancelling the CompletionSource + request.CompletionSource.TrySetCanceled(); + + IOwnedReadOnlyList response = Substitute.For>(); + + _queue.Handle(response, 100); + + response.Received().Dispose(); + } + + [Test] + public void Handle_dequeues_next_request_even_when_current_was_cancelled() + { + Request> request1 = CreateRequest(); + Request> request2 = CreateRequest(); + + _queue.Send(request1); + _queue.Send(request2); + + // Simulate timeout cancelling the first request + request1.CompletionSource.TrySetCanceled(); + + IOwnedReadOnlyList response = Substitute.For>(); + + _queue.Handle(response, 100); + + // The second request should have been dequeued and sent + _recordedSends.Count.Should().Be(2); + request2.CompletionSource.Task.IsCompleted.Should().BeFalse(); + } + + [Test] + public void Send_does_not_send_when_closed() + { + _queue.CompleteAdding(); + + GetBlockHeadersMessage msg = new(); + Request> request = new(msg); + + _queue.Send(request); + + _recordedSends.Count.Should().Be(0); + } + + private static Request> CreateRequest() + { + return new(new GetBlockHeadersMessage()); + } +} diff --git a/src/Nethermind/Nethermind.Network.Test/Nethermind.Network.Test.csproj b/src/Nethermind/Nethermind.Network.Test/Nethermind.Network.Test.csproj index 1b7da78c08fc..86575e37bea7 100644 --- a/src/Nethermind/Nethermind.Network.Test/Nethermind.Network.Test.csproj +++ b/src/Nethermind/Nethermind.Network.Test/Nethermind.Network.Test.csproj @@ -39,5 +39,6 @@ + diff --git a/src/Nethermind/Nethermind.Network.Test/NodesLoaderTests.cs b/src/Nethermind/Nethermind.Network.Test/NodesLoaderTests.cs index cb68afb421d1..2afe096ec948 100644 --- a/src/Nethermind/Nethermind.Network.Test/NodesLoaderTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/NodesLoaderTests.cs @@ -62,7 +62,7 @@ public void Can_load_static_nodes() [Test] public void Can_load_bootnodes() { - _discoveryConfig.Bootnodes = enodesString; + _discoveryConfig.Bootnodes = new[] { new NetworkNode(enode1String), new NetworkNode(enode2String) }; _networkConfig.Bootnodes = _discoveryConfig.Bootnodes; List nodes = _loader.DiscoverNodes(default).ToBlockingEnumerable().ToList(); Assert.That(nodes.Count, Is.EqualTo(2)); @@ -89,7 +89,7 @@ public void Can_load_persisted() public void Can_load_only_static_nodes() { _networkConfig.StaticPeers = enode1String; - _networkConfig.Bootnodes = enode2String; + _networkConfig.Bootnodes = new[] { new NetworkNode(enode2String) }; _networkConfig.OnlyStaticPeers = true; List nodes = _loader.DiscoverNodes(default).ToBlockingEnumerable().ToList(); Assert.That(nodes.Count, Is.EqualTo(1)); diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/P2PProtocolHandlerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/P2PProtocolHandlerTests.cs index ba1cd186deb7..da4f061a4077 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/P2PProtocolHandlerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/P2PProtocolHandlerTests.cs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Linq; -using System.Text.RegularExpressions; using DotNetty.Buffers; using FluentAssertions; +using Nethermind.Consensus.Scheduler; using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Test.Builders; @@ -68,6 +68,7 @@ private P2PProtocolHandler CreateSession() TestItem.PublicKeyA, _nodeStatsManager, _serializer, + Substitute.For(), LimboLogs.Instance); } diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/P2PProtocolInfoProviderTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/P2PProtocolInfoProviderTests.cs index ddfc60643b06..354f992b0484 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 000000000000..43860b4f0dc7 --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Test/P2P/ProtocolParserTests.cs @@ -0,0 +1,310 @@ +// 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_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_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/SessionTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/SessionTests.cs index f5609b63b6a3..0515797be479 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/SessionTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/SessionTests.cs @@ -35,7 +35,7 @@ public void SetUp() _pipeline = Substitute.For(); _channelHandlerContext.Channel.Returns(_channel); _channel.Pipeline.Returns(_pipeline); - _pipeline.Get().Returns(new ZeroPacketSplitter(LimboLogs.Instance)); + _pipeline.Get().Returns(new ZeroPacketSplitter()); _packetSender = Substitute.For(); } 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 87bb693219ec..c202a9b9c247 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; } } @@ -617,7 +619,8 @@ public void should_send_single_transaction_even_if_exceed_MaxPacketSize(int data _handler.SendNewTransactions(txs); - _session.Received(messagesCount).DeliverMessage(Arg.Is(m => m.Transactions.Count == numberOfTxsInOneMsg || m.Transactions.Count == nonFullMsgTxsCount)); + Assert.That(() => _session.ReceivedCallsMatching(s => s.DeliverMessage(Arg.Is(m => m.Transactions.Count == numberOfTxsInOneMsg || m.Transactions.Count == nonFullMsgTxsCount)), messagesCount), Is.True.After(500, 50)); + } private void HandleZeroMessage(T msg, int messageCode) where T : MessageBase diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/TxFloodControllerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/TxFloodControllerTests.cs index 284d9b09cf95..06923774ec5d 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/TxFloodControllerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/TxFloodControllerTests.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; @@ -26,6 +26,8 @@ public class TxFloodControllerTests private ISession _session; private ITimestamper _timestamper; + private readonly AcceptTxResult Flooding = AcceptTxResult.NonceGap; + [SetUp] public void Setup() { @@ -66,7 +68,7 @@ public void Is_allowed_will_be_false_when_misbehaving() { for (int i = 0; i < 601; i++) { - _controller.Report(false); + _controller.Report(Flooding); } int allowedCount = 0; @@ -83,22 +85,22 @@ public void Will_only_get_disconnected_when_really_flooding() { for (int i = 0; i < 600; i++) { - _controller.Report(false); + _controller.Report(Flooding); } // for easier debugging - _controller.Report(false); + _controller.Report(Flooding); _session.DidNotReceiveWithAnyArgs() .InitiateDisconnect(DisconnectReason.TxFlooding, null); for (int i = 0; i < 6000 - 601; i++) { - _controller.Report(false); + _controller.Report(Flooding); } // for easier debugging - _controller.Report(false); + _controller.Report(Flooding); _session.Received() .InitiateDisconnect(DisconnectReason.TxFlooding, Arg.Any()); @@ -109,7 +111,7 @@ public void Will_downgrade_at_first() { for (int i = 0; i < 1000; i++) { - _controller.Report(false); + _controller.Report(Flooding); } _controller.IsDowngraded.Should().BeTrue(); @@ -139,7 +141,7 @@ public void Misbehaving_expires() { for (int i = 0; i < 1000; i++) { - _controller.Report(false); + _controller.Report(Flooding); } _controller.IsDowngraded.Should().BeTrue(); @@ -147,5 +149,14 @@ public void Misbehaving_expires() _controller.Report(false); _controller.IsDowngraded.Should().BeFalse(); } + + [Test] + public void Will_disconnect_on_invalid_tx() + { + _controller.Report(AcceptTxResult.Invalid); + + _session.Received(1) + .InitiateDisconnect(DisconnectReason.Other, "invalid tx"); + } } } diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V63/Eth63ProtocolHandlerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V63/Eth63ProtocolHandlerTests.cs index e61dc1266b9c..869840737857 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V63/Eth63ProtocolHandlerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V63/Eth63ProtocolHandlerTests.cs @@ -23,6 +23,7 @@ using Nethermind.Specs; using Nethermind.Stats; using Nethermind.Stats.Model; +using Nethermind.Stats.SyncLimits; using Nethermind.Synchronization; using Nethermind.TxPool; using NSubstitute; @@ -54,27 +55,30 @@ public void TearDown() [Test] public async Task Can_request_and_handle_receipts() { + const int count = NethermindSyncLimits.MaxReceiptFetch; TxReceipt[][]? receipts = Enumerable.Repeat( Enumerable.Repeat(Build.A.Receipt.WithAllFieldsFilled.TestObject, 100).ToArray(), - 1000).ToArray(); // TxReceipt[1000][100] + count).ToArray(); // TxReceipt[1000][100] using ReceiptsMessage receiptsMsg = new(receipts.ToPooledList()); Packet receiptsPacket = new("eth", Eth63MessageCode.Receipts, _ctx._receiptMessageSerializer.Serialize(receiptsMsg)); Task> task = _ctx.ProtocolHandler.GetReceipts( - Enumerable.Repeat(Keccak.Zero, 1000).ToArray(), + Enumerable.Repeat(Keccak.Zero, count).ToArray(), CancellationToken.None); _ctx.ProtocolHandler.HandleMessage(receiptsPacket); using var result = await task; - result.Should().HaveCount(1000); + result.Should().HaveCount(count); } [Test] public async Task Limit_receipt_request() { + const int count = NethermindSyncLimits.MaxReceiptFetch; + TxReceipt[] oneBlockReceipt = Enumerable.Repeat(Build.A.Receipt.WithAllFieldsFilled.TestObject, 100).ToArray(); Packet smallReceiptsPacket = new("eth", Eth63MessageCode.Receipts, _ctx._receiptMessageSerializer.Serialize( @@ -82,7 +86,7 @@ public async Task Limit_receipt_request() )); Packet largeReceiptsPacket = new("eth", Eth63MessageCode.Receipts, _ctx._receiptMessageSerializer.Serialize( - new(RepeatPooled(oneBlockReceipt, 1000)) + new(RepeatPooled(oneBlockReceipt, count)) )); GetReceiptsMessage? receiptsMessage = null; @@ -92,7 +96,7 @@ public async Task Limit_receipt_request() .Do(info => receiptsMessage = (GetReceiptsMessage)info[0]); Task> receiptsTask = _ctx.ProtocolHandler - .GetReceipts(RepeatPooled(Keccak.Zero, 1000), CancellationToken.None) + .GetReceipts(RepeatPooled(Keccak.Zero, count), CancellationToken.None) .AddResultTo(_disposables); _ctx.ProtocolHandler.HandleMessage(smallReceiptsPacket); @@ -101,7 +105,7 @@ public async Task Limit_receipt_request() Assert.That(receiptsMessage?.Hashes?.Count, Is.EqualTo(8)); receiptsTask = _ctx.ProtocolHandler - .GetReceipts(RepeatPooled(Keccak.Zero, 1000), CancellationToken.None) + .GetReceipts(RepeatPooled(Keccak.Zero, count), CancellationToken.None) .AddResultTo(_disposables); _ctx.ProtocolHandler.HandleMessage(largeReceiptsPacket); @@ -111,7 +115,7 @@ public async Task Limit_receipt_request() // Back to 10 receiptsTask = _ctx.ProtocolHandler - .GetReceipts(RepeatPooled(Keccak.Zero, 1000), CancellationToken.None) + .GetReceipts(RepeatPooled(Keccak.Zero, count), CancellationToken.None) .AddResultTo(_disposables); _ctx.ProtocolHandler.HandleMessage(smallReceiptsPacket); @@ -124,18 +128,6 @@ public async Task Limit_receipt_request() private ArrayPoolList RepeatPooled(T txReceipts, int count) => Enumerable.Repeat(txReceipts, count).ToPooledList(count).AddTo(_disposables); - [Test] - public void Will_not_serve_receipts_requests_above_512() - { - using GetReceiptsMessage getReceiptsMessage = new( - RepeatPooled(Keccak.Zero, 513)); - Packet getReceiptsPacket = - new("eth", Eth63MessageCode.GetReceipts, _ctx._getReceiptMessageSerializer.Serialize(getReceiptsMessage)); - - _ctx.ProtocolHandler.HandleMessage(getReceiptsPacket); - _ctx.Session.Received().InitiateDisconnect(Arg.Any(), Arg.Any()); - } - [Test] public void Will_not_send_messages_larger_than_2MB() { 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 b6bde3d62e7d..c12998f398d6 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/V63/ReceiptsMessageSerializerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V63/ReceiptsMessageSerializerTests.cs index 110015e23426..83b1f954a73a 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V63/ReceiptsMessageSerializerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V63/ReceiptsMessageSerializerTests.cs @@ -3,7 +3,6 @@ using DotNetty.Buffers; using FluentAssertions; -using Nethermind.Blockchain.Receipts; using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Specs; diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V65/Eth65ProtocolHandlerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V65/Eth65ProtocolHandlerTests.cs index e420e543bac0..f7c9f1297b13 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V65/Eth65ProtocolHandlerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V65/Eth65ProtocolHandlerTests.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.Net; using System.Threading; using System.Threading.Tasks; @@ -10,7 +9,6 @@ using FluentAssertions; using Nethermind.Consensus; using Nethermind.Core; -using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; @@ -21,7 +19,6 @@ using Nethermind.Logging; using Nethermind.Network.P2P; using Nethermind.Network.P2P.Messages; -using Nethermind.Network.P2P.Subprotocols.Eth; using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; using Nethermind.Network.P2P.Subprotocols.Eth.V65; using Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages; @@ -43,7 +40,6 @@ public class Eth65ProtocolHandlerTests private IMessageSerializationService _svc = null!; private ISyncServer _syncManager = null!; private ITxPool _transactionPool = null!; - private IPooledTxsRequestor _pooledTxsRequestor = null!; private ISpecProvider _specProvider = null!; private Block _genesisBlock = null!; private Eth65ProtocolHandler _handler = null!; @@ -64,7 +60,6 @@ public void Setup() _session.When(s => s.DeliverMessage(Arg.Any())).Do(c => c.Arg().AddTo(_disposables)); _syncManager = Substitute.For(); _transactionPool = Substitute.For(); - _pooledTxsRequestor = Substitute.For(); _specProvider = Substitute.For(); _genesisBlock = Build.A.Block.Genesis.TestObject; _syncManager.Head.Returns(_genesisBlock.Header); @@ -80,7 +75,6 @@ public void Setup() _syncManager, RunImmediatelyScheduler.Instance, _transactionPool, - _pooledTxsRequestor, Policy.FullGossip, new ForkInfo(_specProvider, _syncManager), LimboLogs.Instance, @@ -197,7 +191,7 @@ public void should_handle_NewPooledTransactionHashesMessage([Values(true, false) HandleIncomingStatusMessage(); HandleZeroMessage(msg, Eth65MessageCode.NewPooledTransactionHashes); - _pooledTxsRequestor.Received(canGossipTransactions ? 1 : 0).RequestTransactions(Arg.Any>(), Arg.Any>()); + _session.Received(canGossipTransactions ? 1 : 0).DeliverMessage(Arg.Any()); } private void HandleZeroMessage(T msg, int messageCode) where T : MessageBase diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V65/PooledTxsRequestorTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V65/PooledTxsRequestorTests.cs deleted file mode 100644 index 1064f0219b66..000000000000 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V65/PooledTxsRequestorTests.cs +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using FluentAssertions; -using Nethermind.Core.Crypto; -using Nethermind.Core.Test.Builders; -using Nethermind.Network.P2P.Subprotocols.Eth; -using Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages; -using Nethermind.TxPool; -using NSubstitute; -using NUnit.Framework; -using Nethermind.Core.Collections; -using Nethermind.Core.Extensions; -using Nethermind.Core.Specs; - -namespace Nethermind.Network.Test.P2P.Subprotocols.Eth.V65 -{ - public class PooledTxsRequestorTests - { - private readonly ITxPool _txPool = Substitute.For(); - private ISpecProvider _specProvider = Substitute.For(); - private readonly Action _doNothing = static msg => msg.Dispose(); - private IPooledTxsRequestor _requestor; - private ArrayPoolList _response; - - [TearDown] - public void TearDown() - { - _response?.Dispose(); - } - - [Test] - public void filter_properly_newPooledTxHashes() - { - _requestor = new PooledTxsRequestor(_txPool, new TxPoolConfig(), _specProvider); - using var skipped = new ArrayPoolList(2) { TestItem.KeccakA, TestItem.KeccakD }; - _requestor.RequestTransactions(_doNothing, skipped); - - using var request = new ArrayPoolList(3) { TestItem.KeccakA, TestItem.KeccakB, TestItem.KeccakC }; - using var expected = new ArrayPoolList(3) { TestItem.KeccakB, TestItem.KeccakC }; - _requestor.RequestTransactions(Send, request); - _response.Should().BeEquivalentTo(expected); - } - - [Test] - public void filter_properly_already_pending_hashes() - { - _requestor = new PooledTxsRequestor(_txPool, new TxPoolConfig(), _specProvider); - using var skipped = new ArrayPoolList(3) { TestItem.KeccakA, TestItem.KeccakB, TestItem.KeccakC }; - _requestor.RequestTransactions(_doNothing, skipped); - - using var request = new ArrayPoolList(3) { TestItem.KeccakA, TestItem.KeccakB, TestItem.KeccakC }; - _requestor.RequestTransactions(Send, request); - _response.Should().BeEmpty(); - } - - [Test] - public void filter_properly_discovered_hashes() - { - _requestor = new PooledTxsRequestor(_txPool, new TxPoolConfig(), _specProvider); - - using var request = new ArrayPoolList(3) { TestItem.KeccakA, TestItem.KeccakB, TestItem.KeccakC }; - using var expected = new ArrayPoolList(3) { TestItem.KeccakA, TestItem.KeccakB, TestItem.KeccakC }; - _requestor.RequestTransactions(Send, request); - _response.Should().BeEquivalentTo(expected); - } - - [Test] - public void can_handle_empty_argument() - { - _requestor = new PooledTxsRequestor(_txPool, new TxPoolConfig(), _specProvider); - using var skipped = new ArrayPoolList(0); - _requestor.RequestTransactions(Send, skipped); - _response.Should().BeEmpty(); - } - - [Test] - public void filter_properly_hashes_present_in_hashCache() - { - ITxPool txPool = Substitute.For(); - txPool.IsKnown(Arg.Any()).Returns(true); - _requestor = new PooledTxsRequestor(txPool, new TxPoolConfig(), _specProvider); - - using var request = new ArrayPoolList(2) { TestItem.KeccakA, TestItem.KeccakB }; - using var expected = new ArrayPoolList(0) { }; - _requestor.RequestTransactions(Send, request); - _response.Should().BeEquivalentTo(expected); - } - - private void Send(GetPooledTransactionsMessage msg) - { - _response?.Dispose(); - using (msg) - { - _response = msg.Hashes.ToPooledList(); - } - } - } -} diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/Eth66ProtocolHandlerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/Eth66ProtocolHandlerTests.cs index 296ce7b8039b..a2c405de0a55 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/Eth66ProtocolHandlerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/Eth66ProtocolHandlerTests.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.Collections.Generic; @@ -22,7 +22,6 @@ using Nethermind.Network.P2P; using Nethermind.Network.P2P.Messages; using Nethermind.Network.P2P.Subprotocols; -using Nethermind.Network.P2P.Subprotocols.Eth; using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; using Nethermind.Network.P2P.Subprotocols.Eth.V65; using Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages; @@ -41,7 +40,6 @@ using GetBlockBodiesMessage = Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages.GetBlockBodiesMessage; using GetBlockHeadersMessage = Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages.GetBlockHeadersMessage; using GetNodeDataMessage = Nethermind.Network.P2P.Subprotocols.Eth.V63.Messages.GetNodeDataMessage; -using GetPooledTransactionsMessage = Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages.GetPooledTransactionsMessage; using GetReceiptsMessage = Nethermind.Network.P2P.Subprotocols.Eth.V63.Messages.GetReceiptsMessage; using NodeDataMessage = Nethermind.Network.P2P.Subprotocols.Eth.V63.Messages.NodeDataMessage; using PooledTransactionsMessage = Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages.PooledTransactionsMessage; @@ -56,7 +54,6 @@ public class Eth66ProtocolHandlerTests private IMessageSerializationService _svc = null!; private ISyncServer _syncManager = null!; private ITxPool _transactionPool = null!; - private IPooledTxsRequestor _pooledTxsRequestor = null!; private IGossipPolicy _gossipPolicy = null!; private ITimerFactory _timerFactory = null!; private ISpecProvider _specProvider = null!; @@ -78,7 +75,6 @@ public void Setup() _session.When(s => s.DeliverMessage(Arg.Any())).Do(c => c.Arg().AddTo(_disposables)); _syncManager = Substitute.For(); _transactionPool = Substitute.For(); - _pooledTxsRequestor = Substitute.For(); _specProvider = Substitute.For(); _gossipPolicy = Substitute.For(); _genesisBlock = Build.A.Block.Genesis.TestObject; @@ -92,7 +88,6 @@ public void Setup() _syncManager, RunImmediatelyScheduler.Instance, _transactionPool, - _pooledTxsRequestor, _gossipPolicy, new ForkInfo(_specProvider, _syncManager), LimboLogs.Instance); @@ -204,8 +199,7 @@ public void Should_throw_when_receiving_unrequested_block_bodies() [Test] public void Can_handle_get_pooled_transactions() { - using var msg65 = new GetPooledTransactionsMessage(new[] { Keccak.Zero, TestItem.KeccakA }.ToPooledList()); - using var msg66 = new Network.P2P.Subprotocols.Eth.V66.Messages.GetPooledTransactionsMessage(1111, msg65); + using var msg66 = new Network.P2P.Subprotocols.Eth.V66.Messages.GetPooledTransactionsMessage(new[] { Keccak.Zero, TestItem.KeccakA }.ToPooledList()); HandleIncomingStatusMessage(); HandleZeroMessage(msg66, Eth66MessageCode.GetPooledTransactions); @@ -322,7 +316,6 @@ public void should_request_in_GetPooledTransactionsMessage_up_to_256_txs(int num _syncManager, RunImmediatelyScheduler.Instance, _transactionPool, - new PooledTxsRequestor(_transactionPool, new TxPoolConfig(), _specProvider), _gossipPolicy, new ForkInfo(_specProvider, _syncManager), LimboLogs.Instance); diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/GetPooledTransactionsSerializerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/GetPooledTransactionsSerializerTests.cs index 6ec52f4bf63a..732886d05f2f 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/GetPooledTransactionsSerializerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/GetPooledTransactionsSerializerTests.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 Nethermind.Core.Crypto; @@ -18,9 +18,8 @@ public void Roundtrip() Hash256 a = new("0x00000000000000000000000000000000000000000000000000000000deadc0de"); Hash256 b = new("0x00000000000000000000000000000000000000000000000000000000feedbeef"); Hash256[] keys = { a, b }; - using var ethMessage = new Network.P2P.Subprotocols.Eth.V65.Messages.GetPooledTransactionsMessage(keys.ToPooledList()); - GetPooledTransactionsMessage message = new(1111, ethMessage); + using GetPooledTransactionsMessage message = new(keys.ToPooledList()) { RequestId = 1111 }; GetPooledTransactionsMessageSerializer serializer = new(); 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 new file mode 100644 index 000000000000..a13a589e7040 --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/PooledTransactionsRequestingTests.cs @@ -0,0 +1,195 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using DotNetty.Buffers; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Spec; +using Nethermind.Consensus; +using Nethermind.Consensus.Comparers; +using Nethermind.Consensus.Validators; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test; +using Nethermind.Core.Test.Builders; +using Nethermind.Core.Test.Db; +using Nethermind.Core.Timers; +using Nethermind.Crypto; +using Nethermind.Logging; +using Nethermind.Network.P2P; +using Nethermind.Network.P2P.Messages; +using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; +using Nethermind.Network.P2P.Subprotocols.Eth.V65; +using Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages; +using Nethermind.Network.P2P.Subprotocols.Eth.V66; +using Nethermind.Network.Rlpx; +using Nethermind.Network.Test.Builders; +using Nethermind.Specs; +using Nethermind.Specs.Forks; +using Nethermind.Stats; +using Nethermind.Stats.Model; +using Nethermind.Synchronization; +using Nethermind.TxPool; +using NSubstitute; +using NUnit.Framework; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using PooledTransactionsMessage = Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages.PooledTransactionsMessage; + +namespace Nethermind.Network.Test.P2P.Subprotocols.Eth.V66; + +[TestFixture, Parallelizable(ParallelScope.Self), FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class PooledTransactionsRequestingTests +{ + private ISession _session = null!; + private ISession _session2 = null!; + private Eth66ProtocolHandler _handler = null!; + private ArrayPoolList _txs = null!; + private IMessageSerializationService _svc = null!; + private Block _genesisBlock = null!; + private CompositeDisposable _disposables = null!; + + private readonly int Timeout = 3000; + private readonly int InTime = 1000; + + [SetUp] + public void Setup() + { + _svc = Build.A.SerializationService().WithEth66().TestObject; + + IGossipPolicy _gossipPolicy = Substitute.For(); + _disposables = []; + + _session = Substitute.For(); + _session.Node.Returns(new Node(TestItem.PublicKeyA, new IPEndPoint(IPAddress.Broadcast, 30303))); + _session.When(s => s.DeliverMessage(Arg.Any())).Do(c => c.Arg().AddTo(_disposables)); + + _genesisBlock = Build.A.Block.Genesis.TestObject; + + TestSingleReleaseSpecProvider specProvider = new(Osaka.Instance); + IBlockTree blockTree = Build.A.BlockTree().WithoutSettingHead.WithSpecProvider(specProvider).TestObject; + + TxPool.TxPool txPool = new( + new EthereumEcdsa(specProvider.ChainId), + new BlobTxStorage(), + new ChainHeadInfoProvider( + new ChainHeadSpecProvider(specProvider, blockTree), blockTree, TestWorldStateFactory.CreateForTestWithStateReader(TestMemDbProvider.Init(), LimboLogs.Instance).Item2), + 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); + + ITimerFactory _timerFactory = Substitute.For(); + + _handler = new Eth66ProtocolHandler( + _session, + _svc, + new NodeStatsManager(_timerFactory, LimboLogs.Instance), + syncManager, + RunImmediatelyScheduler.Instance, + txPool, + Substitute.For(), + new ForkInfo(specProvider, syncManager), + LimboLogs.Instance); + + syncManager.AddTo(_disposables); + _handler.Init(); + + Transaction tx = Build.A.Transaction.WithShardBlobTxTypeAndFields(1).WithMaxPriorityFeePerGas(1).WithGasLimit(100) + .SignedAndResolved(new EthereumEcdsa(1), TestItem.PrivateKeyA).TestObject; + + Hash256 _txHash = tx.CalculateHash(); + _txs = new(1) { tx }; + _txs.AddTo(_disposables); + + _session2 = Substitute.For(); + _session2.Node.Returns(new Node(TestItem.PublicKeyB, new IPEndPoint(IPAddress.Loopback, 30304))); + _session2.When(s => s.DeliverMessage(Arg.Any())).Do(c => c.Arg().AddTo(_disposables)); + + + // Create second handler for second peer + Eth66ProtocolHandler handler2 = new( + _session2, + _svc, + new NodeStatsManager(_timerFactory, LimboLogs.Instance), + syncManager, + RunImmediatelyScheduler.Instance, + txPool, + _gossipPolicy, + new ForkInfo(specProvider, syncManager), + LimboLogs.Instance); + handler2.Init(); + handler2.AddTo(_disposables); + + // Setup both handlers to receive status messages + HandleIncomingStatusMessage(_handler); + HandleIncomingStatusMessage(handler2); + + // Act - Send new pooled transaction hashes from both peers + using NewPooledTransactionHashesMessage hashesMsg1 = new(new ArrayPoolList(1) { _txHash }); + using NewPooledTransactionHashesMessage hashesMsg2 = new(new ArrayPoolList(1) { _txHash }); + + HandleZeroMessage(_handler, hashesMsg1, Eth65MessageCode.NewPooledTransactionHashes); + HandleZeroMessage(handler2, hashesMsg2, Eth65MessageCode.NewPooledTransactionHashes); + } + + [TearDown] + public void TearDown() + { + _session.Dispose(); + _session2.Dispose(); + _handler.Dispose(); + _disposables?.Dispose(); + } + + [Test] + public async Task Should_request_from_others_after_timeout() + { + await Task.Delay(Timeout); + + _session2.Received(1).DeliverMessage(Arg.Is(m => m.EthMessage.Hashes.Contains(_txs[0].Hash))); + } + + + [Test] + public async Task Should_not_request_from_others_if_received() + { + await Task.Delay(InTime); + HandleZeroMessage(_handler, new Network.P2P.Subprotocols.Eth.V66.Messages.PooledTransactionsMessage(1111, new PooledTransactionsMessage(_txs)), Eth65MessageCode.PooledTransactions); + await Task.Delay(Timeout); + + _session2.Received(0).DeliverMessage(Arg.Is(m => m.EthMessage.Hashes.Contains(_txs[0].Hash))); + } + + + [Test] + 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); + + _session2.Received(0).DeliverMessage(Arg.Is(m => m.EthMessage.Hashes.Contains(_txs[0].Hash))); + } + + private void HandleIncomingStatusMessage(Eth66ProtocolHandler handler) + { + using var statusMsg = new StatusMessage(); + statusMsg.GenesisHash = _genesisBlock.Hash; + statusMsg.BestHash = _genesisBlock.Hash; + + IByteBuffer statusPacket = _svc.ZeroSerialize(statusMsg); + statusPacket.ReadByte(); + handler.HandleMessage(new ZeroPacket(statusPacket) { PacketType = 0 }); + } + + private void HandleZeroMessage(Eth66ProtocolHandler handler, T msg, int messageCode) where T : MessageBase + { + IByteBuffer packet = _svc.ZeroSerialize(msg); + packet.ReadByte(); + handler.HandleMessage(new ZeroPacket(packet) { PacketType = (byte)messageCode }); + } +} diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V67/Eth67ProtocolHandlerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V67/Eth67ProtocolHandlerTests.cs index 16b16c217398..262af32c19e7 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V67/Eth67ProtocolHandlerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V67/Eth67ProtocolHandlerTests.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.Net; @@ -16,7 +16,6 @@ using Nethermind.Logging; using Nethermind.Network.P2P; using Nethermind.Network.P2P.Subprotocols; -using Nethermind.Network.P2P.Subprotocols.Eth; using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; using Nethermind.Network.P2P.Subprotocols.Eth.V63.Messages; using Nethermind.Network.P2P.Subprotocols.Eth.V66; @@ -39,7 +38,6 @@ public class Eth67ProtocolHandlerTests private IMessageSerializationService _svc = null!; private ISyncServer _syncManager = null!; private ITxPool _transactionPool = null!; - private IPooledTxsRequestor _pooledTxsRequestor = null!; private IGossipPolicy _gossipPolicy = null!; private ISpecProvider _specProvider = null!; private Block _genesisBlock = null!; @@ -57,7 +55,6 @@ public void Setup() _session.Node.Returns(node); _syncManager = Substitute.For(); _transactionPool = Substitute.For(); - _pooledTxsRequestor = Substitute.For(); _specProvider = Substitute.For(); _gossipPolicy = Substitute.For(); _genesisBlock = Build.A.Block.Genesis.TestObject; @@ -71,7 +68,6 @@ public void Setup() _syncManager, RunImmediatelyScheduler.Instance, _transactionPool, - _pooledTxsRequestor, _gossipPolicy, new ForkInfo(_specProvider, _syncManager), LimboLogs.Instance); 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 f57b8ab3b748..12d649642205 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 @@ -1,8 +1,6 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Net; using DotNetty.Buffers; using FluentAssertions; using Nethermind.Consensus; @@ -18,8 +16,8 @@ using Nethermind.Network.P2P; using Nethermind.Network.P2P.Messages; using Nethermind.Network.P2P.Subprotocols; -using Nethermind.Network.P2P.Subprotocols.Eth; using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; +using Nethermind.Network.P2P.Subprotocols.Eth.V66; using Nethermind.Network.P2P.Subprotocols.Eth.V66.Messages; using Nethermind.Network.P2P.Subprotocols.Eth.V68; using Nethermind.Network.P2P.Subprotocols.Eth.V68.Messages; @@ -32,6 +30,8 @@ using Nethermind.TxPool; using NSubstitute; using NUnit.Framework; +using System; +using System.Net; namespace Nethermind.Network.Test.P2P.Subprotocols.Eth.V68; @@ -41,7 +41,6 @@ public class Eth68ProtocolHandlerTests private IMessageSerializationService _svc = null!; private ISyncServer _syncManager = null!; private ITxPool _transactionPool = null!; - private IPooledTxsRequestor _pooledTxsRequestor = null!; private IGossipPolicy _gossipPolicy = null!; private ISpecProvider _specProvider = null!; private Block _genesisBlock = null!; @@ -64,7 +63,6 @@ public void Setup() _session.When(s => s.DeliverMessage(Arg.Any())).Do(c => c.Arg().AddTo(_disposables)); _syncManager = Substitute.For(); _transactionPool = Substitute.For(); - _pooledTxsRequestor = Substitute.For(); _specProvider = Substitute.For(); _gossipPolicy = Substitute.For(); _genesisBlock = Build.A.Block.Genesis.TestObject; @@ -81,10 +79,11 @@ public void Setup() _syncManager, RunImmediatelyScheduler.Instance, _transactionPool, - _pooledTxsRequestor, _gossipPolicy, new ForkInfo(_specProvider, _syncManager), LimboLogs.Instance, + Substitute.For(), + Substitute.For(), _txGossipPolicy); _handler.Init(); } @@ -123,13 +122,12 @@ public void Can_handle_NewPooledTransactions_message([Values(0, 1, 2, 100)] int HandleIncomingStatusMessage(); HandleZeroMessage(msg, Eth68MessageCode.NewPooledTransactionHashes); - _pooledTxsRequestor.Received(canGossipTransactions ? 1 : 0).RequestTransactionsEth68(Arg.Any>(), - Arg.Any>(), Arg.Any>(), Arg.Any>()); + _session.Received(canGossipTransactions && txCount != 0 ? 1 : 0).DeliverMessage(Arg.Any()); } [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); @@ -149,6 +147,38 @@ public void Should_throw_when_sizes_doesnt_match(bool removeSize) action.Should().Throw(); } + + [Test] + public void Should_disconnect_if_tx_size_is_wrong() + { + GenerateTxLists(4, out ArrayPoolList types, out ArrayPoolList sizes, out ArrayPoolList hashes, out ArrayPoolList txs); + sizes[0] += 10; + using NewPooledTransactionHashesMessage68 hashesMsg = new(types, sizes, hashes); + using PooledTransactionsMessage txsMsg = new(1111, new(txs)); + + HandleIncomingStatusMessage(); + HandleZeroMessage(hashesMsg, Eth68MessageCode.NewPooledTransactionHashes); + HandleZeroMessage(txsMsg, Eth66MessageCode.PooledTransactions); + + _session.Received().InitiateDisconnect(DisconnectReason.BackgroundTaskFailure, "invalid pooled tx type or size"); + } + + + [Test] + public void Should_disconnect_if_tx_type_is_wrong() + { + GenerateTxLists(4, out ArrayPoolList types, out ArrayPoolList sizes, out ArrayPoolList hashes, out ArrayPoolList txs); + types[0]++; + using NewPooledTransactionHashesMessage68 hashesMsg = new(types, sizes, hashes); + using PooledTransactionsMessage txsMsg = new(1111, new(txs)); + + HandleIncomingStatusMessage(); + HandleZeroMessage(hashesMsg, Eth68MessageCode.NewPooledTransactionHashes); + HandleZeroMessage(txsMsg, Eth66MessageCode.PooledTransactions); + + _session.Received().InitiateDisconnect(DisconnectReason.BackgroundTaskFailure, "invalid pooled tx type or size"); + } + [Test] public void Should_process_huge_transaction() { @@ -161,8 +191,7 @@ public void Should_process_huge_transaction() HandleIncomingStatusMessage(); HandleZeroMessage(msg, Eth68MessageCode.NewPooledTransactionHashes); - _pooledTxsRequestor.Received(1).RequestTransactionsEth68(Arg.Any>(), - Arg.Any>(), Arg.Any>(), Arg.Any>()); + _session.Received(1).DeliverMessage(Arg.Any()); } [TestCase(1)] @@ -230,13 +259,14 @@ public void should_divide_GetPooledTransactionsMessage_if_max_message_size_is_ex _syncManager, RunImmediatelyScheduler.Instance, _transactionPool, - new PooledTxsRequestor(_transactionPool, new TxPoolConfig() { MaxTxSize = sizeOfOneTx }, _specProvider), _gossipPolicy, new ForkInfo(_specProvider, _syncManager), LimboLogs.Instance, + Substitute.For(), + Substitute.For(), _txGossipPolicy); - int maxNumberOfTxsInOneMsg = sizeOfOneTx < TransactionsMessage.MaxPacketSize ? TransactionsMessage.MaxPacketSize / sizeOfOneTx : 1; + int maxNumberOfTxsInOneMsg = int.Min(sizeOfOneTx < TransactionsMessage.MaxPacketSize ? TransactionsMessage.MaxPacketSize / sizeOfOneTx : 1, 256); int messagesCount = numberOfTransactions / maxNumberOfTxsInOneMsg + (numberOfTransactions % maxNumberOfTxsInOneMsg == 0 ? 0 : 1); using ArrayPoolList types = new(numberOfTransactions); @@ -276,20 +306,28 @@ private void HandleZeroMessage(T msg, byte messageCode) where T : MessageBase } private void GenerateLists(int txCount, out ArrayPoolList types, out ArrayPoolList sizes, out ArrayPoolList hashes) + { + GenerateTxLists(txCount, out types, out sizes, out hashes, out ArrayPoolList txs); + txs.Dispose(); + } + + private void GenerateTxLists(int txCount, out ArrayPoolList types, out ArrayPoolList sizes, out ArrayPoolList hashes, out ArrayPoolList txs) { TxDecoder txDecoder = TxDecoder.Instance; types = new(txCount); sizes = new(txCount); hashes = new(txCount); + txs = new(txCount); for (int i = 0; i < txCount; ++i) { - Transaction tx = Build.A.Transaction.WithType((TxType)(i % 3)).WithData(new byte[i]) + Transaction tx = Build.A.Transaction.WithType((TxType)(i % 3)).SignedAndResolved().WithData(new byte[i]) .WithHash(i % 2 == 0 ? TestItem.KeccakA : TestItem.KeccakB).TestObject; types.Add((byte)tx.Type); sizes.Add(txDecoder.GetLength(tx, RlpBehaviors.None)); hashes.Add(tx.Hash); + txs.Add(tx); } } } diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V69/Eth69ProtocolHandlerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V69/Eth69ProtocolHandlerTests.cs index da8780ea3cec..e8045316d508 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V69/Eth69ProtocolHandlerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V69/Eth69ProtocolHandlerTests.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; @@ -18,10 +18,10 @@ using Nethermind.Core.Test.Builders; using Nethermind.Core.Timers; using Nethermind.Logging; +using Nethermind.Network.Contract.P2P; using Nethermind.Network.P2P; using Nethermind.Network.P2P.Messages; using Nethermind.Network.P2P.Subprotocols; -using Nethermind.Network.P2P.Subprotocols.Eth; using Nethermind.Network.P2P.Subprotocols.Eth.V63; using Nethermind.Network.P2P.Subprotocols.Eth.V66; using Nethermind.Network.P2P.Subprotocols.Eth.V66.Messages; @@ -45,7 +45,6 @@ public class Eth69ProtocolHandlerTests private IMessageSerializationService _svc = null!; private ISyncServer _syncManager = null!; private ITxPool _transactionPool = null!; - private IPooledTxsRequestor _pooledTxsRequestor = null!; private IGossipPolicy _gossipPolicy = null!; private ISpecProvider _specProvider = null!; private Block _genesisBlock = null!; @@ -63,12 +62,12 @@ public void Setup() _session.Node.Returns(node); _syncManager = Substitute.For(); _transactionPool = Substitute.For(); - _pooledTxsRequestor = Substitute.For(); _specProvider = Substitute.For(); _gossipPolicy = Substitute.For(); _genesisBlock = Build.A.Block.Genesis.TestObject; _syncManager.Head.Returns(_genesisBlock.Header); _syncManager.Genesis.Returns(_genesisBlock.Header); + _syncManager.LowestBlock.Returns(0); _timerFactory = Substitute.For(); _txGossipPolicy = Substitute.For(); _txGossipPolicy.ShouldListenToGossipedTransactions.Returns(true); @@ -81,10 +80,11 @@ public void Setup() _syncManager, RunImmediatelyScheduler.Instance, _transactionPool, - _pooledTxsRequestor, _gossipPolicy, new ForkInfo(_specProvider, _syncManager), LimboLogs.Instance, + Substitute.For(), + _specProvider, _txGossipPolicy); _handler.Init(); } @@ -265,6 +265,18 @@ public void Should_disconnect_on_invalid_BlockRangeUpdate_empty_hash() _session.Received().InitiateDisconnect(DisconnectReason.InvalidBlockRangeUpdate, Arg.Any()); } + [Test] + public void On_init_sends_a_status_message() + { + // init is called in Setup + _session.Received(1).DeliverMessage(Arg.Is(m => + m.ProtocolVersion == 69 + && m.Protocol == Protocol.Eth + && m.GenesisHash == _genesisBlock.Hash + && m.LatestBlockHash == _genesisBlock.Hash + && m.EarliestBlock == 0)); + } + private void HandleIncomingStatusMessage() { using var statusMsg = new StatusMessage69 { ProtocolVersion = 69, GenesisHash = _genesisBlock.Hash!, LatestBlockHash = _genesisBlock.Hash! }; diff --git a/src/Nethermind/Nethermind.Network.Test/PacketTests.cs b/src/Nethermind/Nethermind.Network.Test/PacketTests.cs index 7d04dd647669..d48385292c89 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 5a15c5b00e11..36ab108cbd36 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"; @@ -78,8 +78,7 @@ public async Task Will_connect_to_a_candidate_node() ctx.SetupPersistedPeers(1); ctx.PeerPool.Start(); ctx.PeerManager.Start(); - await Task.Delay(_travisDelay); - Assert.That(ctx.RlpxPeer.ConnectAsyncCallsCount, Is.EqualTo(1)); + Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.EqualTo(1).After(_delay, 10)); } [Test, Retry(3)] @@ -89,14 +88,14 @@ public async Task Will_only_connect_up_to_max_peers() ctx.SetupPersistedPeers(50); ctx.PeerPool.Start(); ctx.PeerManager.Start(); - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); int expectedConnectCount = 25; Assert.That( () => ctx.RlpxPeer.ConnectAsyncCallsCount, Is .InRange(expectedConnectCount, expectedConnectCount + 1) - .After(_travisDelay * 10, 10)); + .After(_delay * 10, 10)); } [Test] @@ -120,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() @@ -163,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() { @@ -216,6 +184,7 @@ public async Task Will_accept_static_connection() [TestCase(false, ConnectionDirection.In)] // [TestCase(true, ConnectionDirection.Out)] // cannot create an active peer waiting for the test [TestCase(false, ConnectionDirection.Out)] + [NonParallelizable] public async Task Will_agree_on_which_session_to_disconnect_when_connecting_at_once(bool shouldLose, ConnectionDirection firstDirection) { @@ -239,9 +208,12 @@ void EnsureSession(ISession? session) { if (session is null) return; if (session.State < SessionState.HandshakeComplete) session.Handshake(session.Node.Id); - session.Init(5, context, packetSender); + if (session.State < SessionState.Initialized) session.Init(5, context, packetSender); } + bool expectedOutSessionClosing = firstDirection == ConnectionDirection.In ? shouldLose : !shouldLose; + bool expectedInSessionClosing = !expectedOutSessionClosing; + if (firstDirection == ConnectionDirection.In) { ctx.RlpxPeer.CreateIncoming(session1); @@ -249,12 +221,6 @@ void EnsureSession(ISession? session) { throw new NetworkingException($"Failed to connect to {session1.Node:s}", NetworkExceptionType.TargetUnreachable); } - - EnsureSession(ctx.PeerManager.ActivePeers.First().OutSession); - EnsureSession(ctx.PeerManager.ActivePeers.First().InSession); - - (ctx.PeerManager.ActivePeers.First().OutSession?.IsClosing ?? true).Should().Be(shouldLose); - (ctx.PeerManager.ActivePeers.First().InSession?.IsClosing ?? true).Should().Be(!shouldLose); } else { @@ -265,15 +231,23 @@ void EnsureSession(ISession? session) } ctx.RlpxPeer.SessionCreated -= HandshakeOnCreate; ctx.RlpxPeer.CreateIncoming(session1); + } - EnsureSession(ctx.PeerManager.ActivePeers.First().OutSession); - EnsureSession(ctx.PeerManager.ActivePeers.First().InSession); + Assert.That(() => + { + Peer? activePeer = ctx.PeerManager.ActivePeers.SingleOrDefault(); + if (activePeer is null) return false; - (ctx.PeerManager.ActivePeers.First().OutSession?.IsClosing ?? true).Should().Be(!shouldLose); - (ctx.PeerManager.ActivePeers.First().InSession?.IsClosing ?? true).Should().Be(shouldLose); - } + EnsureSession(activePeer.OutSession); + EnsureSession(activePeer.InSession); - ctx.PeerManager.ActivePeers.Count.Should().Be(1); + return activePeer.OutSession is not null + && activePeer.InSession is not null + && activePeer.OutSession.IsClosing == expectedOutSessionClosing + && activePeer.InSession.IsClosing == expectedInSessionClosing; + }, Is.True.After(_delayLonger, 20)); + + Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(1).After(_delay, 10)); } private void HandshakeOnCreate(object sender, SessionEventArgs e) @@ -288,11 +262,11 @@ public async Task Will_fill_up_on_disconnects() ctx.SetupPersistedPeers(50); ctx.PeerPool.Start(); ctx.PeerManager.Start(); - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); Assert.That(ctx.RlpxPeer.ConnectAsyncCallsCount, Is.AtLeast(25)); ctx.DisconnectAllSessions(); - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); Assert.That(ctx.RlpxPeer.ConnectAsyncCallsCount, Is.AtLeast(50)); } @@ -305,8 +279,7 @@ public async Task Ok_if_fails_to_connect() ctx.PeerPool.Start(); ctx.PeerManager.Start(); - await Task.Delay(_travisDelay); - Assert.That(ctx.PeerManager.ActivePeers.Count, Is.EqualTo(0)); + Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(0).After(_delay, 10)); } [Test, Retry(3)] @@ -329,7 +302,7 @@ public async Task Will_fill_up_over_and_over_again_on_disconnects() { Assert.That( () => ctx.PeerPool.ActivePeers.Count, - Is.AtLeast(25).After(_travisDelayLonger * 2, 10)); + Is.AtLeast(25).After(_delayLonger * 2, 10)); ctx.DisconnectAllSessions(); } } @@ -351,8 +324,7 @@ public async Task Will_fill_up_over_and_over_again_on_newly_discovered() for (int i = 0; i < 10; i++) { ctx.DiscoverNew(25); - await Task.Delay(_travisDelay); - Assert.That(ctx.PeerManager.ActivePeers.Count, Is.EqualTo(25)); + Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(25).After(_delay, 10)); } } @@ -368,8 +340,8 @@ public async Task Will_not_stop_trying_on_rlpx_connection_failure() for (int i = 0; i < 10; i++) { ctx.DiscoverNew(25); - await Task.Delay(_travisDelay); - Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.EqualTo(25 * (i + 1)).After(1000, 10)); + await Task.Delay(_delay); + Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.EqualTo(25 * (i + 1)).After(_delayLonger, 10)); } } @@ -411,14 +383,13 @@ public async Task IfPeerAdded_with_invalid_chain_then_do_not_connect() ctx.PeerPool.GetOrAdd(networkNode); - await Task.Delay(_travisDelay); - ctx.PeerPool.ActivePeers.Count.Should().Be(0); + Assert.That(() => ctx.PeerPool.ActivePeers.Count, Is.EqualTo(0).After(_delay, 10)); } - private readonly int _travisDelay = 500; + private readonly int _delay = 500; - private readonly int _travisDelayLong = 1000; - private readonly int _travisDelayLonger = 3000; + private readonly int _delayLong = 1000; + private readonly int _delayLonger = 3000; [Test] [Ignore("Behaviour changed that allows peers to go over max if awaiting response")] @@ -432,8 +403,7 @@ public async Task Will_fill_up_with_incoming_over_and_over_again_on_disconnects( for (int i = 0; i < 10; i++) { ctx.CreateNewIncomingSessions(25); - await Task.Delay(_travisDelay); - Assert.That(ctx.PeerManager.ActivePeers.Count, Is.EqualTo(25)); + Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(25).After(_delay, 10)); } } @@ -452,10 +422,10 @@ public async Task Will_fill_up_over_and_over_again_on_disconnects_and_when_ids_k { currentCount += 25; maxCount += 50; - Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.InRange(currentCount, maxCount).After(_travisDelayLonger * 2, 10)); + Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.InRange(currentCount, maxCount).After(_delayLonger * 2, 10)); ctx.RlpxPeer.ConnectAsyncCallsCount.Should().BeInRange(currentCount, maxCount); ctx.HandshakeAllSessions(); - await Task.Delay(_travisDelay); + await Task.Delay(_delay); ctx.DisconnectAllSessions(); } @@ -484,10 +454,10 @@ public async Task Will_fill_up_over_and_over_again_on_disconnects_and_when_ids_k for (int i = 0; i < 10; i++) { currentCount += count; - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); ctx.RlpxPeer.ConnectAsyncCallsCount.Should().BeInRange(currentCount, currentCount + count); ctx.HandshakeAllSessions(); - await Task.Delay(_travisDelay); + await Task.Delay(_delay); ctx.DisconnectAllSessions(); } } @@ -509,12 +479,12 @@ public async Task Will_fill_up_over_and_over_again_on_disconnects_and_when_ids_k for (int i = 0; i < 10; i++) { currentCount += count; - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); ctx.RlpxPeer.ConnectAsyncCallsCount.Should().BeInRange(currentCount, currentCount + count); ctx.HandshakeAllSessions(); - await Task.Delay(_travisDelay); + await Task.Delay(_delay); ctx.CreateIncomingSessions(); - await Task.Delay(_travisDelay); + await Task.Delay(_delay); ctx.DisconnectAllSessions(); } } @@ -550,8 +520,7 @@ public async Task Will_load_static_nodes_and_connect_to_them() ctx.TestNodeSource.AddNode(new Node(TestItem.PublicKeyA, node.Host, node.Port)); } - await Task.Delay(_travisDelay); - ctx.PeerManager.ActivePeers.Count(static p => p.Node.IsStatic).Should().Be(nodesCount); + Assert.That(() => ctx.PeerManager.ActivePeers.Count(static p => p.Node.IsStatic), Is.EqualTo(nodesCount).After(_delay, 10)); } [Test, Retry(5)] @@ -564,7 +533,7 @@ public async Task Will_disconnect_on_remove_static_node() ctx.StaticNodesManager.DiscoverNodes(Arg.Any()).Returns(staticNodes.Select(n => new Node(n, true)).ToAsyncEnumerable()); ctx.PeerPool.Start(); ctx.PeerManager.Start(); - await Task.Delay(_travisDelay); + await Task.Delay(_delay); void DisconnectHandler(object o, DisconnectEventArgs e) => disconnections++; ctx.Sessions.ForEach(s => s.Disconnected += DisconnectHandler); @@ -585,7 +554,7 @@ public async Task Will_connect_and_disconnect_on_peer_management() ctx.PeerManager.Start(); var node = new NetworkNode(ctx.GenerateEnode()); ctx.PeerPool.GetOrAdd(node); - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); void DisconnectHandler(object o, DisconnectEventArgs e) => disconnections++; ctx.PeerManager.ActivePeers.Select(p => p.Node.Id).Should().BeEquivalentTo(new[] { node.NodeId }); @@ -607,7 +576,7 @@ public async Task Will_only_add_same_peer_once() ctx.PeerPool.GetOrAdd(node); ctx.PeerPool.GetOrAdd(node); ctx.PeerPool.GetOrAdd(node); - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); ctx.PeerManager.ActivePeers.Should().HaveCount(1); } @@ -618,8 +587,7 @@ public async Task RemovePeer_should_fail_if_peer_not_added() ctx.PeerPool.Start(); ctx.PeerManager.Start(); var node = new NetworkNode(ctx.GenerateEnode()); - await Task.Delay(_travisDelay); - ctx.PeerPool.TryRemove(node.NodeId, out _).Should().BeFalse(); + Assert.That(() => ctx.PeerPool.TryRemove(node.NodeId, out _), Is.False.After(_delay, 10)); } private class Context : IAsyncDisposable diff --git a/src/Nethermind/Nethermind.Network.Test/PeerPoolTests.cs b/src/Nethermind/Nethermind.Network.Test/PeerPoolTests.cs index 92e13cb55d6a..cca52bbda265 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 9407bf01a298..19271cce2dbe 100644 --- a/src/Nethermind/Nethermind.Network.Test/ProtocolsManagerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/ProtocolsManagerTests.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.Numerics; @@ -10,6 +10,7 @@ using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; using Nethermind.Core.Timers; @@ -20,19 +21,15 @@ using Nethermind.Network.P2P.Analyzers; using Nethermind.Network.P2P.Messages; using Nethermind.Network.P2P.ProtocolHandlers; -using Nethermind.Network.P2P.Subprotocols.Eth; using Nethermind.Network.P2P.Subprotocols.Eth.V62; using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; using Nethermind.Network.Rlpx; using Nethermind.Specs; -using Nethermind.Evm.State; using Nethermind.State; -using Nethermind.State.SnapServer; using Nethermind.Stats; using Nethermind.Stats.Model; using Nethermind.Synchronization; using Nethermind.Synchronization.Peers; -using Nethermind.Synchronization.SnapSync; using Nethermind.TxPool; using NSubstitute; using NUnit.Framework; @@ -65,7 +62,6 @@ public class Context private readonly ISyncServer _syncServer; private readonly ISyncPeerPool _syncPeerPool; private readonly ITxPool _txPool; - private readonly IPooledTxsRequestor _pooledTxsRequestor; private readonly IChannelHandlerContext _channelHandlerContext; private readonly IChannel _channel; private readonly IChannelPipeline _pipeline; @@ -82,14 +78,13 @@ public Context() _pipeline = Substitute.For(); _channelHandlerContext.Channel.Returns(_channel); _channel.Pipeline.Returns(_pipeline); - _pipeline.Get().Returns(new ZeroPacketSplitter(LimboLogs.Instance)); + _pipeline.Get().Returns(new ZeroPacketSplitter()); _packetSender = Substitute.For(); _syncServer = Substitute.For(); _syncServer = Substitute.For(); _syncServer.Genesis.Returns(Build.A.Block.Genesis.TestObject.Header); _syncServer.Head.Returns(Build.A.BlockHeader.TestObject); _txPool = Substitute.For(); - _pooledTxsRequestor = Substitute.For(); _discoveryApp = Substitute.For(); _serializer = new MessageSerializationService( @@ -107,7 +102,7 @@ public Context() _blockTree.NetworkId.Returns((ulong)TestBlockchainIds.NetworkId); _blockTree.ChainId.Returns((ulong)TestBlockchainIds.ChainId); _blockTree.Genesis.Returns(Build.A.Block.Genesis.TestObject.Header); - ForkInfo forkInfo = new ForkInfo(MainnetSpecProvider.Instance, _syncServer); + ForkInfo forkInfo = new(MainnetSpecProvider.Instance, _syncServer); _peerManager = Substitute.For(); _networkConfig = new NetworkConfig(); _protocolValidator = new ProtocolValidator(_nodeStatsManager, _blockTree, forkInfo, _peerManager, _networkConfig, LimboLogs.Instance); @@ -119,7 +114,6 @@ public Context() _syncServer, RunImmediatelyScheduler.Instance, _txPool, - _pooledTxsRequestor, _discoveryApp, _serializer, _rlpxHost, @@ -129,7 +123,9 @@ public Context() forkInfo, _gossipPolicy, Substitute.For(), - LimboLogs.Instance); + LimboLogs.Instance, + Substitute.For(), + Substitute.For()); } public Context CreateIncomingSession() @@ -223,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); @@ -243,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; } @@ -269,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; @@ -313,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); } @@ -325,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); } @@ -478,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() @@ -486,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/FrameHeaderReaderTests.cs b/src/Nethermind/Nethermind.Network.Test/Rlpx/FrameHeaderReaderTests.cs new file mode 100644 index 000000000000..3e92087321dd --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Test/Rlpx/FrameHeaderReaderTests.cs @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using DotNetty.Buffers; +using DotNetty.Codecs; +using Nethermind.Network.Rlpx; +using Nethermind.Serialization.Rlp; +using NUnit.Framework; + +namespace Nethermind.Network.Test.Rlpx +{ + [TestFixture] + public class FrameHeaderReaderTests + { + [Test] + [TestCaseSource(nameof(TotalPacketSizeExceedsLimitValidCases))] + [TestCaseSource(nameof(TotalPacketSizeExceedsLimitInvalidCases))] + public bool Throws_when_total_packet_size_exceeds_limit(int frameSize, long totalPacketSize) + { + FrameHeaderReader reader = new(); + IByteBuffer buffer = Unpooled.Buffer(Frame.HeaderSize); + + try + { + buffer.WriteByte(frameSize >> 16); + buffer.WriteByte(frameSize >> 8); + buffer.WriteByte(frameSize); + + NettyRlpStream stream = new(buffer); + int contentLength = Rlp.LengthOf(0) + Rlp.LengthOf(1) + Rlp.LengthOf(totalPacketSize); + buffer.EnsureWritable(Rlp.LengthOfSequence(contentLength)); + stream.StartSequence(contentLength); + stream.Encode(0); + stream.Encode(1); + stream.Encode(totalPacketSize); + + buffer.WriteZero(Frame.HeaderSize - buffer.WriterIndex); + + reader.ReadFrameHeader(buffer); + } + catch (CorruptedFrameException) + { + return false; + } + finally + { + buffer.Release(); + } + + return true; + } + + private static IEnumerable TotalPacketSizeExceedsLimitValidCases() + { + yield return new(32, 64) { TestName = "A normal packet", ExpectedResult = true }; + yield return new(1, SnappyParameters.MaxSnappyLength) { TestName = "Total_size_is_exactly_snappy_limit", ExpectedResult = true }; + } + + private static IEnumerable TotalPacketSizeExceedsLimitInvalidCases() + { + yield return new(1, (long)(SnappyParameters.MaxSnappyLength + 1)) { TestName = "Total_size_exceeds_snappy_limit_small_frame", ExpectedResult = false }; + yield return new(128, (long)(SnappyParameters.MaxSnappyLength + 256)) { TestName = "Total_size_exceeds_snappy_limit_mid_frame", ExpectedResult = false }; + yield return new(Frame.HeaderSize, (long)(int.MaxValue)) { TestName = "Total_size_exceeds_snappy_limit_max_value", ExpectedResult = false }; + yield return new(200, 100L) { TestName = "Frame_size_cannot_exceed_total_size", ExpectedResult = false }; + yield return new(1, (long)uint.MaxValue) { TestName = "Total_size_cannot_be_negative", ExpectedResult = false }; + } + } +} diff --git a/src/Nethermind/Nethermind.Network.Test/Rlpx/Handshake/EncryptionHandshakeServiceTests.cs b/src/Nethermind/Nethermind.Network.Test/Rlpx/Handshake/EncryptionHandshakeServiceTests.cs index c791e5a10b27..01e47e33d520 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/HobbitTests.cs b/src/Nethermind/Nethermind.Network.Test/Rlpx/HobbitTests.cs index c4fb7335feab..99907c20b1b4 100644 --- a/src/Nethermind/Nethermind.Network.Test/Rlpx/HobbitTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/Rlpx/HobbitTests.cs @@ -214,10 +214,10 @@ private EmbeddedChannel BuildEmbeddedChannel(StackType inbound, StackType outbou throw new NotSupportedException(); } - IChannelHandler decoder = new ZeroFrameDecoder(_frameCipherB, _macProcessorB, LimboLogs.Instance); + IChannelHandler decoder = new ZeroFrameDecoder(_frameCipherB, _macProcessorB); IChannelHandler merger = new ZeroFrameMerger(LimboLogs.Instance); - IChannelHandler encoder = new ZeroFrameEncoder(_frameCipherA, _macProcessorA, LimboLogs.Instance); - IFramingAware splitter = new ZeroPacketSplitter(LimboLogs.Instance); + IChannelHandler encoder = new ZeroFrameEncoder(_frameCipherA, _macProcessorA); + IFramingAware splitter = new ZeroPacketSplitter(); Assert.That(splitter.MaxFrameSize, Is.EqualTo(Frame.DefaultMaxFrameSize), "default max frame size"); diff --git a/src/Nethermind/Nethermind.Network.Test/Rlpx/RlpxPeerTests.cs b/src/Nethermind/Nethermind.Network.Test/Rlpx/RlpxPeerTests.cs index 3167063db3a1..1fd88a073327 100644 --- a/src/Nethermind/Nethermind.Network.Test/Rlpx/RlpxPeerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/Rlpx/RlpxPeerTests.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; diff --git a/src/Nethermind/Nethermind.Network.Test/Rlpx/TestWrappers/ZeroFrameDecoderTestWrapper.cs b/src/Nethermind/Nethermind.Network.Test/Rlpx/TestWrappers/ZeroFrameDecoderTestWrapper.cs index e5080230985b..8cd568259662 100644 --- a/src/Nethermind/Nethermind.Network.Test/Rlpx/TestWrappers/ZeroFrameDecoderTestWrapper.cs +++ b/src/Nethermind/Nethermind.Network.Test/Rlpx/TestWrappers/ZeroFrameDecoderTestWrapper.cs @@ -5,7 +5,6 @@ using DotNetty.Buffers; using DotNetty.Codecs; using DotNetty.Transport.Channels; -using Nethermind.Logging; using Nethermind.Network.Rlpx; using NSubstitute; @@ -15,7 +14,7 @@ internal class ZeroFrameDecoderTestWrapper : ZeroFrameDecoder { private readonly IChannelHandlerContext _context; - public ZeroFrameDecoderTestWrapper(IFrameCipher frameCipher, FrameMacProcessor frameMacProcessor) : base(frameCipher, frameMacProcessor, LimboLogs.Instance) + public ZeroFrameDecoderTestWrapper(IFrameCipher frameCipher, FrameMacProcessor frameMacProcessor) : base(frameCipher, frameMacProcessor) { _context = Substitute.For(); _context.Allocator.Returns(PooledByteBufferAllocator.Default); diff --git a/src/Nethermind/Nethermind.Network.Test/Rlpx/TestWrappers/ZeroFrameEncoderTestWrapper.cs b/src/Nethermind/Nethermind.Network.Test/Rlpx/TestWrappers/ZeroFrameEncoderTestWrapper.cs index f11176d23914..e2de74447076 100644 --- a/src/Nethermind/Nethermind.Network.Test/Rlpx/TestWrappers/ZeroFrameEncoderTestWrapper.cs +++ b/src/Nethermind/Nethermind.Network.Test/Rlpx/TestWrappers/ZeroFrameEncoderTestWrapper.cs @@ -3,7 +3,6 @@ using DotNetty.Buffers; using DotNetty.Transport.Channels; -using Nethermind.Logging; using Nethermind.Network.Rlpx; using NSubstitute; @@ -13,7 +12,7 @@ internal class ZeroFrameEncoderTestWrapper : ZeroFrameEncoder { private readonly IChannelHandlerContext _context; - public ZeroFrameEncoderTestWrapper(IFrameCipher frameCipher, IFrameMacProcessor frameMacProcessor) : base(frameCipher, frameMacProcessor, LimboLogs.Instance) + public ZeroFrameEncoderTestWrapper(IFrameCipher frameCipher, IFrameMacProcessor frameMacProcessor) : base(frameCipher, frameMacProcessor) { _context = Substitute.For(); } diff --git a/src/Nethermind/Nethermind.Network.Test/Rlpx/TestWrappers/ZeroPacketSplitterTestWrapper.cs b/src/Nethermind/Nethermind.Network.Test/Rlpx/TestWrappers/ZeroPacketSplitterTestWrapper.cs index ea4766ad39f2..5049c93037c0 100644 --- a/src/Nethermind/Nethermind.Network.Test/Rlpx/TestWrappers/ZeroPacketSplitterTestWrapper.cs +++ b/src/Nethermind/Nethermind.Network.Test/Rlpx/TestWrappers/ZeroPacketSplitterTestWrapper.cs @@ -3,7 +3,6 @@ using DotNetty.Buffers; using DotNetty.Transport.Channels; -using Nethermind.Logging; using Nethermind.Network.Rlpx; using NSubstitute; @@ -24,7 +23,7 @@ public IByteBuffer Encode(IByteBuffer input) return result; } - public ZeroPacketSplitterTestWrapper() : base(LimboLogs.Instance) + public ZeroPacketSplitterTestWrapper() : base() { _context.Allocator.Returns(PooledByteBufferAllocator.Default); } diff --git a/src/Nethermind/Nethermind.Network.Test/Rlpx/ZeroNettyFrameEncodeDecodeTests.cs b/src/Nethermind/Nethermind.Network.Test/Rlpx/ZeroNettyFrameEncodeDecodeTests.cs index 6304be58efaa..571f6bced6cc 100644 --- a/src/Nethermind/Nethermind.Network.Test/Rlpx/ZeroNettyFrameEncodeDecodeTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/Rlpx/ZeroNettyFrameEncodeDecodeTests.cs @@ -40,10 +40,10 @@ public async Task TwoWayConcurrentEncodeDecodeTests() private async Task RunStreamTests(FrameCipher frameCipher, FrameMacProcessor macProcessor, FrameCipher frameCipher2, FrameMacProcessor macProcessor2) { - ZeroPacketSplitter splitter = new(LimboLogs.Instance); - ZeroFrameEncoder encoder = new(frameCipher, macProcessor, LimboLogs.Instance); + ZeroPacketSplitter splitter = new(); + ZeroFrameEncoder encoder = new(frameCipher, macProcessor); - ZeroFrameDecoder decoder = new(frameCipher2, macProcessor2, LimboLogs.Instance); + ZeroFrameDecoder decoder = new(frameCipher2, macProcessor2); ZeroFrameMerger frameMerger = new(LimboLogs.Instance); IByteBuffer reDecoded = null; @@ -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.Test/Rlpx/ZeroNettyFrameMergerTests.cs b/src/Nethermind/Nethermind.Network.Test/Rlpx/ZeroNettyFrameMergerTests.cs index 0d060f279e4e..c6caabd12788 100644 --- a/src/Nethermind/Nethermind.Network.Test/Rlpx/ZeroNettyFrameMergerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/Rlpx/ZeroNettyFrameMergerTests.cs @@ -4,7 +4,6 @@ using DotNetty.Buffers; using DotNetty.Transport.Channels; using Nethermind.Core.Extensions; -using Nethermind.Logging; using Nethermind.Network.P2P.Messages; using Nethermind.Network.Rlpx; using Nethermind.Network.Test.Rlpx.TestWrappers; @@ -25,7 +24,7 @@ public void Encode(IByteBuffer input, IByteBuffer output) base.Encode(_context, input, output); } - public TestFrameHelper() : base(LimboLogs.Instance) + public TestFrameHelper() : base() { } } diff --git a/src/Nethermind/Nethermind.Network.Test/StaticNodesManagerTests.cs b/src/Nethermind/Nethermind.Network.Test/StaticNodesManagerTests.cs index d1d1f4af61c1..6ecb90bf5e3e 100644 --- a/src/Nethermind/Nethermind.Network.Test/StaticNodesManagerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/StaticNodesManagerTests.cs @@ -57,6 +57,17 @@ public async Task is_static_should_report_correctly() _staticNodesManager.IsStatic(Enode).Should().BeTrue(); } + [Test] + public async Task add_should_emit_node_with_static_flag() + { + ValueTask> listTask = _staticNodesManager.DiscoverNodes(default).Take(1).ToListAsync(); + + await _staticNodesManager.AddAsync(Enode, false); + List nodes = await listTask; + + nodes[0].IsStatic.Should().BeTrue(); + } + [Test] public async Task remove_should_delete_an_existing_static_node_and_trigger_an_event() { diff --git a/src/Nethermind/Nethermind.Network/Config/NetworkConfig.cs b/src/Nethermind/Nethermind.Network/Config/NetworkConfig.cs index dc97e0b48c4d..016a4fcf6fe8 100644 --- a/src/Nethermind/Nethermind.Network/Config/NetworkConfig.cs +++ b/src/Nethermind/Nethermind.Network/Config/NetworkConfig.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using Nethermind.Config; using Nethermind.Core; namespace Nethermind.Network.Config @@ -30,7 +31,7 @@ public class NetworkConfig : INetworkConfig public bool DiagTracerEnabled { get; set; } = false; public int NettyArenaOrder { get; set; } = INetworkConfig.DefaultNettyArenaOrder; public uint MaxNettyArenaCount { get; set; } = INetworkConfig.DefaultMaxNettyArenaCount; - public string Bootnodes { get; set; } = string.Empty; + public NetworkNode[] Bootnodes { get; set; } = []; public bool EnableUPnP { get; set; } = false; public int DiscoveryPort { get; set; } = 30303; public int P2PPort { get; set; } = 30303; diff --git a/src/Nethermind/Nethermind.Network/ForkId.cs b/src/Nethermind/Nethermind.Network/ForkId.cs index e1481984bcc6..2f740229ab9a 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/INodeSource.cs b/src/Nethermind/Nethermind.Network/INodeSource.cs index 4270bbc40e82..2d1db2c89912 100644 --- a/src/Nethermind/Nethermind.Network/INodeSource.cs +++ b/src/Nethermind/Nethermind.Network/INodeSource.cs @@ -1,15 +1,16 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Stats.Model; using System; using System.Collections.Generic; using System.Threading; -using Nethermind.Stats.Model; namespace Nethermind.Network; public interface INodeSource { IAsyncEnumerable DiscoverNodes(CancellationToken cancellationToken); + event EventHandler NodeRemoved; } diff --git a/src/Nethermind/Nethermind.Network/IPeerManager.cs b/src/Nethermind/Nethermind.Network/IPeerManager.cs index 41b62f4a4496..9aee7223c591 100644 --- a/src/Nethermind/Nethermind.Network/IPeerManager.cs +++ b/src/Nethermind/Nethermind.Network/IPeerManager.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Threading.Tasks; using Nethermind.Core.ServiceStopper; namespace Nethermind.Network @@ -14,5 +13,6 @@ public interface IPeerManager : IStoppableService IReadOnlyCollection ConnectedPeers { get; } int MaxActivePeers { get; } int ActivePeersCount { get; } + int ConnectedPeersCount { get; } } } diff --git a/src/Nethermind/Nethermind.Network/IPeerPool.cs b/src/Nethermind/Nethermind.Network/IPeerPool.cs index 22bc132780c9..0fd4e6a5264a 100644 --- a/src/Nethermind/Nethermind.Network/IPeerPool.cs +++ b/src/Nethermind/Nethermind.Network/IPeerPool.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Threading.Tasks; using Nethermind.Config; using Nethermind.Core.Crypto; using Nethermind.Core.ServiceStopper; diff --git a/src/Nethermind/Nethermind.Network/IProtocolsManager.cs b/src/Nethermind/Nethermind.Network/IProtocolsManager.cs index 8c898eebb5cb..d56620f0371f 100644 --- a/src/Nethermind/Nethermind.Network/IProtocolsManager.cs +++ b/src/Nethermind/Nethermind.Network/IProtocolsManager.cs @@ -12,7 +12,6 @@ public interface IProtocolsManager { void AddSupportedCapability(Capability capability); void RemoveSupportedCapability(Capability capability); - void SendNewCapability(Capability capability); // TODO: remove as unused? void AddProtocol(string code, Func factory); int GetHighestProtocolVersion(string protocol); } diff --git a/src/Nethermind/Nethermind.Network/IStaticNodesManager.cs b/src/Nethermind/Nethermind.Network/IStaticNodesManager.cs index 070e03d23c30..fe4237025bc4 100644 --- a/src/Nethermind/Nethermind.Network/IStaticNodesManager.cs +++ b/src/Nethermind/Nethermind.Network/IStaticNodesManager.cs @@ -5,14 +5,13 @@ using System.Threading.Tasks; using Nethermind.Config; -namespace Nethermind.Network +namespace Nethermind.Network; + +public interface IStaticNodesManager : INodeSource { - public interface IStaticNodesManager : INodeSource - { - IEnumerable Nodes { get; } - Task InitAsync(); - Task AddAsync(string enode, bool updateFile = true); - Task RemoveAsync(string enode, bool updateFile = true); - bool IsStatic(string enode); - } + IEnumerable Nodes { get; } + Task InitAsync(); + Task AddAsync(string enode, bool updateFile = true); + Task RemoveAsync(string enode, bool updateFile = true); + bool IsStatic(string enode); } diff --git a/src/Nethermind/Nethermind.Network/ITrustedNodesManager.cs b/src/Nethermind/Nethermind.Network/ITrustedNodesManager.cs index bb8f909f0b4e..46cfb73811af 100644 --- a/src/Nethermind/Nethermind.Network/ITrustedNodesManager.cs +++ b/src/Nethermind/Nethermind.Network/ITrustedNodesManager.cs @@ -1,18 +1,17 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; using System.Threading.Tasks; using Nethermind.Config; -namespace Nethermind.Network +namespace Nethermind.Network; + +public interface ITrustedNodesManager : INodeSource { - public interface ITrustedNodesManager : INodeSource - { - IEnumerable Nodes { get; } - Task InitAsync(); - Task AddAsync(Enode enode, bool updateFile = true); - Task RemoveAsync(Enode enode, bool updateFile = true); - bool IsTrusted(Enode enode); - } + IEnumerable Nodes { get; } + Task InitAsync(); + Task AddAsync(Enode enode, bool updateFile = true); + Task RemoveAsync(Enode enode, bool updateFile = true); + bool IsTrusted(Enode enode); } diff --git a/src/Nethermind/Nethermind.Network/Metrics.cs b/src/Nethermind/Nethermind.Network/Metrics.cs index abbfa0c15fdd..6cd555d27095 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/Nethermind.Network.csproj b/src/Nethermind/Nethermind.Network/Nethermind.Network.csproj index 5906af767825..c37a0efa9e77 100644 --- a/src/Nethermind/Nethermind.Network/Nethermind.Network.csproj +++ b/src/Nethermind/Nethermind.Network/Nethermind.Network.csproj @@ -1,4 +1,4 @@ - + annotations @@ -15,24 +15,19 @@ - - - - - - - - - - - + + static-nodes.json + + + trusted-nodes.json + diff --git a/src/Nethermind/Nethermind.Network/NetworkNodeDecoder.cs b/src/Nethermind/Nethermind.Network/NetworkNodeDecoder.cs index 641d0657188e..9b08784346a2 100644 --- a/src/Nethermind/Nethermind.Network/NetworkNodeDecoder.cs +++ b/src/Nethermind/Nethermind.Network/NetworkNodeDecoder.cs @@ -10,20 +10,22 @@ namespace Nethermind.Network { - public class NetworkNodeDecoder : IRlpStreamDecoder, IRlpObjectDecoder + public sealed class NetworkNodeDecoder : RlpStreamDecoder, IRlpObjectDecoder { + private static readonly RlpLimit RlpLimit = RlpLimit.For((int)1.KiB(), nameof(NetworkNode.HostIp)); + static NetworkNodeDecoder() { Rlp.RegisterDecoder(typeof(NetworkNode), new NetworkNodeDecoder()); } - public NetworkNode Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override NetworkNode DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { rlpStream.ReadSequenceLength(); - PublicKey publicKey = new(rlpStream.DecodeByteArraySpan()); - string ip = rlpStream.DecodeString(); - int port = (int)rlpStream.DecodeByteArraySpan().ReadEthUInt64(); + PublicKey publicKey = new(rlpStream.DecodeByteArraySpan(RlpLimit.L64)); + string ip = rlpStream.DecodeString(RlpLimit); + int port = (int)rlpStream.DecodeByteArraySpan(RlpLimit.L8).ReadEthUInt64(); rlpStream.SkipItem(); long reputation = 0L; try @@ -39,7 +41,7 @@ public NetworkNode Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBe return networkNode; } - public void Encode(RlpStream stream, NetworkNode item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, NetworkNode item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { int contentLength = GetContentLength(item, rlpBehaviors); stream.StartSequence(contentLength); @@ -68,7 +70,7 @@ public void Encode(MemoryStream stream, NetworkNode item, RlpBehaviors rlpBehavi throw new NotImplementedException(); } - public int GetLength(NetworkNode item, RlpBehaviors rlpBehaviors) + public override int GetLength(NetworkNode item, RlpBehaviors rlpBehaviors) { return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); } diff --git a/src/Nethermind/Nethermind.Network/NodesLoader.cs b/src/Nethermind/Nethermind.Network/NodesLoader.cs index 76ba673e3bab..d1aed06a0bc6 100644 --- a/src/Nethermind/Nethermind.Network/NodesLoader.cs +++ b/src/Nethermind/Nethermind.Network/NodesLoader.cs @@ -115,6 +115,11 @@ private static void LoadConfigPeers(List peers, IEnumerable n { foreach (NetworkNode networkNode in networkNodes) { + if (!networkNode.IsEnode) + { + continue; + } + Node node = new(networkNode); nodeUpdate.Invoke(node); peers.Add(node); diff --git a/src/Nethermind/Nethermind.Network/NodesManager.cs b/src/Nethermind/Nethermind.Network/NodesManager.cs new file mode 100644 index 000000000000..adf31ca774e8 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/NodesManager.cs @@ -0,0 +1,145 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Config; +using Nethermind.Core.Crypto; +using Nethermind.Logging; +using Nethermind.Serialization.Json; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Nethermind.Network; + +public abstract class NodesManager(string path, ILogger logger) +{ + private static readonly char[] _separator = ['\r', '\n']; + + protected readonly ILogger _logger = logger; + protected ConcurrentDictionary _nodes = []; + + private void EnsureFile(string resource) + { + if (File.Exists(path)) + { + return; + } + else // For backward compatibility. To be removed in future versions. + { + string oldPath = Path.GetFullPath($"Data/{resource}".GetApplicationResourcePath()); + + if (File.Exists(oldPath)) + { + var moved = true; + + try + { + File.Move(oldPath, path, false); + } + catch (Exception ex) + { + moved = false; + + if (_logger.IsWarn) + _logger.Warn($"Failed to move {oldPath} to {Path.GetFullPath(path)}: {ex.Message}\n {resource} is ignored and will not be used"); + } + + if (moved) + { + if (_logger.IsWarn) + _logger.Warn($"{oldPath} has been moved to {Path.GetFullPath(path)}"); + + return; + } + } + } + + // Create the directory if needed + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + if (_logger.IsDebug) _logger.Debug($"Nodes file was not found, creating one at {Path.GetFullPath(path)}"); + + using Stream actualNodes = File.Create(path); + using Stream embeddedNodes = typeof(NodesManager).Assembly.GetManifestResourceStream(resource); + + if (embeddedNodes is null) + { + if (_logger.IsDebug) _logger.Debug($"Embedded resource {resource} was not found"); + + File.WriteAllText(path, "[]\n"); + } + else + { + embeddedNodes.CopyTo(actualNodes); + } + } + + protected virtual void LogNodeList(string title, IDictionary nodes) + { + if (_logger.IsDebug && nodes.Count != 0) + { + var separator = $"{Environment.NewLine} "; + + _logger.Debug($"{title}:{separator}{string.Join(separator, nodes.Values.Select(n => n.ToString()))}"); + } + } + + protected virtual async Task> ParseNodes(string fallbackResource) + { + EnsureFile(fallbackResource); + + string data = await File.ReadAllTextAsync(path); + + IEnumerable? rawNodes; + + try + { + rawNodes = JsonSerializer.Deserialize>(data); + } + catch (JsonException) + { + rawNodes = data.Split(_separator, StringSplitOptions.RemoveEmptyEntries).ToHashSet(); + } + + ConcurrentDictionary nodes = []; + + foreach (string? n in rawNodes ?? []) + { + NetworkNode node; + + try + { + node = new(n); + } + catch (ArgumentException ex) + { + if (_logger.IsError) _logger.Error($"Failed to parse node: {n}", ex); + + continue; + } + + nodes.TryAdd(node.NodeId, node); + } + + if (_logger.IsInfo) + _logger.Info($"Loaded {nodes.Count} nodes from {Path.GetFullPath(path)}"); + + return nodes; + } + + protected virtual Task SaveFileAsync() + { + ArgumentException.ThrowIfNullOrWhiteSpace(path); + + string contents = JsonSerializer.Serialize( + _nodes.Select(static n => n.Value.ToString()), + EthereumJsonSerializer.JsonOptionsIndented + ); + + return File.WriteAllTextAsync(path, contents); + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/ISession.cs b/src/Nethermind/Nethermind.Network/P2P/ISession.cs index 69d1ca2ae615..f8616a02fc32 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ISession.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ISession.cs @@ -29,7 +29,6 @@ public interface ISession : IDisposable Node Node { get; } DateTime LastPingUtc { get; set; } DateTime LastPongUtc { get; set; } - void ReceiveMessage(Packet packet); void ReceiveMessage(ZeroPacket zeroPacket); int DeliverMessage(T message) where T : P2PMessage; void EnableSnappy(); diff --git a/src/Nethermind/Nethermind.Network/P2P/MessageQueue.cs b/src/Nethermind/Nethermind.Network/P2P/MessageQueue.cs index e8c6ef82f4b1..21f8bcd26995 100644 --- a/src/Nethermind/Nethermind.Network/P2P/MessageQueue.cs +++ b/src/Nethermind/Nethermind.Network/P2P/MessageQueue.cs @@ -47,16 +47,15 @@ public void Handle(TData data, long size) { if (_currentRequest is null) { - if (data is IDisposable d) - { - d.Dispose(); - } - + data.TryDispose(); throw new SubprotocolException($"Received a response to {nameof(TMsg)} that has not been requested"); } _currentRequest.ResponseSize = size; - _currentRequest.CompletionSource.SetResult(data); + if (!_currentRequest.CompletionSource.TrySetResult(data)) + { + data.TryDispose(); + } if (_requestQueue.TryDequeue(out _currentRequest)) { _currentRequest!.StartMeasuringTime(); diff --git a/src/Nethermind/Nethermind.Network/P2P/Messages/AddCapabilityMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Messages/AddCapabilityMessageSerializer.cs index 4a7983068236..8c9e24f43d51 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Messages/AddCapabilityMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Messages/AddCapabilityMessageSerializer.cs @@ -2,16 +2,19 @@ // SPDX-License-Identifier: LGPL-3.0-only using DotNetty.Buffers; +using Nethermind.Core.Extensions; using Nethermind.Serialization.Rlp; using Nethermind.Stats.Model; namespace Nethermind.Network.P2P.Messages { /// - /// This is probably used in NDM + /// Serializes P2P capability negotiation messages. /// public class AddCapabilityMessageSerializer : IZeroMessageSerializer { + private static readonly RlpLimit RlpLimit = RlpLimit.For((int)1.KiB(), nameof(Capability.ProtocolCode)); + public void Serialize(IByteBuffer byteBuffer, AddCapabilityMessage msg) { int totalLength = GetLength(msg, out int contentLength); @@ -27,7 +30,7 @@ public AddCapabilityMessage Deserialize(IByteBuffer byteBuffer) { NettyRlpStream context = new(byteBuffer); context.ReadSequenceLength(); - string protocolCode = context.DecodeString(); + string protocolCode = context.DecodeString(RlpLimit); byte version = context.DecodeByte(); return new AddCapabilityMessage(new Capability(protocolCode, version)); diff --git a/src/Nethermind/Nethermind.Network/P2P/Messages/DisconnectMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Messages/DisconnectMessageSerializer.cs index a8a53930f99f..416b30f1322e 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Messages/DisconnectMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Messages/DisconnectMessageSerializer.cs @@ -50,7 +50,7 @@ public DisconnectMessage Deserialize(IByteBuffer msgBytes) rlpStream.ReadSequenceLength(); int reason = rlpStream.DecodeInt(); - DisconnectMessage disconnectMessage = new DisconnectMessage(reason); + DisconnectMessage disconnectMessage = new(reason); return disconnectMessage; } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Messages/HelloMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Messages/HelloMessageSerializer.cs index b2ad3dea2a52..9b7db3f894ee 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,18 +60,22 @@ 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); }); helloMessage.ListenPort = rlpStream.DecodeInt(); - ReadOnlySpan publicKeyBytes = rlpStream.DecodeByteArraySpan(); + ReadOnlySpan publicKeyBytes = rlpStream.DecodeByteArraySpan(RlpLimit.L64); if (publicKeyBytes.Length != PublicKey.LengthInBytes && publicKeyBytes.Length != PublicKey.PrefixedLengthInBytes) { diff --git a/src/Nethermind/Nethermind.Network/P2P/P2PProtocolInfoProvider.cs b/src/Nethermind/Nethermind.Network/P2P/P2PProtocolInfoProvider.cs index dd9b2018eb9f..c44d9b9293ee 100644 --- a/src/Nethermind/Nethermind.Network/P2P/P2PProtocolInfoProvider.cs +++ b/src/Nethermind/Nethermind.Network/P2P/P2PProtocolInfoProvider.cs @@ -3,9 +3,6 @@ using System.Collections.Generic; using System.Linq; -using Nethermind.Network.Contract.P2P; -using Nethermind.Network.P2P.ProtocolHandlers; -using Nethermind.Stats.Model; namespace Nethermind.Network.P2P { diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/IProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/IProtocolHandler.cs index 0e9b4bbcfefb..8e710f7db6a1 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/IProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/IProtocolHandler.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; diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/P2PProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/P2PProtocolHandler.cs index 79ef7d152c77..e780285dcd1e 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/P2PProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/P2PProtocolHandler.cs @@ -6,10 +6,10 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using FastEnumUtility; +using Nethermind.Consensus.Scheduler; using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; @@ -29,8 +29,9 @@ public class P2PProtocolHandler( PublicKey localNodeId, INodeStatsManager nodeStatsManager, IMessageSerializationService serializer, + IBackgroundTaskScheduler backgroundTaskScheduler, ILogManager logManager) - : ProtocolHandlerBase(session, nodeStatsManager, serializer, logManager), IPingSender, IP2PProtocolHandler + : ProtocolHandlerBase(session, nodeStatsManager, serializer, backgroundTaskScheduler, logManager), IPingSender, IP2PProtocolHandler { private TaskCompletionSource _pongCompletionSource; private readonly INodeStatsManager _nodeStatsManager = nodeStatsManager ?? throw new ArgumentNullException(nameof(nodeStatsManager)); diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ProtocolHandlerBase.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ProtocolHandlerBase.cs index 68d2922f3923..1ba8ffcc5a38 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ProtocolHandlerBase.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ProtocolHandlerBase.cs @@ -1,14 +1,17 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using DotNetty.Buffers; +using Nethermind.Consensus.Scheduler; using Nethermind.Core; using Nethermind.Logging; using Nethermind.Network.P2P.EventArg; using Nethermind.Network.P2P.Messages; +using Nethermind.Network.P2P.Utils; using Nethermind.Network.Rlpx; using Nethermind.Serialization.Rlp; using Nethermind.Stats; @@ -25,36 +28,59 @@ public abstract class ProtocolHandlerBase : IProtocolHandler protected internal ISession Session { get; } protected long Counter; - private readonly TaskCompletionSource _initCompletionSource; + private readonly TaskCompletionSource _initCompletionSource = new(); - protected ProtocolHandlerBase(ISession session, INodeStatsManager nodeStats, IMessageSerializationService serializer, ILogManager logManager) + protected ProtocolHandlerBase(ISession session, + INodeStatsManager nodeStats, + IMessageSerializationService serializer, + IBackgroundTaskScheduler backgroundTaskScheduler, + ILogManager logManager) { - Logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); StatsManager = nodeStats ?? throw new ArgumentNullException(nameof(nodeStats)); - Session = session ?? throw new ArgumentNullException(nameof(session)); - _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); - _initCompletionSource = new TaskCompletionSource(); + Session = session ?? throw new ArgumentNullException(nameof(session)); + Logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + BackgroundTaskScheduler = new BackgroundTaskSchedulerWrapper(this, backgroundTaskScheduler); } protected internal ILogger Logger { get; } protected abstract TimeSpan InitTimeout { get; } + protected BackgroundTaskSchedulerWrapper BackgroundTaskScheduler { get; } + protected T Deserialize(byte[] data) where T : P2PMessage { + var size = data.Length; try { return _serializer.Deserialize(data); } + catch (RlpLimitException e) + { + HandleRlpLimitException(size, e); + throw; + } catch (RlpException e) { - if (Logger.IsDebug) Logger.Debug($"Failed to deserialize message {typeof(T).Name}, with exception {e}"); - ReportIn($"{typeof(T).Name} - Deserialization exception", data.Length); + HandleRlpException(size, e); throw; } } + private void HandleRlpException(int dataLength, RlpException e) where T : P2PMessage + { + if (Logger.IsDebug) Logger.Debug($"Failed to deserialize message {typeof(T).Name} on session {Session}, with exception {e}"); + ReportIn($"{typeof(T).Name} - Deserialization exception", dataLength); + } + + private void HandleRlpLimitException(int dataLength, RlpLimitException e) where T : P2PMessage + { + Session.InitiateDisconnect(DisconnectReason.MessageLimitsBreached, e.Message); + if (Logger.IsDebug) Logger.Debug($"Failed to deserialize message {typeof(T).Name} on session {Session} due to rlp limits, with exception {e}"); + ReportIn($"{typeof(T).Name} - Deserialization limit exception", dataLength); + } + protected T Deserialize(IByteBuffer data) where T : P2PMessage { int size = data.ReadableBytes; @@ -62,21 +88,29 @@ protected T Deserialize(IByteBuffer data) where T : P2PMessage { int originalReaderIndex = data.ReaderIndex; T result = _serializer.Deserialize(data); - if (data.IsReadable()) - { - throw new IncompleteDeserializationException( - $"Incomplete deserialization detected. Buffer is still readable. Read bytes: {data.ReaderIndex - originalReaderIndex}. Readable bytes: {data.ReadableBytes}"); - } + if (data.IsReadable()) ThrowIncompleteDeserializationException(data, originalReaderIndex); + if (Logger.IsTrace) Logger.Trace($"{Counter} Got {typeof(T).Name}"); + return result; } + catch (RlpLimitException e) + { + HandleRlpLimitException(size, e); + throw; + } catch (RlpException e) { - if (Logger.IsDebug) Logger.Debug($"Failed to deserialize message {typeof(T).Name}, with exception {e}"); - ReportIn($"{typeof(T).Name} - Deserialization exception", size); + HandleRlpException(size, e); throw; } } + [DoesNotReturn] + private static void ThrowIncompleteDeserializationException(IByteBuffer data, int originalReaderIndex) + { + throw new IncompleteDeserializationException($"Incomplete deserialization detected. Buffer is still readable. Read bytes: {data.ReaderIndex - originalReaderIndex}. Readable bytes: {data.ReadableBytes}"); + } + protected internal void Send(T message) where T : P2PMessage { Interlocked.Increment(ref Counter); @@ -131,19 +165,35 @@ protected void ReportIn(MessageBase msg, int size) { if (Logger.IsTrace || NetworkDiagTracer.IsEnabled) { - ReportIn(msg.ToString(), size); + ReportIn(msg.ToString() ?? "", size); } } protected void ReportIn(string messageInfo, int size) { if (Logger.IsTrace) - Logger.Trace($"OUT {Counter:D5} {messageInfo}"); + Logger.Trace($"IN {Counter:D5} {messageInfo}"); if (NetworkDiagTracer.IsEnabled) NetworkDiagTracer.ReportIncomingMessage(Session?.Node?.Address, Name, messageInfo, size); } + protected void HandleInBackground(ZeroPacket message, Func> handle) where TReq : P2PMessage where TRes : P2PMessage => + BackgroundTaskScheduler.TryScheduleSyncServe(DeserializeAndReport(message), handle); + + protected void HandleInBackground(ZeroPacket message, Func> handle) where TReq : P2PMessage where TRes : P2PMessage => + BackgroundTaskScheduler.TryScheduleSyncServe(DeserializeAndReport(message), handle); + + protected void HandleInBackground(ZeroPacket message, Func handle) where TReq : P2PMessage => + BackgroundTaskScheduler.TryScheduleBackgroundTask(DeserializeAndReport(message), handle); + + private TReq DeserializeAndReport(ZeroPacket message) where TReq : P2PMessage + { + TReq messageObject = Deserialize(message.Content); + ReportIn(messageObject, message.Content.ReadableBytes); + return messageObject; + } + public abstract void Dispose(); public abstract byte ProtocolVersion { get; } @@ -163,10 +213,5 @@ protected void ReportIn(string messageInfo, int size) public abstract event EventHandler SubprotocolRequested; } - public class IncompleteDeserializationException : Exception - { - public IncompleteDeserializationException(string msg) : base(msg) - { - } - } + public class IncompleteDeserializationException(string msg) : Exception(msg); } diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs index c5c88d446f0b..a9f15b75d8dc 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs @@ -20,8 +20,6 @@ using Nethermind.Network.P2P.Subprotocols.Eth.V62; using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; using Nethermind.Network.P2P.Subprotocols.Eth.V63.Messages; -using Nethermind.Network.P2P.Utils; -using Nethermind.Serialization.Rlp; using Nethermind.Stats; using Nethermind.Stats.Model; using Nethermind.Synchronization; @@ -41,7 +39,6 @@ public abstract class SyncPeerProtocolHandlerBase : ZeroProtocolHandlerBase, ISy public virtual bool IncludeInTxPool => true; protected ISyncServer SyncServer { get; } - protected BackgroundTaskSchedulerWrapper BackgroundTaskScheduler { get; } public long HeadNumber { get; set; } public Hash256 HeadHash { get; set; } @@ -52,7 +49,6 @@ public abstract class SyncPeerProtocolHandlerBase : ZeroProtocolHandlerBase, ISy protected Hash256 _remoteHeadBlockHash; protected readonly ITimestamper _timestamper; - protected readonly TxDecoder _txDecoder; protected readonly MessageQueue> _headersRequests; protected readonly MessageQueue _bodiesRequests; @@ -65,12 +61,10 @@ protected SyncPeerProtocolHandlerBase(ISession session, INodeStatsManager statsManager, ISyncServer syncServer, IBackgroundTaskScheduler backgroundTaskScheduler, - ILogManager logManager) : base(session, statsManager, serializer, logManager) + ILogManager logManager) : base(session, statsManager, serializer, backgroundTaskScheduler, logManager) { SyncServer = syncServer ?? throw new ArgumentNullException(nameof(syncServer)); - BackgroundTaskScheduler = new BackgroundTaskSchedulerWrapper(this, backgroundTaskScheduler ?? throw new ArgumentNullException(nameof(BackgroundTaskScheduler))); _timestamper = Timestamper.Default; - _txDecoder = TxDecoder.Instance; _headersRequests = new MessageQueue>(Send); _bodiesRequests = new MessageQueue(Send); } @@ -319,10 +313,7 @@ startingHash is null protected async Task Handle(GetBlockBodiesMessage request, CancellationToken cancellationToken) { using GetBlockBodiesMessage message = request; - if (Logger.IsTrace) - { - Logger.Trace($"Received bodies request of length {message.BlockHashes.Count} from {Session.Node:c}:"); - } + if (Logger.IsTrace) Logger.Trace($"Received bodies request of length {message.BlockHashes.Count} from {Session.Node:c}:"); long startTime = Stopwatch.GetTimestamp(); @@ -366,11 +357,6 @@ protected void HandleBodies(BlockBodiesMessage blockBodiesMessage, long size) protected async Task Handle(GetReceiptsMessage msg, CancellationToken cancellationToken) { using var message = msg; - if (message.Hashes.Count > 512) - { - throw new EthSyncException("Incoming receipts request for more than 512 blocks"); - } - long startTime = Stopwatch.GetTimestamp(); ReceiptsMessage resp = await FulfillReceiptsRequest(message, cancellationToken); if (Logger.IsTrace) Logger.Trace($"OUT {Counter:D5} Receipts to {Node:c} in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"); diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroNettyP2PHandler.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroNettyP2PHandler.cs index e7e8f7a59149..df1e76503813 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroNettyP2PHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroNettyP2PHandler.cs @@ -15,19 +15,13 @@ namespace Nethermind.Network.P2P.ProtocolHandlers; -public class ZeroNettyP2PHandler : SimpleChannelInboundHandler +public class ZeroNettyP2PHandler(ISession session, ILogManager logManager) : SimpleChannelInboundHandler { - private readonly ISession _session; - private readonly ILogger _logger; + private readonly ISession _session = session ?? throw new ArgumentNullException(nameof(session)); + private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); public bool SnappyEnabled { get; private set; } - public ZeroNettyP2PHandler(ISession session, ILogManager logManager) - { - _session = session ?? throw new ArgumentNullException(nameof(session)); - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - } - public void Init(IPacketSender packetSender, IChannelHandlerContext context) { _session.Init(5, context, packetSender); @@ -112,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) @@ -128,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 { @@ -137,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/ProtocolHandlers/ZeroProtocolHandlerBase.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs index 306044b52a40..59fcac9a4f6f 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using DotNetty.Common.Utilities; +using Nethermind.Consensus.Scheduler; using Nethermind.Core.Extensions; using Nethermind.Logging; using Nethermind.Network.Rlpx; @@ -12,8 +13,13 @@ namespace Nethermind.Network.P2P.ProtocolHandlers { - public abstract class ZeroProtocolHandlerBase(ISession session, INodeStatsManager nodeStats, IMessageSerializationService serializer, ILogManager logManager) - : ProtocolHandlerBase(session, nodeStats, serializer, logManager), IZeroProtocolHandler + public abstract class ZeroProtocolHandlerBase( + ISession session, + INodeStatsManager nodeStats, + IMessageSerializationService serializer, + IBackgroundTaskScheduler backgroundTaskScheduler, + ILogManager logManager) + : ProtocolHandlerBase(session, nodeStats, serializer, backgroundTaskScheduler, logManager), IZeroProtocolHandler { protected readonly INodeStats _nodeStats = nodeStats.GetOrAdd(session.Node); @@ -80,15 +86,21 @@ CancellationToken token } else { - _ = task.ContinueWith(static t => + // TrySetCanceled first: if it succeeds we own the TCS and need to + // dispose any late-arriving response. If it fails, the response was + // already set by Handle() and the caller owns the data — registering + // a disposal continuation would dispose data the caller still holds. + if (request.CompletionSource.TrySetCanceled(cancellationToken)) { - if (t.IsCompletedSuccessfully) + _ = task.ContinueWith(static t => { - t.Result.TryDispose(); - } - }); + if (t.IsCompletedSuccessfully) + { + t.Result.TryDispose(); + } + }); + } - request.CompletionSource.TrySetCanceled(cancellationToken); StatsManager.ReportTransferSpeedEvent(Session.Node, speedType, 0L); if (Logger.IsDebug) Logger.Debug($"{Session} Request timeout in {describeRequestFunc(request.Message)}"); diff --git a/src/Nethermind/Nethermind.Network/P2P/Session.cs b/src/Nethermind/Nethermind.Network/P2P/Session.cs index b58bbdf6be5a..7faaee20745d 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Session.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Session.cs @@ -245,52 +245,6 @@ public int DeliverMessage(T message) where T : P2PMessage } } - public void ReceiveMessage(Packet packet) - { - Interlocked.Add(ref Metrics.P2PBytesReceived, packet.Data.Length); - - lock (_sessionStateLock) - { - if (State < SessionState.Initialized) - { - throw new InvalidOperationException($"{nameof(ReceiveMessage)} called on {this}"); - } - - if (IsClosing) - { - return; - } - } - - int dynamicMessageCode = packet.PacketType; - (string protocol, int messageId) = _resolver.ResolveProtocol(packet.PacketType); - packet.Protocol = protocol; - - MsgReceived?.Invoke(this, new PeerEventArgs(_node, packet.Protocol, packet.PacketType, packet.Data.Length)); - - RecordIncomingMessageMetric(protocol, messageId, packet.Data.Length); - - if (_logger.IsTrace) - _logger.Trace($"{this} received a message of length {packet.Data.Length} " + - $"({dynamicMessageCode} => {protocol}.{messageId})"); - - if (protocol is null) - { - if (_logger.IsTrace) - _logger.Warn($"Received a message from node: {RemoteNodeId}, ({dynamicMessageCode} => {messageId}), " + - $"known protocols ({_protocols.Count}): " + - $"{string.Join(", ", _protocols.Select(static x => $"{x.Value.Name} {x.Value.MessageIdSpaceSize}"))}"); - return; - } - - packet.PacketType = messageId; - - if (State < SessionState.DisconnectingProtocols) - { - _protocols[protocol].HandleMessage(packet); - } - } - public bool TryGetProtocolHandler(string protocolCode, out IProtocolHandler handler) { return _protocols.TryGetValue(protocolCode, out handler); @@ -599,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/HashesMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/HashesMessage.cs index f9cec26aefde..3c762d8e4415 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/HashesMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/HashesMessage.cs @@ -8,14 +8,9 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth { - public abstract class HashesMessage : P2PMessage + public abstract class HashesMessage(IOwnedReadOnlyList hashes) : P2PMessage { - protected HashesMessage(IOwnedReadOnlyList hashes) - { - Hashes = hashes ?? throw new ArgumentNullException(nameof(hashes)); - } - - public IOwnedReadOnlyList Hashes { get; } + public IOwnedReadOnlyList Hashes { get; } = hashes ?? throw new ArgumentNullException(nameof(hashes)); public override string ToString() { diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/HashesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/HashesMessageSerializer.cs index 3e19519d8844..d9bcdc3ad621 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/HashesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/HashesMessageSerializer.cs @@ -16,21 +16,21 @@ protected Hash256[] DeserializeHashes(IByteBuffer byteBuffer) return DeserializeHashes(nettyRlpStream); } - protected static Hash256[] DeserializeHashes(RlpStream rlpStream) + protected static Hash256[] DeserializeHashes(RlpStream rlpStream, RlpLimit? limit = null) { - Hash256[] hashes = rlpStream.DecodeArray(static itemContext => itemContext.DecodeKeccak()); + Hash256[] hashes = rlpStream.DecodeArray(static itemContext => itemContext.DecodeKeccak(), limit: limit); return hashes; } - protected ArrayPoolList DeserializeHashesArrayPool(IByteBuffer byteBuffer) + protected ArrayPoolList DeserializeHashesArrayPool(IByteBuffer byteBuffer, RlpLimit? limit = null) { NettyRlpStream nettyRlpStream = new(byteBuffer); - return DeserializeHashesArrayPool(nettyRlpStream); + return DeserializeHashesArrayPool(nettyRlpStream, limit); } - protected static ArrayPoolList DeserializeHashesArrayPool(RlpStream rlpStream) + protected static ArrayPoolList DeserializeHashesArrayPool(RlpStream rlpStream, RlpLimit? limit = null) { - return rlpStream.DecodeArrayPoolList(static itemContext => itemContext.DecodeKeccak()); + return rlpStream.DecodeArrayPoolList(static itemContext => itemContext.DecodeKeccak(), limit: limit); } public void Serialize(IByteBuffer byteBuffer, T message) diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/IPooledTxsRequestor.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/IPooledTxsRequestor.cs deleted file mode 100644 index 26f443961a5e..000000000000 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/IPooledTxsRequestor.cs +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Core.Collections; -using Nethermind.Core.Crypto; -using Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages; - -namespace Nethermind.Network.P2P.Subprotocols.Eth -{ - public interface IPooledTxsRequestor - { - void RequestTransactions(Action send, IOwnedReadOnlyList hashes); - void RequestTransactionsEth66(Action send, IOwnedReadOnlyList hashes); - void RequestTransactionsEth68(Action send, IOwnedReadOnlyList hashes, IOwnedReadOnlyList sizes, IOwnedReadOnlyList types); - } -} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/PooledTxsRequestor.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/PooledTxsRequestor.cs deleted file mode 100644 index c50ddaadee10..000000000000 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/PooledTxsRequestor.cs +++ /dev/null @@ -1,145 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Linq; -using Nethermind.Core; -using Nethermind.Core.Caching; -using Nethermind.Core.Collections; -using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; -using Nethermind.Core.Specs; -using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; -using Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages; -using Nethermind.TxPool; - -namespace Nethermind.Network.P2P.Subprotocols.Eth -{ - public class PooledTxsRequestor(ITxPool txPool, ITxPoolConfig txPoolConfig, ISpecProvider specProvider) : IPooledTxsRequestor - { - private const int MaxNumberOfTxsInOneMsg = 256; - private readonly bool _blobSupportEnabled = txPoolConfig.BlobsSupport.IsEnabled(); - private readonly long _configuredMaxTxSize = txPoolConfig.MaxTxSize ?? long.MaxValue; - - private readonly long _configuredMaxBlobTxSize = txPoolConfig.MaxBlobTxSize is null - ? long.MaxValue - : txPoolConfig.MaxBlobTxSize.Value + (long)specProvider.GetFinalMaxBlobGasPerBlock(); - - private readonly ClockKeyCache _pendingHashes = new(MemoryAllowance.TxHashCacheSize); - - public void RequestTransactions(Action send, IOwnedReadOnlyList hashes) - { - ArrayPoolList discoveredTxHashes = AddMarkUnknownHashes(hashes.AsSpan()); - RequestPooledTransactions(send, discoveredTxHashes); - } - - public void RequestTransactionsEth66(Action send, IOwnedReadOnlyList hashes) - { - ArrayPoolList discoveredTxHashes = AddMarkUnknownHashes(hashes.AsSpan()); - - if (discoveredTxHashes.Count <= MaxNumberOfTxsInOneMsg) - { - RequestPooledTransactionsEth66(send, discoveredTxHashes); - } - else - { - using ArrayPoolList _ = discoveredTxHashes; - - for (int start = 0; start < discoveredTxHashes.Count; start += MaxNumberOfTxsInOneMsg) - { - var end = Math.Min(start + MaxNumberOfTxsInOneMsg, discoveredTxHashes.Count); - - ArrayPoolList hashesToRequest = new(end - start); - hashesToRequest.AddRange(discoveredTxHashes.AsSpan()[start..end]); - RequestPooledTransactionsEth66(send, hashesToRequest); - } - } - } - - public void RequestTransactionsEth68(Action send, IOwnedReadOnlyList hashes, IOwnedReadOnlyList sizes, IOwnedReadOnlyList types) - { - using ArrayPoolList<(Hash256 Hash, byte Type, int Size)> discoveredTxHashesAndSizes = AddMarkUnknownHashesEth68(hashes.AsSpan(), sizes.AsSpan(), types.AsSpan()); - if (discoveredTxHashesAndSizes.Count == 0) return; - - int packetSizeLeft = TransactionsMessage.MaxPacketSize; - ArrayPoolList hashesToRequest = new(discoveredTxHashesAndSizes.Count); - - var discoveredCount = discoveredTxHashesAndSizes.Count; - var toRequestCount = 0; - foreach ((Hash256 hash, byte type, int size) in discoveredTxHashesAndSizes.AsSpan()) - { - int txSize = size; - TxType txType = (TxType)type; - - long maxSize = txType.SupportsBlobs() ? _configuredMaxBlobTxSize : _configuredMaxTxSize; - if (txSize > maxSize) - continue; - - if (txSize > packetSizeLeft && toRequestCount > 0) - { - RequestPooledTransactionsEth66(send, hashesToRequest); - hashesToRequest = new ArrayPoolList(discoveredCount); - packetSizeLeft = TransactionsMessage.MaxPacketSize; - toRequestCount = 0; - } - - if (_blobSupportEnabled || txType != TxType.Blob) - { - hashesToRequest.Add(hash); - packetSizeLeft -= txSize; - toRequestCount++; - } - } - - RequestPooledTransactionsEth66(send, hashesToRequest); - } - - private ArrayPoolList AddMarkUnknownHashes(ReadOnlySpan hashes) - { - ArrayPoolList discoveredTxHashes = new ArrayPoolList(hashes.Length); - for (int i = 0; i < hashes.Length; i++) - { - Hash256 hash = hashes[i]; - if (!txPool.IsKnown(hash) && _pendingHashes.Set(hash)) - { - discoveredTxHashes.Add(hash); - } - } - - return discoveredTxHashes; - } - - private ArrayPoolList<(Hash256, byte, int)> AddMarkUnknownHashesEth68(ReadOnlySpan hashes, ReadOnlySpan sizes, ReadOnlySpan types) - { - ArrayPoolList<(Hash256, byte, int)> discoveredTxHashesAndSizes = new(hashes.Length); - for (int i = 0; i < hashes.Length; i++) - { - Hash256 hash = hashes[i]; - if (!txPool.IsKnown(hash) && !txPool.ContainsTx(hash, (TxType)types[i]) && _pendingHashes.Set(hash)) - { - discoveredTxHashesAndSizes.Add((hash, types[i], sizes[i])); - } - } - - return discoveredTxHashesAndSizes; - } - - private static void RequestPooledTransactions(Action send, IOwnedReadOnlyList hashesToRequest) - { - send(new(hashesToRequest)); - } - - private static void RequestPooledTransactionsEth66(Action send, IOwnedReadOnlyList hashesToRequest) - { - if (hashesToRequest.Count > 0) - { - GetPooledTransactionsMessage msg65 = new(hashesToRequest); - send(new() { EthMessage = msg65 }); - } - else - { - hashesToRequest.Dispose(); - } - } - } -} 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 6fc37559591a..5850486aa32e 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Eth62ProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Eth62ProtocolHandler.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; @@ -55,10 +55,7 @@ public Eth62ProtocolHandler(ISession session, EnsureGossipPolicy(); } - public void DisableTxFiltering() - { - _floodController.IsEnabled = false; - } + public void DisableTxFiltering() => _floodController.IsEnabled = false; public override byte ProtocolVersion => EthVersions.Eth62; public override string ProtocolCode => Protocol.Eth; @@ -168,9 +165,7 @@ bool CanAcceptBlockGossip() break; case Eth62MessageCode.GetBlockHeaders: - GetBlockHeadersMessage getBlockHeadersMessage = Deserialize(message.Content); - ReportIn(getBlockHeadersMessage, size); - BackgroundTaskScheduler.ScheduleSyncServe(getBlockHeadersMessage, Handle); + HandleInBackground(message, Handle); break; case Eth62MessageCode.BlockHeaders: BlockHeadersMessage headersMsg = Deserialize(message.Content); @@ -178,9 +173,7 @@ bool CanAcceptBlockGossip() Handle(headersMsg, size); break; case Eth62MessageCode.GetBlockBodies: - GetBlockBodiesMessage getBodiesMsg = Deserialize(message.Content); - ReportIn(getBodiesMsg, size); - BackgroundTaskScheduler.ScheduleSyncServe(getBodiesMsg, Handle); + HandleInBackground(message, Handle); break; case Eth62MessageCode.BlockBodies: BlockBodiesMessage bodiesMsg = Deserialize(message.Content); @@ -243,11 +236,10 @@ private void Handle(StatusMessage status) protected void Handle(TransactionsMessage msg) { IOwnedReadOnlyList iList = msg.Transactions; - - BackgroundTaskScheduler.ScheduleBackgroundTask((iList, 0), _handleSlow); + BackgroundTaskScheduler.TryScheduleBackgroundTask((iList, 0), _handleSlow); } - private ValueTask HandleSlow((IOwnedReadOnlyList txs, int startIndex) request, CancellationToken cancellationToken) + protected virtual ValueTask HandleSlow((IOwnedReadOnlyList txs, int startIndex) request, CancellationToken cancellationToken) { IOwnedReadOnlyList transactions = request.txs; ReadOnlySpan transactionsSpan = transactions.AsSpan(); @@ -255,6 +247,7 @@ private ValueTask HandleSlow((IOwnedReadOnlyList txs, int startInde { int startIdx = request.startIndex; bool isTrace = Logger.IsTrace; + for (int i = startIdx; i < transactionsSpan.Length; i++) { if (cancellationToken.IsCancellationRequested) @@ -269,7 +262,7 @@ private ValueTask HandleSlow((IOwnedReadOnlyList txs, int startInde } // 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/V62/Messages/BlockBodiesMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessage.cs index 730e231b2fae..515765ec3c25 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessage.cs @@ -9,8 +9,8 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages { public class BlockBodiesMessage : P2PMessage { - public override int PacketType { get; } = Eth62MessageCode.BlockBodies; - public override string Protocol { get; } = "eth"; + public override int PacketType => Eth62MessageCode.BlockBodies; + public override string Protocol => "eth"; public OwnedBlockBodies? Bodies { get; set; } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs index 8ecf43889765..b984e244ddc1 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs @@ -1,16 +1,17 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Linq; using DotNetty.Buffers; using Nethermind.Core; using Nethermind.Core.Buffers; using Nethermind.Serialization.Rlp; +using Nethermind.Stats.SyncLimits; namespace Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages { public class BlockBodiesMessageSerializer : IZeroInnerMessageSerializer { + private static readonly RlpLimit RlpLimit = RlpLimit.For(NethermindSyncLimits.MaxBodyFetch, nameof(BlockBodiesMessage.Bodies)); private readonly BlockBodyDecoder _blockBodyDecoder = BlockBodyDecoder.Instance; public void Serialize(IByteBuffer byteBuffer, BlockBodiesMessage message) @@ -54,7 +55,7 @@ public BlockBodiesMessage Deserialize(IByteBuffer byteBuffer) Rlp.ValueDecoderContext ctx = new(memoryOwner.Memory, true); int startingPosition = ctx.Position; - BlockBody[]? bodies = ctx.DecodeArray(_blockBodyDecoder, false); + BlockBody[]? bodies = ctx.DecodeArray(_blockBodyDecoder, false, limit: RlpLimit); byteBuffer.SetReaderIndex(byteBuffer.ReaderIndex + (ctx.Position - startingPosition)); return new() { Bodies = new(bodies, memoryOwner) }; diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockHeadersMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockHeadersMessage.cs index 6ddde2450d3b..d29c0744f173 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockHeadersMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockHeadersMessage.cs @@ -9,8 +9,8 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages { public class BlockHeadersMessage : P2PMessage { - public override int PacketType { get; } = Eth62MessageCode.BlockHeaders; - public override string Protocol { get; } = "eth"; + public override int PacketType => Eth62MessageCode.BlockHeaders; + public override string Protocol => "eth"; public IOwnedReadOnlyList? BlockHeaders { get; set; } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockHeadersMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockHeadersMessageSerializer.cs index 30e182a4c9c2..a230de2f1bc6 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockHeadersMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockHeadersMessageSerializer.cs @@ -4,11 +4,13 @@ using DotNetty.Buffers; using Nethermind.Core; using Nethermind.Serialization.Rlp; +using Nethermind.Stats.SyncLimits; namespace Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages { public class BlockHeadersMessageSerializer : IZeroInnerMessageSerializer { + private static readonly RlpLimit RlpLimit = RlpLimit.For(NethermindSyncLimits.MaxHeaderFetch, nameof(BlockHeadersMessage.BlockHeaders)); private readonly HeaderDecoder _headerDecoder = new(); public void Serialize(IByteBuffer byteBuffer, BlockHeadersMessage message) @@ -44,7 +46,7 @@ public int GetLength(BlockHeadersMessage message, out int contentLength) public static BlockHeadersMessage Deserialize(RlpStream rlpStream) { BlockHeadersMessage message = new(); - message.BlockHeaders = Rlp.DecodeArrayPool(rlpStream); + message.BlockHeaders = Rlp.DecodeArrayPool(rlpStream, limit: RlpLimit); return message; } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/GetBlockBodiesMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/GetBlockBodiesMessage.cs index e0e925394f62..40a0147b23a4 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/GetBlockBodiesMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/GetBlockBodiesMessage.cs @@ -7,16 +7,11 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages { - public class GetBlockBodiesMessage : P2PMessage + public class GetBlockBodiesMessage(IReadOnlyList blockHashes) : P2PMessage { - public IReadOnlyList BlockHashes { get; } - public override int PacketType { get; } = Eth62MessageCode.GetBlockBodies; - public override string Protocol { get; } = "eth"; - - public GetBlockBodiesMessage(IReadOnlyList blockHashes) - { - BlockHashes = blockHashes; - } + public IReadOnlyList BlockHashes { get; } = blockHashes; + public override int PacketType => Eth62MessageCode.GetBlockBodies; + public override string Protocol => "eth"; public GetBlockBodiesMessage(params Hash256[] blockHashes) : this((IReadOnlyList)blockHashes) { diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/GetBlockBodiesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/GetBlockBodiesMessageSerializer.cs index 9aeed10d02ba..366fb5baf0b4 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/GetBlockBodiesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/GetBlockBodiesMessageSerializer.cs @@ -4,11 +4,14 @@ using DotNetty.Buffers; using Nethermind.Core.Crypto; using Nethermind.Serialization.Rlp; +using Nethermind.Stats.SyncLimits; namespace Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages { public class GetBlockBodiesMessageSerializer : IZeroInnerMessageSerializer { + private static readonly RlpLimit RlpLimit = RlpLimit.For(NethermindSyncLimits.MaxBodyFetch, nameof(GetBlockBodiesMessage.BlockHashes)); + public void Serialize(IByteBuffer byteBuffer, GetBlockBodiesMessage message) { int length = GetLength(message, out int contentLength); @@ -41,7 +44,7 @@ public int GetLength(GetBlockBodiesMessage message, out int contentLength) public static GetBlockBodiesMessage Deserialize(RlpStream rlpStream) { - Hash256[] hashes = rlpStream.DecodeArray(ctx => rlpStream.DecodeKeccak(), false); + Hash256[] hashes = rlpStream.DecodeArray(ctx => rlpStream.DecodeKeccak(), false, limit: RlpLimit); return new GetBlockBodiesMessage(hashes); } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/GetBlockHeadersMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/GetBlockHeadersMessage.cs index bc059be1ca8f..8e178a1353e9 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/GetBlockHeadersMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/GetBlockHeadersMessage.cs @@ -10,8 +10,8 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages [DebuggerDisplay("{StartBlockHash} {MaxHeaders} {Skip} {Reverse}")] public class GetBlockHeadersMessage : P2PMessage { - public override int PacketType { get; } = Eth62MessageCode.GetBlockHeaders; - public override string Protocol { get; } = "eth"; + public override int PacketType => Eth62MessageCode.GetBlockHeaders; + public override string Protocol => "eth"; public long StartBlockNumber { get; set; } public Hash256? StartBlockHash { get; set; } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/GetBlockHeadersMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/GetBlockHeadersMessageSerializer.cs index ec44c9cbf130..76cdfec614c6 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/GetBlockHeadersMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/GetBlockHeadersMessageSerializer.cs @@ -15,7 +15,7 @@ public static GetBlockHeadersMessage Deserialize(RlpStream rlpStream) GetBlockHeadersMessage message = new(); rlpStream.ReadSequenceLength(); byte[] startingBytes = rlpStream.DecodeByteArray(); - if (startingBytes.Length == 32) + if (startingBytes.Length == Hash256.Size) { message.StartBlockHash = new Hash256(startingBytes); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/NewBlockHashesMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/NewBlockHashesMessage.cs index 524f4cef6754..ce242a53bd99 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/NewBlockHashesMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/NewBlockHashesMessage.cs @@ -6,17 +6,12 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages { - public class NewBlockHashesMessage : P2PMessage + public class NewBlockHashesMessage(params (Hash256, long)[] blockHashes) : P2PMessage { - public override int PacketType { get; } = Eth62MessageCode.NewBlockHashes; - public override string Protocol { get; } = "eth"; + public override int PacketType => Eth62MessageCode.NewBlockHashes; + public override string Protocol => "eth"; - public (Hash256, long)[] BlockHashes { get; } - - public NewBlockHashesMessage(params (Hash256, long)[] blockHashes) - { - BlockHashes = blockHashes; - } + public (Hash256, long)[] BlockHashes { get; } = blockHashes; public override string ToString() { diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/NewBlockHashesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/NewBlockHashesMessageSerializer.cs index 102ca84c9cc8..13b51de6e9f8 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/NewBlockHashesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/NewBlockHashesMessageSerializer.cs @@ -4,11 +4,14 @@ using DotNetty.Buffers; using Nethermind.Core.Crypto; using Nethermind.Serialization.Rlp; +using Nethermind.Stats.SyncLimits; namespace Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages { public class NewBlockHashesMessageSerializer : IZeroInnerMessageSerializer { + private static readonly RlpLimit RlpLimit = RlpLimit.For(NethermindSyncLimits.MaxHashesFetch, nameof(NewBlockHashesMessage.BlockHashes)); + public void Serialize(IByteBuffer byteBuffer, NewBlockHashesMessage message) { int length = GetLength(message, out int contentLength); @@ -51,7 +54,7 @@ private static NewBlockHashesMessage Deserialize(RlpStream rlpStream) { ctx.ReadSequenceLength(); return (ctx.DecodeKeccak(), (long)ctx.DecodeUInt256()); - }, false); + }, false, limit: RlpLimit); return new NewBlockHashesMessage(blockHashes); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/NewBlockMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/NewBlockMessage.cs index 2636fc3cd693..e02f72d5aabb 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/NewBlockMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/NewBlockMessage.cs @@ -9,8 +9,8 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages { public class NewBlockMessage : P2PMessage { - public override int PacketType { get; } = Eth62MessageCode.NewBlock; - public override string Protocol { get; } = "eth"; + public override int PacketType => Eth62MessageCode.NewBlock; + public override string Protocol => "eth"; public Block Block { get; set; } public UInt256 TotalDifficulty { get; set; } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/StatusMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/StatusMessage.cs index 58dc1d7b6991..d2b6f55a14a8 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/StatusMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/StatusMessage.cs @@ -16,12 +16,9 @@ public class StatusMessage : P2PMessage public Hash256? GenesisHash { get; set; } public ForkId? ForkId { get; set; } - public override int PacketType { get; } = Eth62MessageCode.Status; - public override string Protocol { get; } = "eth"; + public override int PacketType => Eth62MessageCode.Status; + public override string Protocol => "eth"; - public override string ToString() - { - return $"{Protocol}.{ProtocolVersion} network: {NetworkId} | diff: {TotalDifficulty} | best: {BestHash?.ToShortString()} | genesis: {GenesisHash?.ToShortString()} | fork: {ForkId}"; - } + public override string ToString() => $"{Protocol}.{ProtocolVersion} network: {NetworkId} | diff: {TotalDifficulty} | best: {BestHash?.ToShortString()} | genesis: {GenesisHash?.ToShortString()} | fork: {ForkId}"; } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/TransactionsMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/TransactionsMessage.cs index 6fb8c871841d..51ca48678388 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/TransactionsMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/TransactionsMessage.cs @@ -7,20 +7,15 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages { - public class TransactionsMessage : P2PMessage + public class TransactionsMessage(IOwnedReadOnlyList transactions) : P2PMessage { // This is the target size for the packs of transactions. A pack can get larger than this if a single // transaction exceeds this size. This solution is similar to Geth one. public const int MaxPacketSize = 102400; - public override int PacketType { get; } = Eth62MessageCode.Transactions; - public override string Protocol { get; } = "eth"; - public IOwnedReadOnlyList Transactions { get; } - - public TransactionsMessage(IOwnedReadOnlyList transactions) - { - Transactions = transactions; - } + public override int PacketType => Eth62MessageCode.Transactions; + public override string Protocol => "eth"; + public IOwnedReadOnlyList Transactions { get; } = transactions; public override string ToString() => $"{nameof(TransactionsMessage)}({Transactions?.Count})"; diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/TransactionsMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/TransactionsMessageSerializer.cs index 33c633924d80..b35f064a929f 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/TransactionsMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/TransactionsMessageSerializer.cs @@ -5,11 +5,13 @@ using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Serialization.Rlp; +using Nethermind.Stats.SyncLimits; namespace Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages { public class TransactionsMessageSerializer : IZeroInnerMessageSerializer { + private static readonly RlpLimit RlpLimit = RlpLimit.For(NethermindSyncLimits.MaxHashesFetch, nameof(TransactionsMessage.Transactions)); private readonly TxDecoder _decoder = TxDecoder.Instance; public void Serialize(IByteBuffer byteBuffer, TransactionsMessage message) @@ -45,7 +47,7 @@ public int GetLength(TransactionsMessage message, out int contentLength) public static IOwnedReadOnlyList DeserializeTxs(RlpStream rlpStream) { - return Rlp.DecodeArrayPool(rlpStream, RlpBehaviors.InMempoolForm); + return Rlp.DecodeArrayPool(rlpStream, RlpBehaviors.InMempoolForm, limit: RlpLimit); } } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/TxFloodController.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/TxFloodController.cs index 83a132a492a1..be3dff9aa0e3 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/TxFloodController.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/TxFloodController.cs @@ -1,47 +1,47 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Nethermind.Core; using Nethermind.Logging; using Nethermind.Stats.Model; +using Nethermind.TxPool; +using System; namespace Nethermind.Network.P2P.Subprotocols.Eth.V62 { - internal class TxFloodController + internal class TxFloodController(Eth62ProtocolHandler protocolHandler, ITimestamper timestamper, ILogger logger) { private DateTime _checkpoint = DateTime.UtcNow; - private readonly Eth62ProtocolHandler _protocolHandler; - private readonly ITimestamper _timestamper; - private readonly ILogger _logger; + private readonly Eth62ProtocolHandler _protocolHandler = protocolHandler ?? throw new ArgumentNullException(nameof(protocolHandler)); + private readonly ITimestamper _timestamper = timestamper ?? throw new ArgumentNullException(nameof(timestamper)); private readonly TimeSpan _checkInterval = TimeSpan.FromSeconds(60); private long _notAcceptedSinceLastCheck; private readonly Random _random = new(); internal bool IsDowngraded { get; private set; } - public TxFloodController(Eth62ProtocolHandler protocolHandler, ITimestamper timestamper, ILogger logger) - { - _protocolHandler = protocolHandler ?? throw new ArgumentNullException(nameof(protocolHandler)); - _timestamper = timestamper ?? throw new ArgumentNullException(nameof(timestamper)); - _logger = logger; - } - - public void Report(bool accepted) + public void Report(AcceptTxResult accepted) { TryReset(); if (!accepted) { + if (accepted == AcceptTxResult.Invalid) + { + if (logger.IsDebug) logger.Debug($"Disconnecting {_protocolHandler} due to invalid tx received"); + _protocolHandler.Disconnect(DisconnectReason.Other, $"invalid tx"); + return; + } + _notAcceptedSinceLastCheck++; if (!IsDowngraded && _notAcceptedSinceLastCheck / _checkInterval.TotalSeconds > 10) { - if (_logger.IsDebug) _logger.Debug($"Downgrading {_protocolHandler} due to tx flooding"); + if (logger.IsDebug) logger.Debug($"Downgrading {_protocolHandler} due to tx flooding"); IsDowngraded = true; } else if (_notAcceptedSinceLastCheck / _checkInterval.TotalSeconds > 100) { - if (_logger.IsDebug) _logger.Debug($"Disconnecting {_protocolHandler} due to tx flooding"); + if (logger.IsDebug) logger.Debug($"Disconnecting {_protocolHandler} due to tx flooding"); _protocolHandler.Disconnect( DisconnectReason.TxFlooding, $"tx flooding {_notAcceptedSinceLastCheck}/{_checkInterval.TotalSeconds > 100}"); 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 ac0b4c832787..7ab629571267 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Eth63ProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Eth63ProtocolHandler.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; @@ -56,9 +55,7 @@ public override void HandleMessage(ZeroPacket message) switch (message.PacketType) { case Eth63MessageCode.GetReceipts: - GetReceiptsMessage getReceiptsMessage = Deserialize(message.Content); - ReportIn(getReceiptsMessage, size); - BackgroundTaskScheduler.ScheduleSyncServe(getReceiptsMessage, Handle); + HandleInBackground(message, Handle); break; case Eth63MessageCode.Receipts: ReceiptsMessage receiptsMessage = Deserialize(message.Content); @@ -66,9 +63,7 @@ public override void HandleMessage(ZeroPacket message) Handle(receiptsMessage, size); break; case Eth63MessageCode.GetNodeData: - GetNodeDataMessage getNodeDataMessage = Deserialize(message.Content); - ReportIn(getNodeDataMessage, size); - BackgroundTaskScheduler.ScheduleSyncServe(getNodeDataMessage, Handle); + HandleInBackground(message, Handle); break; case Eth63MessageCode.NodeData: NodeDataMessage nodeDataMessage = Deserialize(message.Content); @@ -98,13 +93,7 @@ private async Task Handle(GetNodeDataMessage msg, CancellationT protected Task FulfillNodeDataRequest(GetNodeDataMessage msg, CancellationToken cancellationToken) { - if (msg.Hashes.Count > 4096) - { - throw new EthSyncException("Incoming node data request for more than 4096 nodes"); - } - IOwnedReadOnlyList nodeData = SyncServer.GetNodeData(msg.Hashes, cancellationToken); - return Task.FromResult(new NodeDataMessage(nodeData)); } @@ -122,7 +111,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}"); } @@ -159,7 +148,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/V63/Messages/GetNodeDataMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/GetNodeDataMessage.cs index 8da22b5d4dfc..f72d7cd5a93f 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/GetNodeDataMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/GetNodeDataMessage.cs @@ -8,7 +8,7 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V63.Messages { public class GetNodeDataMessage(IOwnedReadOnlyList keys) : HashesMessage(keys) { - public override int PacketType { get; } = Eth63MessageCode.GetNodeData; - public override string Protocol { get; } = "eth"; + public override int PacketType => Eth63MessageCode.GetNodeData; + public override string Protocol => "eth"; } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/GetNodeDataMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/GetNodeDataMessageSerializer.cs index 336296fa3f3e..0c9aea68c1e4 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/GetNodeDataMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/GetNodeDataMessageSerializer.cs @@ -4,14 +4,18 @@ using DotNetty.Buffers; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Stats.SyncLimits; namespace Nethermind.Network.P2P.Subprotocols.Eth.V63.Messages { public class GetNodeDataMessageSerializer : HashesMessageSerializer { + private static readonly RlpLimit RlpLimit = RlpLimit.For(NethermindSyncLimits.MaxHashesFetch, nameof(GetNodeDataMessage.Hashes)); + public override GetNodeDataMessage Deserialize(IByteBuffer byteBuffer) { - ArrayPoolList? keys = DeserializeHashesArrayPool(byteBuffer); + ArrayPoolList? keys = DeserializeHashesArrayPool(byteBuffer, RlpLimit); return new GetNodeDataMessage(keys); } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/GetReceiptsMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/GetReceiptsMessage.cs index bc87c59f009c..05e01e49d224 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/GetReceiptsMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/GetReceiptsMessage.cs @@ -8,7 +8,7 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V63.Messages { public class GetReceiptsMessage(IOwnedReadOnlyList blockHashes) : HashesMessage(blockHashes) { - public override int PacketType { get; } = Eth63MessageCode.GetReceipts; - public override string Protocol { get; } = "eth"; + public override int PacketType => Eth63MessageCode.GetReceipts; + public override string Protocol => "eth"; } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/GetReceiptsMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/GetReceiptsMessageSerializer.cs index 06b5743d470a..2b01925fa1d2 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/GetReceiptsMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/GetReceiptsMessageSerializer.cs @@ -5,15 +5,18 @@ using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Serialization.Rlp; +using Nethermind.Stats.SyncLimits; namespace Nethermind.Network.P2P.Subprotocols.Eth.V63.Messages { public class GetReceiptsMessageSerializer : HashesMessageSerializer { + private static readonly RlpLimit RlpLimit = RlpLimit.For(NethermindSyncLimits.MaxHashesFetch, nameof(GetReceiptsMessage.Hashes)); + public static GetReceiptsMessage Deserialize(byte[] bytes) { RlpStream rlpStream = bytes.AsRlpStream(); - ArrayPoolList? hashes = rlpStream.DecodeArrayPoolList(static itemContext => itemContext.DecodeKeccak()); + ArrayPoolList? hashes = rlpStream.DecodeArrayPoolList(static itemContext => itemContext.DecodeKeccak(), limit: RlpLimit); return new GetReceiptsMessage(hashes); } @@ -25,7 +28,7 @@ public override GetReceiptsMessage Deserialize(IByteBuffer byteBuffer) public static GetReceiptsMessage Deserialize(RlpStream rlpStream) { - ArrayPoolList? hashes = HashesMessageSerializer.DeserializeHashesArrayPool(rlpStream); + ArrayPoolList? hashes = DeserializeHashesArrayPool(rlpStream, RlpLimit); return new GetReceiptsMessage(hashes); } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/NodeDataMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/NodeDataMessage.cs index ab9c7b45c7d6..8ad5343b927f 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/NodeDataMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/NodeDataMessage.cs @@ -9,8 +9,8 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V63.Messages public class NodeDataMessage(IOwnedReadOnlyList? data) : P2PMessage { public IOwnedReadOnlyList Data { get; } = data ?? ArrayPoolList.Empty(); - public override int PacketType { get; } = Eth63MessageCode.NodeData; - public override string Protocol { get; } = "eth"; + public override int PacketType => Eth63MessageCode.NodeData; + public override string Protocol => "eth"; public override string ToString() => $"{nameof(NodeDataMessage)}({Data.Count})"; diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/NodeDataMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/NodeDataMessageSerializer.cs index e4519a03d8d0..2663b042e123 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/NodeDataMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/NodeDataMessageSerializer.cs @@ -4,11 +4,14 @@ using DotNetty.Buffers; using Nethermind.Core.Collections; using Nethermind.Serialization.Rlp; +using Nethermind.Stats.SyncLimits; namespace Nethermind.Network.P2P.Subprotocols.Eth.V63.Messages { public class NodeDataMessageSerializer : IZeroInnerMessageSerializer { + private static readonly RlpLimit RlpLimit = RlpLimit.For(NethermindSyncLimits.MaxHashesFetch, nameof(NodeDataMessage.Data)); + public void Serialize(IByteBuffer byteBuffer, NodeDataMessage message) { int length = GetLength(message, out int contentLength); @@ -25,7 +28,7 @@ public void Serialize(IByteBuffer byteBuffer, NodeDataMessage message) public NodeDataMessage Deserialize(IByteBuffer byteBuffer) { RlpStream rlpStream = new NettyRlpStream(byteBuffer); - ArrayPoolList result = rlpStream.DecodeArrayPoolList(static stream => stream.DecodeByteArray()); + ArrayPoolList result = rlpStream.DecodeArrayPoolList(static stream => stream.DecodeByteArray(), limit: RlpLimit); return new NodeDataMessage(result); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/ReceiptsMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/ReceiptsMessage.cs index 289662c83db2..d454590eb370 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/ReceiptsMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/ReceiptsMessage.cs @@ -10,8 +10,8 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V63.Messages public class ReceiptsMessage(IOwnedReadOnlyList txReceipts) : P2PMessage { public IOwnedReadOnlyList TxReceipts { get; } = txReceipts ?? ArrayPoolList.Empty(); - public override int PacketType { get; } = Eth63MessageCode.Receipts; - public override string Protocol { get; } = "eth"; + public override int PacketType => Eth63MessageCode.Receipts; + public override string Protocol => "eth"; private static ReceiptsMessage? _empty; public static ReceiptsMessage Empty => _empty ??= new ReceiptsMessage(null); diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/ReceiptsMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/ReceiptsMessageSerializer.cs index 86db5ff59c5d..8c6a7ece9aa0 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/ReceiptsMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Messages/ReceiptsMessageSerializer.cs @@ -34,7 +34,7 @@ public void Serialize(IByteBuffer byteBuffer, ReceiptsMessage message) NettyRlpStream stream = new(byteBuffer); stream.StartSequence(contentLength); - // Track last‐seen block number & its RLP behavior + // Track the last ‐ seen block number & its RLP behavior long lastBlockNumber = -1; RlpBehaviors behaviors = RlpBehaviors.None; @@ -89,7 +89,7 @@ public ReceiptsMessage Deserialize(IByteBuffer byteBuffer) public ReceiptsMessage Deserialize(RlpStream rlpStream) { - ArrayPoolList data = rlpStream.DecodeArrayPoolList(_decodeArrayFunc, true); + ArrayPoolList data = rlpStream.DecodeArrayPoolList(_decodeArrayFunc); ReceiptsMessage message = new(data); return message; diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V64/Eth64ProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V64/Eth64ProtocolHandler.cs index 513a42fd9487..8d936deab366 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V64/Eth64ProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V64/Eth64ProtocolHandler.cs @@ -17,24 +17,21 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V64 /// /// https://eips.ethereum.org/EIPS/eip-2364 /// - public class Eth64ProtocolHandler : Eth63ProtocolHandler + public class Eth64ProtocolHandler( + ISession session, + IMessageSerializationService serializer, + INodeStatsManager nodeStatsManager, + ISyncServer syncServer, + IBackgroundTaskScheduler backgroundTaskScheduler, + ITxPool txPool, + IGossipPolicy gossipPolicy, + IForkInfo forkInfo, + ILogManager logManager, + ITxGossipPolicy? transactionsGossipPolicy = null) + : Eth63ProtocolHandler(session, serializer, nodeStatsManager, syncServer, backgroundTaskScheduler, txPool, + gossipPolicy, logManager, transactionsGossipPolicy) { - protected readonly IForkInfo _forkInfo; - - public Eth64ProtocolHandler(ISession session, - IMessageSerializationService serializer, - INodeStatsManager nodeStatsManager, - ISyncServer syncServer, - IBackgroundTaskScheduler backgroundTaskScheduler, - ITxPool txPool, - IGossipPolicy gossipPolicy, - IForkInfo forkInfo, - ILogManager logManager, - ITxGossipPolicy? transactionsGossipPolicy = null) - : base(session, serializer, nodeStatsManager, syncServer, backgroundTaskScheduler, txPool, gossipPolicy, logManager, transactionsGossipPolicy) - { - _forkInfo = forkInfo ?? throw new ArgumentNullException(nameof(forkInfo)); - } + protected readonly IForkInfo _forkInfo = forkInfo ?? throw new ArgumentNullException(nameof(forkInfo)); public override string Name => "eth64"; 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 ce2e4977c657..de56ae1be6d7 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Eth65ProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Eth65ProtocolHandler.cs @@ -1,17 +1,15 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; using Nethermind.Consensus; using Nethermind.Consensus.Scheduler; using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Logging; +using Nethermind.Network.Contract.Messages; using Nethermind.Network.Contract.P2P; +using Nethermind.Network.P2P.Messages; using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; using Nethermind.Network.P2P.Subprotocols.Eth.V64; using Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages; @@ -19,36 +17,37 @@ using Nethermind.Stats; using Nethermind.Synchronization; using Nethermind.TxPool; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; namespace Nethermind.Network.P2P.Subprotocols.Eth.V65 { /// /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2464.md /// - public class Eth65ProtocolHandler : Eth64ProtocolHandler + public class Eth65ProtocolHandler( + ISession session, + IMessageSerializationService serializer, + INodeStatsManager nodeStatsManager, + ISyncServer syncServer, + IBackgroundTaskScheduler backgroundTaskScheduler, + ITxPool txPool, + IGossipPolicy gossipPolicy, + IForkInfo forkInfo, + ILogManager logManager, + ITxGossipPolicy? transactionsGossipPolicy = null) + : Eth64ProtocolHandler(session, serializer, nodeStatsManager, syncServer, backgroundTaskScheduler, txPool, gossipPolicy, forkInfo, logManager, transactionsGossipPolicy), + IMessageHandler { - private readonly IPooledTxsRequestor _pooledTxsRequestor; - - public Eth65ProtocolHandler(ISession session, - IMessageSerializationService serializer, - INodeStatsManager nodeStatsManager, - ISyncServer syncServer, - IBackgroundTaskScheduler backgroundTaskScheduler, - ITxPool txPool, - IPooledTxsRequestor pooledTxsRequestor, - IGossipPolicy gossipPolicy, - IForkInfo forkInfo, - ILogManager logManager, - ITxGossipPolicy? transactionsGossipPolicy = null) - : base(session, serializer, nodeStatsManager, syncServer, backgroundTaskScheduler, txPool, gossipPolicy, forkInfo, logManager, transactionsGossipPolicy) - { - _pooledTxsRequestor = pooledTxsRequestor; - } - public override string Name => "eth65"; public override byte ProtocolVersion => EthVersions.Eth65; + private const int MaxNumberOfTxsInOneMsg = 256; + public override void HandleMessage(ZeroPacket message) { base.HandleMessage(message); @@ -56,35 +55,33 @@ public override void HandleMessage(ZeroPacket message) int size = message.Content.ReadableBytes; switch (message.PacketType) { - case Eth65MessageCode.PooledTransactions: + case Eth65MessageCode.NewPooledTransactionHashes: if (CanReceiveTransactions) { - PooledTransactionsMessage pooledTxMsg = Deserialize(message.Content); - ReportIn(pooledTxMsg, size); - Handle(pooledTxMsg); + using NewPooledTransactionHashesMessage newPooledTxMsg = Deserialize(message.Content); + ReportIn(newPooledTxMsg, size); + Handle(newPooledTxMsg); } else { - const string ignored = $"{nameof(PooledTransactionsMessage)} ignored, syncing"; + const string ignored = $"{nameof(NewPooledTransactionHashesMessage)} ignored, syncing"; ReportIn(ignored, size); } break; case Eth65MessageCode.GetPooledTransactions: - GetPooledTransactionsMessage getPooledTxMsg = Deserialize(message.Content); - ReportIn(getPooledTxMsg, size); - BackgroundTaskScheduler.ScheduleBackgroundTask(getPooledTxMsg, Handle); + HandleInBackground(message, Handle); break; - case Eth65MessageCode.NewPooledTransactionHashes: + case Eth65MessageCode.PooledTransactions: if (CanReceiveTransactions) { - NewPooledTransactionHashesMessage newPooledTxMsg = Deserialize(message.Content); - ReportIn(newPooledTxMsg, size); - Handle(newPooledTxMsg); + PooledTransactionsMessage pooledTxMsg = Deserialize(message.Content); + ReportIn(pooledTxMsg, size); + Handle(pooledTxMsg); } else { - const string ignored = $"{nameof(NewPooledTransactionHashesMessage)} ignored, syncing"; + const string ignored = $"{nameof(PooledTransactionsMessage)} ignored, syncing"; ReportIn(ignored, size); } @@ -94,17 +91,7 @@ public override void HandleMessage(ZeroPacket message) protected virtual void Handle(NewPooledTransactionHashesMessage msg) { - using var _ = msg; - AddNotifiedTransactions(msg.Hashes); - - long startTime = Stopwatch.GetTimestamp(); - - TxPool.Metrics.PendingTransactionsHashesReceived += msg.Hashes.Count; - _pooledTxsRequestor.RequestTransactions(Send, msg.Hashes); - - if (Logger.IsTrace) - Logger.Trace($"OUT {Counter:D5} {nameof(NewPooledTransactionHashesMessage)} to {Node:c} " + - $"in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"); + RequestPooledTransactions(msg.Hashes); } protected void AddNotifiedTransactions(IReadOnlyList hashes) @@ -154,6 +141,8 @@ internal Task FulfillPooledTransactionsRequest(GetPoo protected override void SendNewTransactionsCore(IEnumerable txs, bool sendFullTx) { + void SendNewPooledTransactionMessage(IOwnedReadOnlyList hashes) => Send(new NewPooledTransactionHashesMessage(hashes)); + if (sendFullTx) { base.SendNewTransactionsCore(txs, true); @@ -187,10 +176,73 @@ protected override void SendNewTransactionsCore(IEnumerable txs, bo } } - private void SendNewPooledTransactionMessage(IOwnedReadOnlyList hashes) + protected void RequestPooledTransactions(IOwnedReadOnlyList hashes) + where TMessage : P2PMessage, INew, TMessage> + { + AddNotifiedTransactions(hashes); + + long startTime = Stopwatch.GetTimestamp(); + TxPool.Metrics.PendingTransactionsHashesReceived += hashes.Count; + + ArrayPoolList newTxHashes = AddMarkUnknownHashes(hashes.AsSpan()); + + if (newTxHashes.Count is 0) + { + newTxHashes.Dispose(); + return; + } + + if (newTxHashes.Count <= MaxNumberOfTxsInOneMsg) + { + Send(TMessage.New(newTxHashes)); + } + else + { + try + { + for (int start = 0; start < newTxHashes.Count; start += MaxNumberOfTxsInOneMsg) + { + int end = Math.Min(start + MaxNumberOfTxsInOneMsg, newTxHashes.Count); + + ArrayPoolList hashesToRequest = new(end - start); + hashesToRequest.AddRange(newTxHashes.AsSpan()[start..end]); + + Send(TMessage.New(hashesToRequest)); + } + } + finally + { + newTxHashes.Dispose(); + } + } + + if (Logger.IsTrace) Logger.Trace($"OUT {Counter:D5} {nameof(NewPooledTransactionHashesMessage)} to {Node:c} " + + $"in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"); + } + + private ArrayPoolList AddMarkUnknownHashes(ReadOnlySpan hashes) + { + ArrayPoolList discoveredTxHashesAndSizes = new(hashes.Length); + + for (int i = 0; i < hashes.Length; i++) + { + Hash256 hash = hashes[i]; + if (!_txPool.IsKnown(hash)) + { + if (_txPool.NotifyAboutTx(hash, this) is AnnounceResult.RequestRequired) + { + discoveredTxHashesAndSizes.Add(hash); + } + } + } + + return discoveredTxHashesAndSizes; + } + + public virtual void HandleMessage(PooledTransactionRequestMessage message) { - NewPooledTransactionHashesMessage msg = new(hashes); - Send(msg); + using ArrayPoolList hashesToRetry = new(1) { new Hash256(message.TxHash) }; + RequestPooledTransactions(hashesToRetry); } } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/GetPooledTransactionsMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/GetPooledTransactionsMessage.cs index 284eb9aaa3aa..b93a9ec98571 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/GetPooledTransactionsMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/GetPooledTransactionsMessage.cs @@ -1,16 +1,18 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; -namespace Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages +namespace Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages; + +public class GetPooledTransactionsMessage(IOwnedReadOnlyList hashes) : HashesMessage(hashes), INew, GetPooledTransactionsMessage> { - public class GetPooledTransactionsMessage(IOwnedReadOnlyList hashes) : HashesMessage(hashes) - { - public override int PacketType { get; } = Eth65MessageCode.GetPooledTransactions; - public override string Protocol { get; } = "eth"; + public override int PacketType => Eth65MessageCode.GetPooledTransactions; + public override string Protocol => "eth"; + + public static GetPooledTransactionsMessage New(IOwnedReadOnlyList arg) => new(arg); - public override string ToString() => $"{nameof(GetPooledTransactionsMessage)}({Hashes?.Count})"; - } + public override string ToString() => $"{nameof(GetPooledTransactionsMessage)}({Hashes?.Count})"; } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/GetPooledTransactionsMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/GetPooledTransactionsMessageSerializer.cs index 22d16d8b79e3..2f3e7c4b4714 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/GetPooledTransactionsMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/GetPooledTransactionsMessageSerializer.cs @@ -4,14 +4,18 @@ using DotNetty.Buffers; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Stats.SyncLimits; namespace Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages { public class GetPooledTransactionsMessageSerializer : HashesMessageSerializer { + private static readonly RlpLimit RlpLimit = RlpLimit.For(NethermindSyncLimits.MaxHashesFetch, nameof(GetPooledTransactionsMessage.Hashes)); + public override GetPooledTransactionsMessage Deserialize(IByteBuffer byteBuffer) { - ArrayPoolList? hashes = DeserializeHashesArrayPool(byteBuffer); + ArrayPoolList? hashes = DeserializeHashesArrayPool(byteBuffer, RlpLimit); return new GetPooledTransactionsMessage(hashes); } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/NewPooledTransactionHashesMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/NewPooledTransactionHashesMessage.cs index f23bcd94d2bb..f00fc49b385e 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/NewPooledTransactionHashesMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/NewPooledTransactionHashesMessage.cs @@ -8,12 +8,12 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages { public class NewPooledTransactionHashesMessage(IOwnedReadOnlyList hashes) : HashesMessage(hashes) { - // we are able to safely send message with up to 3102 hashes to not exceed message size of 102400 bytes + // we are able to safely send a message with up to 3102 hashes to not exceed message size of 102400 bytes // which is used by Geth and us as max message size. (3102 items message has 102370 bytes) public const int MaxCount = 2048; - public override int PacketType { get; } = Eth65MessageCode.NewPooledTransactionHashes; - public override string Protocol { get; } = "eth"; + public override int PacketType => Eth65MessageCode.NewPooledTransactionHashes; + public override string Protocol => "eth"; public override string ToString() => $"{nameof(NewPooledTransactionHashesMessage)}({Hashes?.Count})"; } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/NewPooledTransactionHashesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/NewPooledTransactionHashesMessageSerializer.cs index dd3580492e9b..535551f2ddf9 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/NewPooledTransactionHashesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/NewPooledTransactionHashesMessageSerializer.cs @@ -4,15 +4,19 @@ using DotNetty.Buffers; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Stats.SyncLimits; namespace Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages { public class NewPooledTransactionHashesMessageSerializer : HashesMessageSerializer { + private static readonly RlpLimit RlpLimit = RlpLimit.For(NethermindSyncLimits.MaxHashesFetch, nameof(NewPooledTransactionHashesMessage.Hashes)); + public override NewPooledTransactionHashesMessage Deserialize(IByteBuffer byteBuffer) { - ArrayPoolList? hashes = DeserializeHashesArrayPool(byteBuffer); + ArrayPoolList? hashes = DeserializeHashesArrayPool(byteBuffer, RlpLimit); return new NewPooledTransactionHashesMessage(hashes); } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/PooledTransactionsMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/PooledTransactionsMessage.cs index 887d4b17bd1e..f8169bf5daa2 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/PooledTransactionsMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Messages/PooledTransactionsMessage.cs @@ -7,15 +7,11 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages { - public class PooledTransactionsMessage : TransactionsMessage + public class PooledTransactionsMessage(IOwnedReadOnlyList transactions) + : TransactionsMessage(transactions) { - public override int PacketType { get; } = Eth65MessageCode.PooledTransactions; - public override string Protocol { get; } = "eth"; - - public PooledTransactionsMessage(IOwnedReadOnlyList transactions) - : base(transactions) - { - } + public override int PacketType => Eth65MessageCode.PooledTransactions; + public override string Protocol => "eth"; public override string ToString() => $"{nameof(PooledTransactionsMessage)}({Transactions?.Count})"; } 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 aa9c7d54ca91..dc0adcb8babd 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Eth66ProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Eth66ProtocolHandler.cs @@ -1,15 +1,13 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; using Nethermind.Consensus; using Nethermind.Consensus.Scheduler; using Nethermind.Core; using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; using Nethermind.Logging; +using Nethermind.Network.Contract.Messages; using Nethermind.Network.Contract.P2P; using Nethermind.Network.P2P.Subprotocols.Eth.V65; using Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages; @@ -18,6 +16,9 @@ using Nethermind.Stats; using Nethermind.Synchronization; using Nethermind.TxPool; +using System; +using System.Threading; +using System.Threading.Tasks; using GetPooledTransactionsMessage = Nethermind.Network.P2P.Subprotocols.Eth.V66.Messages.GetPooledTransactionsMessage; using PooledTransactionsMessage = Nethermind.Network.P2P.Subprotocols.Eth.V66.Messages.PooledTransactionsMessage; @@ -32,8 +33,7 @@ public class Eth66ProtocolHandler : Eth65ProtocolHandler private readonly MessageDictionary _bodiesRequests66; private readonly MessageDictionary> _nodeDataRequests66; private readonly MessageDictionary, long)> _receiptsRequests66; - private readonly IPooledTxsRequestor _pooledTxsRequestor; - private readonly Action _sendAction; + public Eth66ProtocolHandler(ISession session, IMessageSerializationService serializer, @@ -41,20 +41,16 @@ public Eth66ProtocolHandler(ISession session, ISyncServer syncServer, IBackgroundTaskScheduler backgroundTaskScheduler, ITxPool txPool, - IPooledTxsRequestor pooledTxsRequestor, IGossipPolicy gossipPolicy, IForkInfo forkInfo, ILogManager logManager, ITxGossipPolicy? transactionsGossipPolicy = null) - : base(session, serializer, nodeStatsManager, syncServer, backgroundTaskScheduler, txPool, pooledTxsRequestor, gossipPolicy, forkInfo, logManager, transactionsGossipPolicy) + : base(session, serializer, nodeStatsManager, syncServer, backgroundTaskScheduler, txPool, gossipPolicy, forkInfo, logManager, transactionsGossipPolicy) { _headersRequests66 = new MessageDictionary>(Send); _bodiesRequests66 = new MessageDictionary(Send); _nodeDataRequests66 = new MessageDictionary>(Send); _receiptsRequests66 = new MessageDictionary, long)>(Send); - _pooledTxsRequestor = pooledTxsRequestor; - // Capture Action once rather than per call - _sendAction = Send; } public override string Name => "eth66"; @@ -68,9 +64,7 @@ public override void HandleMessage(ZeroPacket message) switch (message.PacketType) { case Eth66MessageCode.GetBlockHeaders: - GetBlockHeadersMessage getBlockHeadersMessage = Deserialize(message.Content); - ReportIn(getBlockHeadersMessage, size); - BackgroundTaskScheduler.ScheduleSyncServe(getBlockHeadersMessage, Handle); + HandleInBackground(message, Handle); break; case Eth66MessageCode.BlockHeaders: BlockHeadersMessage headersMsg = Deserialize(message.Content); @@ -78,9 +72,7 @@ public override void HandleMessage(ZeroPacket message) Handle(headersMsg, size); break; case Eth66MessageCode.GetBlockBodies: - GetBlockBodiesMessage getBodiesMsg = Deserialize(message.Content); - ReportIn(getBodiesMsg, size); - BackgroundTaskScheduler.ScheduleSyncServe(getBodiesMsg, Handle); + HandleInBackground(message, Handle); break; case Eth66MessageCode.BlockBodies: BlockBodiesMessage bodiesMsg = Deserialize(message.Content); @@ -88,16 +80,12 @@ public override void HandleMessage(ZeroPacket message) HandleBodies(bodiesMsg, size); break; case Eth66MessageCode.GetPooledTransactions: - GetPooledTransactionsMessage getPooledTxMsg - = Deserialize(message.Content); - ReportIn(getPooledTxMsg, size); - BackgroundTaskScheduler.ScheduleSyncServe(getPooledTxMsg, Handle); + HandleInBackground(message, Handle); break; case Eth66MessageCode.PooledTransactions: if (CanReceiveTransactions) { - PooledTransactionsMessage pooledTxMsg - = Deserialize(message.Content); + PooledTransactionsMessage pooledTxMsg = Deserialize(message.Content); ReportIn(pooledTxMsg, size); Handle(pooledTxMsg.EthMessage); } @@ -109,9 +97,7 @@ PooledTransactionsMessage pooledTxMsg break; case Eth66MessageCode.GetReceipts: - GetReceiptsMessage getReceiptsMessage = Deserialize(message.Content); - ReportIn(getReceiptsMessage, size); - BackgroundTaskScheduler.ScheduleSyncServe(getReceiptsMessage, Handle); + HandleInBackground(message, Handle); break; case Eth66MessageCode.Receipts: ReceiptsMessage receiptsMessage = Deserialize(message.Content); @@ -119,9 +105,7 @@ PooledTransactionsMessage pooledTxMsg Handle(receiptsMessage, size); break; case Eth66MessageCode.GetNodeData: - GetNodeDataMessage getNodeDataMessage = Deserialize(message.Content); - ReportIn(getNodeDataMessage, size); - BackgroundTaskScheduler.ScheduleSyncServe(getNodeDataMessage, Handle); + HandleInBackground(message, Handle); break; case Eth66MessageCode.NodeData: NodeDataMessage nodeDataMessage = Deserialize(message.Content); @@ -189,19 +173,8 @@ protected void Handle(ReceiptsMessage msg, long size) _receiptsRequests66.Handle(msg.RequestId, (msg.EthMessage.TxReceipts, size), size); } - protected override void Handle(NewPooledTransactionHashesMessage msg) - { - using var message = msg; - bool isTrace = Logger.IsTrace; - long startTime = Stopwatch.GetTimestamp(); - - TxPool.Metrics.PendingTransactionsHashesReceived += message.Hashes.Count; - _pooledTxsRequestor.RequestTransactionsEth66(_sendAction, message.Hashes); + protected override void Handle(NewPooledTransactionHashesMessage message) => RequestPooledTransactions(message.Hashes); - if (isTrace) - Logger.Trace($"OUT {Counter:D5} {nameof(NewPooledTransactionHashesMessage)} to {Node:c} " + - $"in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"); - } protected override async Task> SendRequest(V62.Messages.GetBlockHeadersMessage message, CancellationToken token) { @@ -248,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}"); } @@ -266,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}"); } @@ -294,5 +267,11 @@ CancellationToken token return HandleResponse(request, speedType, describeRequestFunc, token); } + + public override void HandleMessage(PooledTransactionRequestMessage message) + { + using ArrayPoolList hashesToRetry = new(1) { new Hash256(message.TxHash) }; + RequestPooledTransactions(hashesToRetry); + } } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/BlockBodiesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/BlockBodiesMessageSerializer.cs index c5d4e2ca9824..dc7fc425bb67 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/BlockBodiesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/BlockBodiesMessageSerializer.cs @@ -3,10 +3,6 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V66.Messages { - public class BlockBodiesMessageSerializer : Eth66MessageSerializer - { - public BlockBodiesMessageSerializer() : base(new V62.Messages.BlockBodiesMessageSerializer()) - { - } - } + public class BlockBodiesMessageSerializer() + : Eth66MessageSerializer(new V62.Messages.BlockBodiesMessageSerializer()); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/BlockHeadersMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/BlockHeadersMessageSerializer.cs index 3b15a9945d64..459d1566c0e5 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/BlockHeadersMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/BlockHeadersMessageSerializer.cs @@ -3,10 +3,6 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V66.Messages { - public class BlockHeadersMessageSerializer : Eth66MessageSerializer - { - public BlockHeadersMessageSerializer() : base(new V62.Messages.BlockHeadersMessageSerializer()) - { - } - } + public class BlockHeadersMessageSerializer() + : Eth66MessageSerializer(new V62.Messages.BlockHeadersMessageSerializer()); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetBlockBodiesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetBlockBodiesMessageSerializer.cs index 7bf40351cae6..f0ebeb4a9fcd 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetBlockBodiesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetBlockBodiesMessageSerializer.cs @@ -3,10 +3,6 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V66.Messages { - public class GetBlockBodiesMessageSerializer : Eth66MessageSerializer - { - public GetBlockBodiesMessageSerializer() : base(new V62.Messages.GetBlockBodiesMessageSerializer()) - { - } - } + public class GetBlockBodiesMessageSerializer() + : Eth66MessageSerializer(new V62.Messages.GetBlockBodiesMessageSerializer()); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetBlockHeadersMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetBlockHeadersMessageSerializer.cs index 6b4769864464..b80f3dbc1bfa 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetBlockHeadersMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetBlockHeadersMessageSerializer.cs @@ -3,10 +3,6 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V66.Messages { - public class GetBlockHeadersMessageSerializer : Eth66MessageSerializer - { - public GetBlockHeadersMessageSerializer() : base(new V62.Messages.GetBlockHeadersMessageSerializer()) - { - } - } + public class GetBlockHeadersMessageSerializer() + : Eth66MessageSerializer(new V62.Messages.GetBlockHeadersMessageSerializer()); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetNodeDataMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetNodeDataMessageSerializer.cs index fc6e82870cf3..0d59ac302506 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetNodeDataMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetNodeDataMessageSerializer.cs @@ -3,10 +3,6 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V66.Messages { - public class GetNodeDataMessageSerializer : Eth66MessageSerializer - { - public GetNodeDataMessageSerializer() : base(new V63.Messages.GetNodeDataMessageSerializer()) - { - } - } + public class GetNodeDataMessageSerializer() + : Eth66MessageSerializer(new V63.Messages.GetNodeDataMessageSerializer()); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetPooledTransactionsMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetPooledTransactionsMessage.cs index d4d1a99570bf..81d502d4252a 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetPooledTransactionsMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetPooledTransactionsMessage.cs @@ -1,16 +1,21 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -namespace Nethermind.Network.P2P.Subprotocols.Eth.V66.Messages +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; + +namespace Nethermind.Network.P2P.Subprotocols.Eth.V66.Messages; + +public class GetPooledTransactionsMessage : Eth66Message, INew, GetPooledTransactionsMessage> { - public class GetPooledTransactionsMessage : Eth66Message + public GetPooledTransactionsMessage() { - public GetPooledTransactionsMessage() - { - } + } - public GetPooledTransactionsMessage(long requestId, V65.Messages.GetPooledTransactionsMessage ethMessage) : base(requestId, ethMessage) - { - } + public GetPooledTransactionsMessage(IOwnedReadOnlyList hashes) : base(MessageConstants.Random.NextLong(), new V65.Messages.GetPooledTransactionsMessage(hashes)) + { } + + public static GetPooledTransactionsMessage New(IOwnedReadOnlyList arg) => new(arg); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetPooledTransactionsMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetPooledTransactionsMessageSerializer.cs index 86d8b7d1d0e3..c81c47799262 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetPooledTransactionsMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetPooledTransactionsMessageSerializer.cs @@ -3,10 +3,6 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V66.Messages { - public class GetPooledTransactionsMessageSerializer : Eth66MessageSerializer - { - public GetPooledTransactionsMessageSerializer() : base(new V65.Messages.GetPooledTransactionsMessageSerializer()) - { - } - } + public class GetPooledTransactionsMessageSerializer() + : Eth66MessageSerializer(new V65.Messages.GetPooledTransactionsMessageSerializer()); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetReceiptsMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetReceiptsMessageSerializer.cs index ec89eb16a2f1..9c3a6ff8c01a 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetReceiptsMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/GetReceiptsMessageSerializer.cs @@ -3,10 +3,6 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V66.Messages { - public class GetReceiptsMessageSerializer : Eth66MessageSerializer - { - public GetReceiptsMessageSerializer() : base(new V63.Messages.GetReceiptsMessageSerializer()) - { - } - } + public class GetReceiptsMessageSerializer() + : Eth66MessageSerializer(new V63.Messages.GetReceiptsMessageSerializer()); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/NodeDataMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/NodeDataMessageSerializer.cs index bc52cce1434f..5887191a14b1 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/NodeDataMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/NodeDataMessageSerializer.cs @@ -3,10 +3,6 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V66.Messages { - public class NodeDataMessageSerializer : Eth66MessageSerializer - { - public NodeDataMessageSerializer() : base(new V63.Messages.NodeDataMessageSerializer()) - { - } - } + public class NodeDataMessageSerializer() + : Eth66MessageSerializer(new V63.Messages.NodeDataMessageSerializer()); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/PooledTransactionsMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/PooledTransactionsMessageSerializer.cs index 1b6832671cf2..6f68ebf22092 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/PooledTransactionsMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/PooledTransactionsMessageSerializer.cs @@ -3,10 +3,6 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V66.Messages { - public class PooledTransactionsMessageSerializer : Eth66MessageSerializer - { - public PooledTransactionsMessageSerializer() : base(new V65.Messages.PooledTransactionsMessageSerializer()) - { - } - } + public class PooledTransactionsMessageSerializer() + : Eth66MessageSerializer(new V65.Messages.PooledTransactionsMessageSerializer()); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/ReceiptsMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/ReceiptsMessageSerializer.cs index b853c6c55795..25a51531cc86 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/ReceiptsMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/ReceiptsMessageSerializer.cs @@ -3,10 +3,6 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V66.Messages { - public class ReceiptsMessageSerializer : Eth66MessageSerializer - { - public ReceiptsMessageSerializer(IZeroInnerMessageSerializer ethMessageSerializer) : base(ethMessageSerializer) - { - } - } + public class ReceiptsMessageSerializer(IZeroInnerMessageSerializer ethMessageSerializer) + : Eth66MessageSerializer(ethMessageSerializer); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V67/Eth67ProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V67/Eth67ProtocolHandler.cs index a154a9dd47ef..d90953907767 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V67/Eth67ProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V67/Eth67ProtocolHandler.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 Nethermind.Consensus; @@ -16,23 +16,20 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V67; /// /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4938.md /// -public class Eth67ProtocolHandler : Eth66ProtocolHandler +public class Eth67ProtocolHandler( + ISession session, + IMessageSerializationService serializer, + INodeStatsManager nodeStatsManager, + ISyncServer syncServer, + IBackgroundTaskScheduler backgroundTaskScheduler, + ITxPool txPool, + IGossipPolicy gossipPolicy, + IForkInfo forkInfo, + ILogManager logManager, + ITxGossipPolicy? transactionsGossipPolicy = null) + : Eth66ProtocolHandler(session, serializer, nodeStatsManager, syncServer, backgroundTaskScheduler, txPool, + gossipPolicy, forkInfo, logManager, transactionsGossipPolicy) { - public Eth67ProtocolHandler(ISession session, - IMessageSerializationService serializer, - INodeStatsManager nodeStatsManager, - ISyncServer syncServer, - IBackgroundTaskScheduler backgroundTaskScheduler, - ITxPool txPool, - IPooledTxsRequestor pooledTxsRequestor, - IGossipPolicy gossipPolicy, - IForkInfo forkInfo, - ILogManager logManager, - ITxGossipPolicy? transactionsGossipPolicy = null) - : base(session, serializer, nodeStatsManager, syncServer, backgroundTaskScheduler, txPool, pooledTxsRequestor, gossipPolicy, forkInfo, logManager, transactionsGossipPolicy) - { - } - public override string Name => "eth67"; public override byte ProtocolVersion => EthVersions.Eth67; 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 e6c50c85b8eb..512ef7f38e6b 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandler.cs @@ -1,54 +1,59 @@ -// 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.Diagnostics; using Nethermind.Consensus; using Nethermind.Consensus.Scheduler; using Nethermind.Core; +using Nethermind.Core.Caching; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; using Nethermind.Logging; using Nethermind.Network.Contract.P2P; +using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; using Nethermind.Network.P2P.Subprotocols.Eth.V67; using Nethermind.Network.P2P.Subprotocols.Eth.V68.Messages; using Nethermind.Network.Rlpx; using Nethermind.Stats; using Nethermind.Synchronization; using Nethermind.TxPool; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; namespace Nethermind.Network.P2P.Subprotocols.Eth.V68; -public class Eth68ProtocolHandler : Eth67ProtocolHandler +public class Eth68ProtocolHandler(ISession session, + IMessageSerializationService serializer, + INodeStatsManager nodeStatsManager, + ISyncServer syncServer, + IBackgroundTaskScheduler backgroundTaskScheduler, + ITxPool txPool, + IGossipPolicy gossipPolicy, + IForkInfo forkInfo, + ILogManager logManager, + ITxPoolConfig txPoolConfig, + ISpecProvider specProvider, + ITxGossipPolicy? transactionsGossipPolicy = null + ) + : Eth67ProtocolHandler(session, serializer, nodeStatsManager, syncServer, backgroundTaskScheduler, txPool, gossipPolicy, forkInfo, logManager, transactionsGossipPolicy) { - private readonly IPooledTxsRequestor _pooledTxsRequestor; + private readonly bool _blobSupportEnabled = txPoolConfig.BlobsSupport.IsEnabled(); + private readonly long _configuredMaxTxSize = txPoolConfig.MaxTxSize ?? long.MaxValue; - private readonly Action _sendAction; + private readonly long _configuredMaxBlobTxSize = txPoolConfig.MaxBlobTxSize is null + ? long.MaxValue + : txPoolConfig.MaxBlobTxSize.Value + (long)specProvider.GetFinalMaxBlobGasPerBlock(); + + private ClockCache TxShapeAnnouncements { get; } = new(MemoryAllowance.TxHashCacheSize / 10); public override string Name => "eth68"; public override byte ProtocolVersion => EthVersions.Eth68; - public Eth68ProtocolHandler(ISession session, - IMessageSerializationService serializer, - INodeStatsManager nodeStatsManager, - ISyncServer syncServer, - IBackgroundTaskScheduler backgroundTaskScheduler, - ITxPool txPool, - IPooledTxsRequestor pooledTxsRequestor, - IGossipPolicy gossipPolicy, - IForkInfo forkInfo, - ILogManager logManager, - ITxGossipPolicy? transactionsGossipPolicy = null) - : base(session, serializer, nodeStatsManager, syncServer, backgroundTaskScheduler, txPool, pooledTxsRequestor, gossipPolicy, forkInfo, logManager, transactionsGossipPolicy) - { - _pooledTxsRequestor = pooledTxsRequestor; - - // Capture Action once rather than per call - _sendAction = Send; - } - public override void HandleMessage(ZeroPacket message) { int size = message.Content.ReadableBytes; @@ -77,15 +82,15 @@ public override void HandleMessage(ZeroPacket message) private void Handle(NewPooledTransactionHashesMessage68 msg) { - using var message = msg; - bool isTrace = Logger.IsTrace; + using NewPooledTransactionHashesMessage68 message = msg; + if (message.Hashes.Count != message.Types.Count || message.Hashes.Count != message.Sizes.Count) { string errorMessage = $"Wrong format of {nameof(NewPooledTransactionHashesMessage68)} message. " + $"Hashes count: {message.Hashes.Count} " + $"Types count: {message.Types.Count} " + $"Sizes count: {message.Sizes.Count}"; - if (isTrace) Logger.Trace(errorMessage); + if (Logger.IsTrace) Logger.Trace(errorMessage); throw new SubprotocolException(errorMessage); } @@ -94,11 +99,86 @@ private void Handle(NewPooledTransactionHashesMessage68 msg) AddNotifiedTransactions(message.Hashes); - long startTime = isTrace ? Stopwatch.GetTimestamp() : 0; + long startTime = Logger.IsTrace ? Stopwatch.GetTimestamp() : 0; - _pooledTxsRequestor.RequestTransactionsEth68(_sendAction, message.Hashes, message.Sizes, message.Types); + RequestPooledTransactions(message.Hashes, message.Sizes, message.Types); - if (isTrace) Logger.Trace($"OUT {Counter:D5} {nameof(NewPooledTransactionHashesMessage68)} to {Node:c} in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds}ms"); + if (Logger.IsTrace) Logger.Trace($"OUT {Counter:D5} {nameof(NewPooledTransactionHashesMessage68)} to {Node:c} in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds}ms"); + } + + protected void RequestPooledTransactions(IOwnedReadOnlyList hashes, IOwnedReadOnlyList sizes, IOwnedReadOnlyList types) + { + using ArrayPoolListRef newTxHashesIndexes = AddMarkUnknownHashes(hashes.AsSpan()); + + if (newTxHashesIndexes.Count == 0) + { + hashes.Dispose(); + sizes.Dispose(); + types.Dispose(); + + return; + } + + int packetSizeLeft = TransactionsMessage.MaxPacketSize; + ArrayPoolList hashesToRequest = new(newTxHashesIndexes.Count); + + int discoveredCount = newTxHashesIndexes.Count; + int toRequestCount = 0; + + foreach (int index in newTxHashesIndexes.AsSpan()) + { + Hash256 hash = hashes[index]; + int txSize = sizes[index]; + TxType txType = (TxType)types[index]; + TxShapeAnnouncements.Set(hash, (txSize, txType)); + + long maxTxSize = txType.SupportsBlobs() ? _configuredMaxBlobTxSize : _configuredMaxTxSize; + + if (txSize > maxTxSize) + continue; + + if ((txSize > packetSizeLeft && toRequestCount > 0) || toRequestCount >= 256) + { + Send(V66.Messages.GetPooledTransactionsMessage.New(hashesToRequest)); + hashesToRequest = new ArrayPoolList(discoveredCount); + packetSizeLeft = TransactionsMessage.MaxPacketSize; + toRequestCount = 0; + } + + if (_blobSupportEnabled || txType != TxType.Blob) + { + hashesToRequest.Add(hash); + packetSizeLeft -= txSize; + toRequestCount++; + } + } + + if (hashesToRequest.Count is not 0) + { + Send(V66.Messages.GetPooledTransactionsMessage.New(hashesToRequest)); + } + else + { + hashesToRequest.Dispose(); + } + } + + private ArrayPoolListRef AddMarkUnknownHashes(ReadOnlySpan hashes) + { + ArrayPoolListRef discoveredTxHashesAndSizes = new(hashes.Length); + for (int i = 0; i < hashes.Length; i++) + { + Hash256 hash = hashes[i]; + if (!_txPool.IsKnown(hash)) + { + if (_txPool.NotifyAboutTx(hash, this) is AnnounceResult.RequestRequired) + { + discoveredTxHashesAndSizes.Add(i); + } + } + } + + return discoveredTxHashesAndSizes; } protected override void SendNewTransactionCore(Transaction tx) @@ -165,4 +245,22 @@ private void SendMessage(IOwnedReadOnlyList types, IOwnedReadOnlyList NewPooledTransactionHashesMessage68 message = new(types, sizes, hashes); Send(message); } + + protected override ValueTask HandleSlow((IOwnedReadOnlyList txs, int startIndex) request, CancellationToken cancellationToken) + { + int startIdx = request.startIndex; + for (int i = startIdx; i < request.txs.Count; i++) + { + if (!ValidateSizeAndType(request.txs[i])) + { + request.txs.Dispose(); + throw new SubprotocolException("invalid pooled tx type or size"); + } + } + + return base.HandleSlow(request, cancellationToken); + } + + private bool ValidateSizeAndType(Transaction tx) + => !TxShapeAnnouncements.Delete(tx.Hash!, out (int Size, TxType Type) txShape) || (tx.GetLength() == txShape.Size && tx.Type == txShape.Type); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Messages/NewPooledTransactionHashesMessage68.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Messages/NewPooledTransactionHashesMessage68.cs index bfeb04e34b17..8526e32c469d 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Messages/NewPooledTransactionHashesMessage68.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Messages/NewPooledTransactionHashesMessage68.cs @@ -16,8 +16,8 @@ public class NewPooledTransactionHashesMessage68( // of 102400 bytes which is used by Geth and us as max message size. (2925 items message has 102385 bytes) public const int MaxCount = 2048; - public override int PacketType { get; } = Eth68MessageCode.NewPooledTransactionHashes; - public override string Protocol { get; } = "eth"; + public override int PacketType => Eth68MessageCode.NewPooledTransactionHashes; + public override string Protocol => "eth"; public readonly IOwnedReadOnlyList Types = types; public readonly IOwnedReadOnlyList Sizes = sizes; diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Messages/NewPooledTransactionHashesMessageSerializer68.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Messages/NewPooledTransactionHashesMessageSerializer68.cs index 64e80a7bd696..5e36ab48e844 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Messages/NewPooledTransactionHashesMessageSerializer68.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Messages/NewPooledTransactionHashesMessageSerializer68.cs @@ -5,19 +5,24 @@ using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Serialization.Rlp; +using Nethermind.Stats.SyncLimits; namespace Nethermind.Network.P2P.Subprotocols.Eth.V68.Messages { public class NewPooledTransactionHashesMessageSerializer : IZeroMessageSerializer { + private static readonly RlpLimit TypesRlpLimit = RlpLimit.For(NethermindSyncLimits.MaxHashesFetch, nameof(NewPooledTransactionHashesMessage68.Types)); + private static readonly RlpLimit SizesRlpLimit = RlpLimit.For(NethermindSyncLimits.MaxHashesFetch, nameof(NewPooledTransactionHashesMessage68.Sizes)); + private static readonly RlpLimit HashesRlpLimit = RlpLimit.For(NethermindSyncLimits.MaxHashesFetch, nameof(NewPooledTransactionHashesMessage68.Hashes)); + public NewPooledTransactionHashesMessage68 Deserialize(IByteBuffer byteBuffer) { NettyRlpStream rlpStream = new(byteBuffer); rlpStream.ReadSequenceLength(); - ArrayPoolList types = rlpStream.DecodeByteArrayPoolList(); - ArrayPoolList sizes = rlpStream.DecodeArrayPoolList(static item => item.DecodeInt()); - ArrayPoolList hashes = rlpStream.DecodeArrayPoolList(static item => item.DecodeKeccak()); + ArrayPoolList types = rlpStream.DecodeByteArrayPoolList(TypesRlpLimit); + ArrayPoolList sizes = rlpStream.DecodeArrayPoolList(static item => item.DecodeInt(), limit: SizesRlpLimit); + ArrayPoolList hashes = rlpStream.DecodeArrayPoolList(static item => item.DecodeKeccak(), limit: HashesRlpLimit); return new NewPooledTransactionHashesMessage68(types, sizes, hashes); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Eth69ProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Eth69ProtocolHandler.cs index 389bc1f7a4d5..4616984be10d 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Eth69ProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Eth69ProtocolHandler.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; @@ -8,7 +8,7 @@ using Nethermind.Consensus; using Nethermind.Consensus.Scheduler; using Nethermind.Core; -using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Network.Contract.P2P; @@ -27,22 +27,22 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V69; /// /// https://eips.ethereum.org/EIPS/eip-7642 /// -public class Eth69ProtocolHandler : Eth68ProtocolHandler, ISyncPeer +public class Eth69ProtocolHandler( + ISession session, + IMessageSerializationService serializer, + INodeStatsManager nodeStatsManager, + ISyncServer syncServer, + IBackgroundTaskScheduler backgroundTaskScheduler, + ITxPool txPool, + IGossipPolicy gossipPolicy, + IForkInfo forkInfo, + ILogManager logManager, + ITxPoolConfig txPoolConfig, + ISpecProvider specProvider, + ITxGossipPolicy? transactionsGossipPolicy = null) + : Eth68ProtocolHandler(session, serializer, nodeStatsManager, syncServer, backgroundTaskScheduler, txPool, + gossipPolicy, forkInfo, logManager, txPoolConfig, specProvider, transactionsGossipPolicy), ISyncPeer { - public Eth69ProtocolHandler(ISession session, - IMessageSerializationService serializer, - INodeStatsManager nodeStatsManager, - ISyncServer syncServer, - IBackgroundTaskScheduler backgroundTaskScheduler, - ITxPool txPool, - IPooledTxsRequestor pooledTxsRequestor, - IGossipPolicy gossipPolicy, - IForkInfo forkInfo, - ILogManager logManager, - ITxGossipPolicy? transactionsGossipPolicy = null) - : base(session, serializer, nodeStatsManager, syncServer, backgroundTaskScheduler, txPool, pooledTxsRequestor, gossipPolicy, forkInfo, logManager, transactionsGossipPolicy) - { } - public override string Name => "eth69"; public override byte ProtocolVersion => EthVersions.Eth69; @@ -65,23 +65,21 @@ public override void HandleMessage(ZeroPacket message) { case Eth69MessageCode.Status: StatusMessage69 statusMsg = Deserialize(message.Content); - base.ReportIn(statusMsg, size); - this.Handle(statusMsg); + ReportIn(statusMsg, size); + Handle(statusMsg); break; case Eth69MessageCode.Receipts: ReceiptsMessage69 receiptsMessage = Deserialize(message.Content); - base.ReportIn(receiptsMessage, size); + ReportIn(receiptsMessage, size); base.Handle(receiptsMessage, size); break; case Eth69MessageCode.GetReceipts: - GetReceiptsMessage getReceiptsMessage = Deserialize(message.Content); - ReportIn(getReceiptsMessage, size); - BackgroundTaskScheduler.ScheduleSyncServe(getReceiptsMessage, this.Handle); + HandleInBackground(message, Handle); break; case Eth69MessageCode.BlockRangeUpdate: BlockRangeUpdateMessage blockRangeUpdateMsg = Deserialize(message.Content); ReportIn(blockRangeUpdateMsg, size); - this.Handle(blockRangeUpdateMsg); + Handle(blockRangeUpdateMsg); break; default: base.HandleMessage(message); @@ -154,7 +152,7 @@ protected override void NotifyOfStatus(BlockHeader head) NetworkId = SyncServer.NetworkId, GenesisHash = SyncServer.Genesis.Hash!, ForkId = _forkInfo.GetForkId(head.Number, head.Timestamp), - EarliestBlock = 0, + EarliestBlock = SyncServer.LowestBlock, LatestBlock = head.Number, LatestBlockHash = head.Hash! }; diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/BlockRangeUpdateMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/BlockRangeUpdateMessage.cs index 5805b7732866..b36be88abcdb 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/BlockRangeUpdateMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/BlockRangeUpdateMessage.cs @@ -12,8 +12,8 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V69.Messages; /// public class BlockRangeUpdateMessage : P2PMessage { - public override int PacketType { get; } = Eth69MessageCode.BlockRangeUpdate; - public override string Protocol { get; } = "eth"; + public override int PacketType => Eth69MessageCode.BlockRangeUpdate; + public override string Protocol => "eth"; /// /// Number of the earliest available full block. diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/ReceiptMessageDecoder69.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/ReceiptMessageDecoder69.cs index 2b594d7e7f29..0920e9499136 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/ReceiptMessageDecoder69.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/ReceiptMessageDecoder69.cs @@ -10,25 +10,19 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V69.Messages; [Rlp.SkipGlobalRegistration] // Created explicitly -public class ReceiptMessageDecoder69 : IRlpStreamDecoder, IRlpValueDecoder +public sealed class ReceiptMessageDecoder69(bool skipStateAndStatus = false) : RlpValueDecoder { - private readonly bool _skipStateAndStatus; - - public ReceiptMessageDecoder69(bool skipStateAndStatus = false) - { - _skipStateAndStatus = skipStateAndStatus; - } - public TxReceipt? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override TxReceipt? DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { Span span = rlpStream.PeekNextItem(); - Rlp.ValueDecoderContext ctx = new Rlp.ValueDecoderContext(span); + Rlp.ValueDecoderContext ctx = new(span); TxReceipt response = Decode(ref ctx, rlpBehaviors); rlpStream.SkipItem(); return response; } - public TxReceipt? Decode(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override TxReceipt? DecodeInternal(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (ctx.IsNextItemNull()) { @@ -61,6 +55,7 @@ public ReceiptMessageDecoder69(bool skipStateAndStatus = false) int lastCheck = ctx.ReadSequenceLength() + ctx.Position; int numberOfReceipts = ctx.PeekNumberOfItemsRemaining(lastCheck); + ctx.GuardLimit(numberOfReceipts); LogEntry[] entries = new LogEntry[numberOfReceipts]; for (int i = 0; i < numberOfReceipts; i++) { @@ -88,7 +83,7 @@ public ReceiptMessageDecoder69(bool skipStateAndStatus = false) bool isEip658Receipts = (rlpBehaviors & RlpBehaviors.Eip658Receipts) == RlpBehaviors.Eip658Receipts; - if (!_skipStateAndStatus) + if (!skipStateAndStatus) { contentLength += isEip658Receipts ? Rlp.LengthOf(item.StatusCode) @@ -109,13 +104,13 @@ private static int GetLogsLength(TxReceipt item) return logsLength; } - public int GetLength(TxReceipt item, RlpBehaviors rlpBehaviors) + public override int GetLength(TxReceipt item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { (int total, _) = GetContentLength(item, rlpBehaviors); return Rlp.LengthOfSequence(total); } - public void Encode(RlpStream rlpStream, TxReceipt? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream rlpStream, TxReceipt? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) { @@ -129,7 +124,7 @@ public void Encode(RlpStream rlpStream, TxReceipt? item, RlpBehaviors rlpBehavio rlpStream.Encode((byte)item.TxType); - if (!_skipStateAndStatus) + if (!skipStateAndStatus) { if ((rlpBehaviors & RlpBehaviors.Eip658Receipts) == RlpBehaviors.Eip658Receipts) { diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/ReceiptsInnerMessage69.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/ReceiptsInnerMessage69.cs index 7d05b83f4e6c..19a43e51759b 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/ReceiptsInnerMessage69.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/ReceiptsInnerMessage69.cs @@ -6,7 +6,4 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V69.Messages; -public class ReceiptsInnerMessage69 : V63.Messages.ReceiptsMessage -{ - public ReceiptsInnerMessage69(IOwnedReadOnlyList txReceipts) : base(txReceipts) { } -} +public class ReceiptsInnerMessage69(IOwnedReadOnlyList txReceipts) : V63.Messages.ReceiptsMessage(txReceipts); diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/ReceiptsMessageInnerSerializer69.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/ReceiptsMessageInnerSerializer69.cs index 82c5c0ca72f3..fe4c5941d94e 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/ReceiptsMessageInnerSerializer69.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/ReceiptsMessageInnerSerializer69.cs @@ -11,12 +11,10 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V69.Messages; /// "Inner" serializer here inherits and overrides parts of eth/63 implementation, /// while "wraps" it after, similar to eth/66 version. /// -public class ReceiptsMessageInnerSerializer69 : - ReceiptsMessageSerializer, +public class ReceiptsMessageInnerSerializer69(ISpecProvider specProvider) : + ReceiptsMessageSerializer(specProvider, new ReceiptMessageDecoder69()), IZeroInnerMessageSerializer { - public ReceiptsMessageInnerSerializer69(ISpecProvider specProvider) : base(specProvider, new ReceiptMessageDecoder69()) { } - int IZeroInnerMessageSerializer.GetLength(ReceiptsInnerMessage69 message, out int contentLength) => GetLength(message, out contentLength); diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/ReceiptsMessageSerializer69.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/ReceiptsMessageSerializer69.cs index 442b274210ca..9860a409286c 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/ReceiptsMessageSerializer69.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/ReceiptsMessageSerializer69.cs @@ -7,12 +7,10 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V69.Messages { - public class ReceiptsMessageSerializer69 : - ReceiptsMessageSerializer, + public class ReceiptsMessageSerializer69(ISpecProvider specProvider) : + ReceiptsMessageSerializer(new ReceiptsMessageInnerSerializer69(specProvider)), IZeroInnerMessageSerializer { - public ReceiptsMessageSerializer69(ISpecProvider specProvider) : base(new ReceiptsMessageInnerSerializer69(specProvider)) { } - int IZeroInnerMessageSerializer.GetLength(ReceiptsMessage69 message, out int contentLength) => base.GetLength(message, out contentLength); diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/StatusMessage69.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/StatusMessage69.cs index fba19fc24a4b..71e0e143af4d 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/StatusMessage69.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V69/Messages/StatusMessage69.cs @@ -18,8 +18,8 @@ public class StatusMessage69 : P2PMessage public long LatestBlock { get; set; } public required Hash256 LatestBlockHash { get; set; } - public override int PacketType { get; } = Eth69MessageCode.Status; - public override string Protocol { get; } = "eth"; + public override int PacketType => Eth69MessageCode.Status; + public override string Protocol => "eth"; public override string ToString() { diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/Messages/GetNodeDataMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/Messages/GetNodeDataMessage.cs index b45e331b7aef..e714ddd67514 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/Messages/GetNodeDataMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/Messages/GetNodeDataMessage.cs @@ -6,13 +6,8 @@ namespace Nethermind.Network.P2P.Subprotocols.NodeData.Messages; -public class GetNodeDataMessage : Eth.V63.Messages.GetNodeDataMessage +public class GetNodeDataMessage(IOwnedReadOnlyList keys) : Eth.V63.Messages.GetNodeDataMessage(keys) { - public override int PacketType { get; } = NodeDataMessageCode.GetNodeData; - public override string Protocol { get; } = "nodedata"; - - public GetNodeDataMessage(IOwnedReadOnlyList keys) - : base(keys) - { - } + public override int PacketType => NodeDataMessageCode.GetNodeData; + public override string Protocol => "nodedata"; } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/Messages/GetNodeDataMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/Messages/GetNodeDataMessageSerializer.cs index 531e35015fe4..1ae3d7b0fa07 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/Messages/GetNodeDataMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/Messages/GetNodeDataMessageSerializer.cs @@ -5,14 +5,18 @@ using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Network.P2P.Subprotocols.Eth; +using Nethermind.Serialization.Rlp; +using Nethermind.Stats.SyncLimits; namespace Nethermind.Network.P2P.Subprotocols.NodeData.Messages; public class GetNodeDataMessageSerializer : HashesMessageSerializer { + private static readonly RlpLimit RlpLimit = RlpLimit.For(NethermindSyncLimits.MaxHashesFetch, nameof(GetNodeDataMessage.Hashes)); + public override GetNodeDataMessage Deserialize(IByteBuffer byteBuffer) { - IOwnedReadOnlyList keys = DeserializeHashesArrayPool(byteBuffer); + IOwnedReadOnlyList keys = DeserializeHashesArrayPool(byteBuffer, RlpLimit); return new GetNodeDataMessage(keys); } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/Messages/NodeDataMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/Messages/NodeDataMessage.cs index 3edbae80c910..693e1b5c21e5 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/Messages/NodeDataMessage.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/Messages/NodeDataMessage.cs @@ -5,13 +5,8 @@ namespace Nethermind.Network.P2P.Subprotocols.NodeData.Messages; -public class NodeDataMessage : Eth.V63.Messages.NodeDataMessage +public class NodeDataMessage(IOwnedReadOnlyList? data) : Eth.V63.Messages.NodeDataMessage(data) { - public override int PacketType { get; } = NodeDataMessageCode.NodeData; - public override string Protocol { get; } = "nodedata"; - - public NodeDataMessage(IOwnedReadOnlyList? data) - : base(data) - { - } + public override int PacketType => NodeDataMessageCode.NodeData; + public override string Protocol => "nodedata"; } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/Messages/NodeDataMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/Messages/NodeDataMessageSerializer.cs index 1ef6a7fb86df..2f1c4c72e60f 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/Messages/NodeDataMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/Messages/NodeDataMessageSerializer.cs @@ -4,11 +4,14 @@ using DotNetty.Buffers; using Nethermind.Core.Collections; using Nethermind.Serialization.Rlp; +using Nethermind.Stats.SyncLimits; namespace Nethermind.Network.P2P.Subprotocols.NodeData.Messages; public class NodeDataMessageSerializer : IZeroInnerMessageSerializer { + private static readonly RlpLimit RlpLimit = RlpLimit.For(NethermindSyncLimits.MaxHashesFetch, nameof(NodeDataMessage.Data)); + public void Serialize(IByteBuffer byteBuffer, NodeDataMessage message) { int length = GetLength(message, out int contentLength); @@ -25,7 +28,7 @@ public void Serialize(IByteBuffer byteBuffer, NodeDataMessage message) public NodeDataMessage Deserialize(IByteBuffer byteBuffer) { RlpStream rlpStream = new NettyRlpStream(byteBuffer); - ArrayPoolList? result = rlpStream.DecodeArrayPoolList(static stream => stream.DecodeByteArray()); + ArrayPoolList? result = rlpStream.DecodeArrayPoolList(static stream => stream.DecodeByteArray(RlpLimit), limit: RlpLimit); return new NodeDataMessage(result); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/NodeDataProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/NodeDataProtocolHandler.cs index 9db1209ee499..e89ff4ed68b8 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/NodeDataProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/NodeData/NodeDataProtocolHandler.cs @@ -15,7 +15,6 @@ using Nethermind.Network.P2P.EventArg; using Nethermind.Network.P2P.ProtocolHandlers; using Nethermind.Network.P2P.Subprotocols.NodeData.Messages; -using Nethermind.Network.P2P.Utils; using Nethermind.Network.Rlpx; using Nethermind.Stats; using Nethermind.Stats.Model; @@ -27,7 +26,6 @@ public class NodeDataProtocolHandler : ZeroProtocolHandlerBase, INodeDataPeer { private readonly ISyncServer _syncServer; private readonly MessageQueue> _nodeDataRequests; - private readonly BackgroundTaskSchedulerWrapper _backgroundTaskScheduler; public override string Name => "nodedata1"; protected override TimeSpan InitTimeout => Timeouts.Eth; @@ -41,10 +39,9 @@ public NodeDataProtocolHandler(ISession session, ISyncServer syncServer, IBackgroundTaskScheduler backgroundTaskScheduler, ILogManager logManager) - : base(session, statsManager, serializer, logManager) + : base(session, statsManager, serializer, backgroundTaskScheduler, logManager) { _syncServer = syncServer ?? throw new ArgumentNullException(nameof(syncServer)); - _backgroundTaskScheduler = new BackgroundTaskSchedulerWrapper(this, backgroundTaskScheduler ?? throw new ArgumentNullException(nameof(backgroundTaskScheduler))); ; _nodeDataRequests = new MessageQueue>(Send); } public override void Init() @@ -77,9 +74,7 @@ public override void HandleMessage(ZeroPacket message) { case NodeDataMessageCode.GetNodeData: { - GetNodeDataMessage getNodeDataMessage = Deserialize(message.Content); - ReportIn(getNodeDataMessage, size); - _backgroundTaskScheduler.ScheduleSyncServe(getNodeDataMessage, Handle); + HandleInBackground(message, Handle); break; } case NodeDataMessageCode.NodeData: @@ -106,13 +101,7 @@ private Task Handle(GetNodeDataMessage getNodeDataMessage, Canc private NodeDataMessage FulfillNodeDataRequest(GetNodeDataMessage msg, CancellationToken cancellationToken) { - if (msg.Hashes.Count > 4096) - { - throw new EthSyncException("NODEDATA protocol: Incoming node data request for more than 4096 nodes"); - } - IOwnedReadOnlyList? nodeData = _syncServer.GetNodeData(msg.Hashes, cancellationToken); - return new NodeDataMessage(nodeData); } 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 238adab58c38..ec56bff71ea4 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetStorageRangesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetStorageRangesMessageSerializer.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Linq; using DotNetty.Buffers; using Nethermind.Serialization.Rlp; using Nethermind.State.Snap; @@ -10,14 +9,20 @@ namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages { public class GetStorageRangesMessageSerializer : SnapSerializerBase { - public override void Serialize(IByteBuffer byteBuffer, GetStorageRangeMessage message) { NettyRlpStream rlpStream = GetRlpStreamAndStartSequence(byteBuffer, message); 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); @@ -49,7 +54,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/Subprotocols/Snap/SnapProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/SnapProtocolHandler.cs index 48c517ccf830..e4d3364da86b 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/SnapProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/SnapProtocolHandler.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using Nethermind.Blockchain.Synchronization; using Nethermind.Consensus.Scheduler; -using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; @@ -16,22 +15,19 @@ using Nethermind.Network.P2P.EventArg; using Nethermind.Network.P2P.ProtocolHandlers; using Nethermind.Network.P2P.Subprotocols.Snap.Messages; -using Nethermind.Network.P2P.Utils; using Nethermind.Network.Rlpx; using Nethermind.State.Snap; using Nethermind.State.SnapServer; using Nethermind.Stats; using Nethermind.Stats.Model; -using Nethermind.Synchronization.SnapSync; namespace Nethermind.Network.P2P.Subprotocols.Snap { public class SnapProtocolHandler : ZeroProtocolHandlerBase, ISnapSyncPeer { - private static readonly TrieNodesMessage EmptyTrieNodesMessage = new TrieNodesMessage(ArrayPoolList.Empty()); + private static readonly TrieNodesMessage EmptyTrieNodesMessage = new(ArrayPoolList.Empty()); private ISnapServer? SyncServer { get; } - private BackgroundTaskSchedulerWrapper BackgroundTaskScheduler { get; } private bool ServingEnabled { get; } public override string Name => "snap1"; @@ -55,14 +51,13 @@ public SnapProtocolHandler(ISession session, IBackgroundTaskScheduler backgroundTaskScheduler, ILogManager logManager, ISnapServer? snapServer = null) - : base(session, nodeStats, serializer, logManager) + : base(session, nodeStats, serializer, backgroundTaskScheduler, logManager) { _getAccountRangeRequests = new(Send); _getStorageRangeRequests = new(Send); _getByteCodesRequests = new(Send); _getTrieNodesRequests = new(Send); SyncServer = snapServer; - BackgroundTaskScheduler = new BackgroundTaskSchedulerWrapper(this, backgroundTaskScheduler); ServingEnabled = SyncServer is not null; } @@ -93,9 +88,7 @@ public override void HandleMessage(ZeroPacket message) case SnapMessageCode.GetAccountRange: if (ShouldServeSnap(nameof(GetAccountRangeMessage))) { - GetAccountRangeMessage getAccountRangeMessage = Deserialize(message.Content); - ReportIn(getAccountRangeMessage, size); - BackgroundTaskScheduler.ScheduleSyncServe(getAccountRangeMessage, Handle); + HandleInBackground(message, Handle); } break; @@ -107,9 +100,7 @@ public override void HandleMessage(ZeroPacket message) case SnapMessageCode.GetStorageRanges: if (ShouldServeSnap(nameof(GetStorageRangeMessage))) { - GetStorageRangeMessage getStorageRangesMessage = Deserialize(message.Content); - ReportIn(getStorageRangesMessage, size); - BackgroundTaskScheduler.ScheduleSyncServe(getStorageRangesMessage, Handle); + HandleInBackground(message, Handle); } break; @@ -121,9 +112,7 @@ public override void HandleMessage(ZeroPacket message) case SnapMessageCode.GetByteCodes: if (ShouldServeSnap(nameof(GetByteCodesMessage))) { - GetByteCodesMessage getByteCodesMessage = Deserialize(message.Content); - ReportIn(getByteCodesMessage, size); - BackgroundTaskScheduler.ScheduleSyncServe(getByteCodesMessage, Handle); + HandleInBackground(message, Handle); } break; @@ -135,9 +124,7 @@ public override void HandleMessage(ZeroPacket message) case SnapMessageCode.GetTrieNodes: if (ShouldServeSnap(nameof(GetTrieNodes))) { - GetTrieNodesMessage getTrieNodesMessage = Deserialize(message.Content); - ReportIn(getTrieNodesMessage, size); - BackgroundTaskScheduler.ScheduleSyncServe(getTrieNodesMessage, Handle); + HandleInBackground(message, Handle); } break; diff --git a/src/Nethermind/Nethermind.Network/P2P/Utils/BackgroundTaskSchedulerWrapper.cs b/src/Nethermind/Nethermind.Network/P2P/Utils/BackgroundTaskSchedulerWrapper.cs index 733c21558820..f2fcc398c217 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Utils/BackgroundTaskSchedulerWrapper.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Utils/BackgroundTaskSchedulerWrapper.cs @@ -7,6 +7,7 @@ using Nethermind.Consensus.Scheduler; using Nethermind.Network.P2P.Messages; using Nethermind.Network.P2P.ProtocolHandlers; +using Nethermind.Serialization.Rlp; using Nethermind.Stats.Model; using Nethermind.Synchronization; @@ -19,33 +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. + // 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.Invoke(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.Invoke(input.Request, cancellationToken); + TRes response = await input.FulfillFunc(input.Request, cancellationToken); handler.Send(response); } @@ -53,11 +48,19 @@ private async Task BackgroundTaskFailureHandlerValueTask((TReq Request, Fu { try { - await input.BackgroundTask.Invoke(input.Request, cancellationToken); + await input.BackgroundTask(input.Request, cancellationToken); } catch (Exception e) { - handler.Session.InitiateDisconnect(e is EthSyncException ? DisconnectReason.EthSyncException : DisconnectReason.BackgroundTaskFailure, e.Message); + DisconnectReason disconnectReason = e switch + { + EthSyncException => DisconnectReason.EthSyncException, + RlpLimitException => DisconnectReason.MessageLimitsBreached, + RlpException => DisconnectReason.BreachOfProtocol, + _ => DisconnectReason.BackgroundTaskFailure + }; + + handler.Session.InitiateDisconnect(disconnectReason, e.Message); if (handler.Logger.IsDebug) handler.Logger.Debug($"Failure running background task on session {handler.Session}, {e}"); } } diff --git a/src/Nethermind/Nethermind.Network/PeerEqualityComparer.cs b/src/Nethermind/Nethermind.Network/PeerEqualityComparer.cs deleted file mode 100644 index 68a0ad6649d3..000000000000 --- a/src/Nethermind/Nethermind.Network/PeerEqualityComparer.cs +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; - -namespace Nethermind.Network -{ - internal class PeerEqualityComparer : IEqualityComparer - { - public bool Equals(Peer x, Peer y) - { - if (x is null || y is null) - { - return false; - } - - return x.Node.Id.Equals(y.Node.Id); - } - - public int GetHashCode(Peer obj) => obj?.Node is null ? 0 : obj.Node.GetHashCode(); - } -} diff --git a/src/Nethermind/Nethermind.Network/PeerEventArgs.cs b/src/Nethermind/Nethermind.Network/PeerEventArgs.cs index ddf99bd059dc..ffb63957df6a 100644 --- a/src/Nethermind/Nethermind.Network/PeerEventArgs.cs +++ b/src/Nethermind/Nethermind.Network/PeerEventArgs.cs @@ -2,8 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using Nethermind.Network.P2P.Messages; -using Nethermind.Network.Rlpx; using Nethermind.Stats.Model; namespace Nethermind.Network; diff --git a/src/Nethermind/Nethermind.Network/PeerManager.cs b/src/Nethermind/Nethermind.Network/PeerManager.cs index 5c382cf7d9ac..a5c30a9988c0 100644 --- a/src/Nethermind/Nethermind.Network/PeerManager.cs +++ b/src/Nethermind/Nethermind.Network/PeerManager.cs @@ -9,7 +9,6 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; -using FastEnumUtility; using Nethermind.Core; using Nethermind.Core.Attributes; using Nethermind.Core.Collections; @@ -87,6 +86,7 @@ public PeerManager( public int MaxActivePeers => _networkConfig.MaxActivePeers + _peerPool.StaticPeerCount; public int ActivePeersCount => _peerPool.ActivePeerCount; + public int ConnectedPeersCount => _peerPool.ActivePeers.Values.Count(IsConnected); private int AvailableActivePeersCount => MaxActivePeers - _peerPool.ActivePeers.Count; /// diff --git a/src/Nethermind/Nethermind.Network/PeerPool.cs b/src/Nethermind/Nethermind.Network/PeerPool.cs index a49a7c2b829b..659d2a9cad01 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/ProtocolValidator.cs b/src/Nethermind/Nethermind.Network/ProtocolValidator.cs index 36442c98bfcf..bd3b8b6000b7 100644 --- a/src/Nethermind/Nethermind.Network/ProtocolValidator.cs +++ b/src/Nethermind/Nethermind.Network/ProtocolValidator.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Text.RegularExpressions; using Nethermind.Blockchain; using Nethermind.Core; diff --git a/src/Nethermind/Nethermind.Network/ProtocolsManager.cs b/src/Nethermind/Nethermind.Network/ProtocolsManager.cs index 6b79bd4bf632..40f49a6c6eae 100644 --- a/src/Nethermind/Nethermind.Network/ProtocolsManager.cs +++ b/src/Nethermind/Nethermind.Network/ProtocolsManager.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; @@ -10,14 +10,14 @@ using Nethermind.Config; using Nethermind.Consensus; using Nethermind.Consensus.Scheduler; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; using Nethermind.Db; using Nethermind.Logging; using Nethermind.Network.Contract.P2P; using Nethermind.Network.P2P; using Nethermind.Network.P2P.EventArg; -using Nethermind.Network.P2P.Messages; using Nethermind.Network.P2P.ProtocolHandlers; -using Nethermind.Network.P2P.Subprotocols.Eth; using Nethermind.Network.P2P.Subprotocols.Eth.V66; using Nethermind.Network.P2P.Subprotocols.Eth.V67; using Nethermind.Network.P2P.Subprotocols.Eth.V68; @@ -40,8 +40,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) }; @@ -55,7 +53,6 @@ public class ProtocolsManager : IProtocolsManager private readonly ISyncPeerPool _syncPool; private readonly ISyncServer _syncServer; private readonly ITxPool _txPool; - private readonly IPooledTxsRequestor _pooledTxsRequestor; private readonly IDiscoveryApp _discoveryApp; private readonly IMessageSerializationService _serializer; private readonly IRlpxHost _rlpxHost; @@ -66,17 +63,19 @@ public class ProtocolsManager : IProtocolsManager private readonly IGossipPolicy _gossipPolicy; private readonly ITxGossipPolicy _txGossipPolicy; private readonly ILogManager _logManager; + private readonly ITxPoolConfig _txPoolConfig; + private readonly ISpecProvider _specProvider; private readonly ILogger _logger; private readonly IDictionary> _protocolFactories; private readonly HashSet _capabilities = DefaultCapabilities.ToHashSet(); private readonly IBackgroundTaskScheduler _backgroundTaskScheduler; private readonly ISnapServer? _snapServer; + public ProtocolsManager( ISyncPeerPool syncPeerPool, ISyncServer syncServer, IBackgroundTaskScheduler backgroundTaskScheduler, ITxPool txPool, - IPooledTxsRequestor pooledTxsRequestor, IDiscoveryApp discoveryApp, IMessageSerializationService serializationService, IRlpxHost rlpxHost, @@ -87,13 +86,14 @@ public ProtocolsManager( IGossipPolicy gossipPolicy, IWorldStateManager worldStateManager, ILogManager logManager, + ITxPoolConfig txPoolConfig, + ISpecProvider specProvider, ITxGossipPolicy? transactionsGossipPolicy = null) { _syncPool = syncPeerPool ?? throw new ArgumentNullException(nameof(syncPeerPool)); _syncServer = syncServer ?? throw new ArgumentNullException(nameof(syncServer)); _backgroundTaskScheduler = backgroundTaskScheduler ?? throw new ArgumentNullException(nameof(backgroundTaskScheduler)); _txPool = txPool ?? throw new ArgumentNullException(nameof(txPool)); - _pooledTxsRequestor = pooledTxsRequestor ?? throw new ArgumentNullException(nameof(pooledTxsRequestor)); _discoveryApp = discoveryApp ?? throw new ArgumentNullException(nameof(discoveryApp)); _serializer = serializationService ?? throw new ArgumentNullException(nameof(serializationService)); _rlpxHost = rlpxHost ?? throw new ArgumentNullException(nameof(rlpxHost)); @@ -104,6 +104,8 @@ public ProtocolsManager( _gossipPolicy = gossipPolicy ?? throw new ArgumentNullException(nameof(gossipPolicy)); _txGossipPolicy = transactionsGossipPolicy ?? ShouldGossip.Instance; _logManager = logManager ?? throw new ArgumentNullException(nameof(logManager)); + _txPoolConfig = txPoolConfig; + _specProvider = specProvider; _snapServer = worldStateManager.SnapServer; _logger = _logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); @@ -123,23 +125,36 @@ private void SessionDisconnected(object sender, DisconnectEventArgs e) ISession session = (ISession)sender; session.Initialized -= SessionInitialized; session.Disconnected -= SessionDisconnected; + _sessions.TryRemove(session.SessionId, out _); + + if (_logger.IsDebug && session.BestStateReached == SessionState.Initialized) + { + _logger.Debug($"{session.Direction} {session.Node:s} disconnected {e.DisconnectType} {e.DisconnectReason} {e.Details}"); + } + + if (session.Node is not null + && _hangingSatelliteProtocols.TryGetValue(session.Node, out ConcurrentDictionary? registrations) + && registrations is not null) + { + registrations.TryRemove(session.SessionId, out _); + } - if (_syncPeers.TryRemove(session.SessionId, out var removed)) + PublicKey? handlerKey = null; + if (_syncPeers.TryRemove(session.SessionId, out SyncPeerProtocolHandlerBase? removed) && removed is not null) { _syncPool.RemovePeer(removed); - _txPool.RemovePeer(removed.Node.Id); - if (session.BestStateReached == SessionState.Initialized) + if (removed.Node?.Id is not null) { - if (_logger.IsDebug) _logger.Debug($"{session.Direction} {session.Node:s} disconnected {e.DisconnectType} {e.DisconnectReason} {e.Details}"); + handlerKey = removed.Node.Id; + _txPool.RemovePeer(handlerKey); } } - if (_hangingSatelliteProtocols.TryGetValue(session.Node, out var registrations)) + PublicKey sessionKey = session.Node?.Id; + if (sessionKey is not null && sessionKey != handlerKey) { - registrations.TryRemove(session.SessionId, out _); + _txPool.RemovePeer(session.Node.Id); } - - _sessions.TryRemove(session.SessionId, out session); } private void SessionInitialized(object sender, EventArgs e) @@ -195,7 +210,7 @@ private IDictionary> GetProtocolFa { [Protocol.P2P] = (session, _) => { - P2PProtocolHandler handler = new(session, _rlpxHost.LocalNodeId, _stats, _serializer, _logManager); + P2PProtocolHandler handler = new(session, _rlpxHost.LocalNodeId, _stats, _serializer, _backgroundTaskScheduler, _logManager); session.PingSender = handler; InitP2PProtocol(session, handler); @@ -203,12 +218,12 @@ private IDictionary> GetProtocolFa }, [Protocol.Eth] = (session, version) => { - var ethHandler = version switch + Eth66ProtocolHandler ethHandler = version switch { - 66 => new Eth66ProtocolHandler(session, _serializer, _stats, _syncServer, _backgroundTaskScheduler, _txPool, _pooledTxsRequestor, _gossipPolicy, _forkInfo, _logManager, _txGossipPolicy), - 67 => new Eth67ProtocolHandler(session, _serializer, _stats, _syncServer, _backgroundTaskScheduler, _txPool, _pooledTxsRequestor, _gossipPolicy, _forkInfo, _logManager, _txGossipPolicy), - 68 => new Eth68ProtocolHandler(session, _serializer, _stats, _syncServer, _backgroundTaskScheduler, _txPool, _pooledTxsRequestor, _gossipPolicy, _forkInfo, _logManager, _txGossipPolicy), - 69 => new Eth69ProtocolHandler(session, _serializer, _stats, _syncServer, _backgroundTaskScheduler, _txPool, _pooledTxsRequestor, _gossipPolicy, _forkInfo, _logManager, _txGossipPolicy), + 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, _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.") }; @@ -217,7 +232,7 @@ private IDictionary> GetProtocolFa }, [Protocol.Snap] = (session, version) => { - var handler = version switch + SnapProtocolHandler handler = version switch { 1 => new SnapProtocolHandler(session, _stats, _serializer, _backgroundTaskScheduler, _logManager, _snapServer), _ => throw new NotSupportedException($"{Protocol.Snap}.{version} is not supported.") @@ -228,7 +243,7 @@ private IDictionary> GetProtocolFa }, [Protocol.NodeData] = (session, version) => { - var handler = version switch + NodeDataProtocolHandler handler = version switch { 1 => new NodeDataProtocolHandler(session, _serializer, _stats, _syncServer, _backgroundTaskScheduler, _logManager), _ => throw new NotSupportedException($"{Protocol.NodeData}.{version} is not supported.") @@ -417,22 +432,6 @@ public void RemoveSupportedCapability(Capability capability) } } - public void SendNewCapability(Capability capability) - { - AddCapabilityMessage message = new(capability); - foreach ((Guid _, ISession session) in _sessions) - { - if (!session.HasAgreedCapability(capability) && session.HasAvailableCapability(capability)) - { - session.DeliverMessage(message); - } - else - { - message.Dispose(); - } - } - } - public int GetHighestProtocolVersion(string protocol) { int highestVersion = 0; diff --git a/src/Nethermind/Nethermind.Network/Rlpx/FrameHeaderReader.cs b/src/Nethermind/Nethermind.Network/Rlpx/FrameHeaderReader.cs index f4087825ef81..48fd05d092da 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/FrameHeaderReader.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/FrameHeaderReader.cs @@ -1,9 +1,12 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using DotNetty.Buffers; +using DotNetty.Codecs; using Nethermind.Serialization.Rlp; +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace Nethermind.Network.Rlpx { @@ -28,25 +31,43 @@ public FrameInfo ReadFrameHeader(IByteBuffer input) _currentContextId = contextId; int? totalPacketSize = numberOfItems > 2 ? headerBodyItems.DecodeInt() : (int?)null; + ValidateTotalPacketSize(frameSize, totalPacketSize); + 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); } - internal readonly struct FrameInfo + private static void ValidateTotalPacketSize(int frameSize, int? totalPacketSize) { - public FrameInfo(bool isChunked, bool isFirst, int size, int totalPacketSize) + if (totalPacketSize is not null) + { + if (totalPacketSize <= 0 || totalPacketSize > SnappyParameters.MaxSnappyLength) + { + ThrowCorruptedFrameException(frameSize, totalPacketSize.Value); + } + + if (frameSize > totalPacketSize) + { + ThrowCorruptedFrameException(frameSize, totalPacketSize.Value); + } + } + + [DoesNotReturn, StackTraceHidden] + static void ThrowCorruptedFrameException(int frameSize, int totalPacketSize) { - IsChunked = isChunked; - IsFirst = isFirst; - Size = size; - TotalPacketSize = totalPacketSize; + throw new CorruptedFrameException($"Invalid Rlpx header lengths, packet size {totalPacketSize}, frame size {frameSize}"); } + } - public bool IsChunked { get; } - public bool IsFirst { get; } - public int Size { get; } - public int TotalPacketSize { get; } + internal readonly struct FrameInfo(bool isChunked, bool isFirst, int size, int totalPacketSize) + { + public bool IsChunked { get; } = isChunked; + public bool IsFirst { get; } = isFirst; + public int Size { get; } = size; + public int TotalPacketSize { get; } = totalPacketSize; public int Padding => Frame.CalculatePadding(Size); public int PayloadSize => Size + Padding; diff --git a/src/Nethermind/Nethermind.Network/Rlpx/Handshake/AckEip8MessageSerializer.cs b/src/Nethermind/Nethermind.Network/Rlpx/Handshake/AckEip8MessageSerializer.cs index 4bc7552cf937..f8424995d436 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/Handshake/AckEip8MessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/Handshake/AckEip8MessageSerializer.cs @@ -41,7 +41,7 @@ public AckEip8Message Deserialize(IByteBuffer msgBytes) NettyRlpStream rlpStream = new(msgBytes); AckEip8Message authEip8Message = new(); rlpStream.ReadSequenceLength(); - authEip8Message.EphemeralPublicKey = new PublicKey(rlpStream.DecodeByteArraySpan()); + authEip8Message.EphemeralPublicKey = new PublicKey(rlpStream.DecodeByteArraySpan(RlpLimit.L64)); authEip8Message.Nonce = rlpStream.DecodeByteArray(); return authEip8Message; } diff --git a/src/Nethermind/Nethermind.Network/Rlpx/Handshake/AckMessageSerializer.cs b/src/Nethermind/Nethermind.Network/Rlpx/Handshake/AckMessageSerializer.cs index 27190620f79d..b0de00736313 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/Handshake/AckMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/Handshake/AckMessageSerializer.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using DotNetty.Buffers; using Nethermind.Core.Crypto; using Nethermind.Serialization.Rlp; @@ -21,12 +20,9 @@ public class AckMessageSerializer : IZeroMessageSerializer public void Serialize(IByteBuffer byteBuffer, AckMessage msg) { byteBuffer.EnsureWritable(TotalLength); - // TODO: find a way to now allocate this here - byte[] data = new byte[TotalLength]; - Buffer.BlockCopy(msg.EphemeralPublicKey.Bytes, 0, data, EphemeralPublicKeyOffset, EphemeralPublicKeyLength); - Buffer.BlockCopy(msg.Nonce, 0, data, NonceOffset, NonceLength); - data[IsTokenUsedOffset] = msg.IsTokenUsed ? (byte)0x01 : (byte)0x00; - byteBuffer.WriteBytes(data); + byteBuffer.WriteBytes(msg.EphemeralPublicKey.Bytes); + byteBuffer.WriteBytes(msg.Nonce); + byteBuffer.WriteByte(msg.IsTokenUsed ? (byte)0x01 : (byte)0x00); } public AckMessage Deserialize(IByteBuffer msgBytes) diff --git a/src/Nethermind/Nethermind.Network/Rlpx/Handshake/AuthEip8MessageSerializer.cs b/src/Nethermind/Nethermind.Network/Rlpx/Handshake/AuthEip8MessageSerializer.cs index d310ecec7cbb..627a24ac726d 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/Handshake/AuthEip8MessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/Handshake/AuthEip8MessageSerializer.cs @@ -46,10 +46,10 @@ public AuthEip8Message Deserialize(IByteBuffer msgBytes) NettyRlpStream rlpStream = new(msgBytes); AuthEip8Message authMessage = new(); rlpStream.ReadSequenceLength(); - ReadOnlySpan sigAllBytes = rlpStream.DecodeByteArraySpan(); + ReadOnlySpan sigAllBytes = rlpStream.DecodeByteArraySpan(RlpLimit.L65); Signature signature = new(sigAllBytes[..64], sigAllBytes[64]); // since Signature class is Ethereum style it expects V as the 65th byte, hence we use RecoveryID constructor authMessage.Signature = signature; - authMessage.PublicKey = new PublicKey(rlpStream.DecodeByteArraySpan()); + authMessage.PublicKey = new PublicKey(rlpStream.DecodeByteArraySpan(RlpLimit.L64)); authMessage.Nonce = rlpStream.DecodeByteArray(); return authMessage; } diff --git a/src/Nethermind/Nethermind.Network/Rlpx/NettyHandshakeHandler.cs b/src/Nethermind/Nethermind.Network/Rlpx/NettyHandshakeHandler.cs index ca8d2f683ab9..4e5a09a5e28b 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/NettyHandshakeHandler.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/NettyHandshakeHandler.cs @@ -11,7 +11,6 @@ using DotNetty.Handlers.Timeout; using DotNetty.Transport.Channels; using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; using Nethermind.Logging; using Nethermind.Network.P2P; using Nethermind.Network.P2P.ProtocolHandlers; @@ -166,15 +165,15 @@ protected override void ChannelRead0(IChannelHandlerContext context, IByteBuffer using (FrameMacProcessor macProcessor = new(_session.RemoteNodeId, _handshake.Secrets)) { FrameCipher frameCipher = new(_handshake.Secrets.AesSecret); - context.Channel.Pipeline.AddLast(new ZeroFrameDecoder(frameCipher, macProcessor, _logManager)); + context.Channel.Pipeline.AddLast(new ZeroFrameDecoder(frameCipher, macProcessor)); if (_logger.IsTrace) _logger.Trace($"Registering {nameof(ZeroFrameEncoder)} for {RemoteId} @ {context.Channel.RemoteAddress}"); - context.Channel.Pipeline.AddLast(new ZeroFrameEncoder(frameCipher, macProcessor, _logManager)); + context.Channel.Pipeline.AddLast(new ZeroFrameEncoder(frameCipher, macProcessor)); } if (_logger.IsTrace) _logger.Trace($"Registering {nameof(ZeroFrameMerger)} for {RemoteId} @ {context.Channel.RemoteAddress}"); context.Channel.Pipeline.AddLast(new ZeroFrameMerger(_logManager)); if (_logger.IsTrace) _logger.Trace($"Registering {nameof(ZeroPacketSplitter)} for {RemoteId} @ {context.Channel.RemoteAddress}"); - context.Channel.Pipeline.AddLast(new ZeroPacketSplitter(_logManager)); + context.Channel.Pipeline.AddLast(new ZeroPacketSplitter()); PacketSender packetSender = new(_serializationService, _logManager, _sendLatency); if (_logger.IsTrace) _logger.Trace($"Registering {nameof(PacketSender)} for {_session.RemoteNodeId} @ {context.Channel.RemoteAddress}"); diff --git a/src/Nethermind/Nethermind.Network/Rlpx/Packet.cs b/src/Nethermind/Nethermind.Network/Rlpx/Packet.cs index 439aead8a3df..0ff0255dcbc5 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/Packet.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/Packet.cs @@ -7,36 +7,24 @@ namespace Nethermind.Network.Rlpx { [DebuggerDisplay("{Protocol}.{PacketType}")] - public class Packet + public class Packet(string? protocol, int packetType, byte[] data) { - public byte[] Data; + public byte[] Data = data; - public Packet(ZeroPacket zeroPacket) + public Packet(ZeroPacket zeroPacket) : this(zeroPacket.Protocol, zeroPacket.PacketType, zeroPacket.Content.ReadAllBytesAsArray()) { - Data = zeroPacket.Content.ReadAllBytesAsArray(); - PacketType = zeroPacket.PacketType; - Protocol = zeroPacket.Protocol; } - public Packet(string protocol, int packetType, byte[] data) + public Packet(byte[] data) : this(null, 0, data) { - Data = data; - Protocol = protocol; - PacketType = packetType; } - public Packet(byte[] data) - { - Data = data; - } + public int PacketType { get; set; } = packetType; - public int PacketType { get; set; } + public string? Protocol { get; set; } = protocol; - public string Protocol { get; set; } + public override string ToString() => $"{Protocol ?? "???"}.{PacketType}"; - public override string ToString() - { - return $"{Protocol ?? "???"}.{PacketType}"; - } + public static implicit operator ZeroPacket(Packet packet) => new(packet); } } diff --git a/src/Nethermind/Nethermind.Network/Rlpx/Padding.cs b/src/Nethermind/Nethermind.Network/Rlpx/Padding.cs index 0db9fc47c762..6f6f746d9aeb 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/Padding.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/Padding.cs @@ -16,9 +16,6 @@ public static class Frame public const int DefaultMaxFrameSize = BlockSize * 64; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CalculatePadding(int size) - { - return size % BlockSize == 0 ? 0 : BlockSize - size % BlockSize; - } + public static int CalculatePadding(int size) => size % BlockSize == 0 ? 0 : BlockSize - size % BlockSize; } } diff --git a/src/Nethermind/Nethermind.Network/Rlpx/RlpxHost.cs b/src/Nethermind/Nethermind.Network/Rlpx/RlpxHost.cs index 0b7c243373df..ef857a506056 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/RlpxHost.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/RlpxHost.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using Autofac.Features.AttributeFilters; -using DotNetty.Buffers; using DotNetty.Common.Concurrency; using DotNetty.Handlers.Logging; using DotNetty.Transport.Bootstrapping; @@ -105,7 +104,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 +167,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 +291,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/SnappyDecoder.cs b/src/Nethermind/Nethermind.Network/Rlpx/SnappyDecoder.cs index a41be3a915a2..f21f4b6fa810 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/SnappyDecoder.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/SnappyDecoder.cs @@ -11,15 +11,8 @@ namespace Nethermind.Network.Rlpx; -public class SnappyDecoder : MessageToMessageDecoder +public class SnappyDecoder(ILogger logger) : MessageToMessageDecoder { - private readonly ILogger _logger; - - public SnappyDecoder(ILogger logger) - { - _logger = logger; - } - protected override void Decode(IChannelHandlerContext context, Packet message, List output) { if (Snappy.GetUncompressedLength(message.Data) > SnappyParameters.MaxSnappyLength) @@ -29,11 +22,11 @@ protected override void Decode(IChannelHandlerContext context, Packet message, L if (message.Data.Length > SnappyParameters.MaxSnappyLength / 4) { - if (_logger.IsWarn) _logger.Warn($"Big Snappy message of length {message.Data.Length}"); + if (logger.IsWarn) logger.Warn($"Big Snappy message of length {message.Data.Length}"); } else { - if (_logger.IsTrace) _logger.Trace($"Decompressing with Snappy a message of length {message.Data.Length}"); + if (logger.IsTrace) logger.Trace($"Decompressing with Snappy a message of length {message.Data.Length}"); } try @@ -42,7 +35,7 @@ protected override void Decode(IChannelHandlerContext context, Packet message, L } catch { - _logger.Error($"{message.Data.ToHexString()}"); + logger.Error($"{message.Data.ToHexString()}"); throw; } diff --git a/src/Nethermind/Nethermind.Network/Rlpx/ZeroFrameDecoder.cs b/src/Nethermind/Nethermind.Network/Rlpx/ZeroFrameDecoder.cs index ffc28431207d..9829661df657 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/ZeroFrameDecoder.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/ZeroFrameDecoder.cs @@ -7,15 +7,14 @@ using DotNetty.Buffers; using DotNetty.Codecs; using DotNetty.Transport.Channels; -using Nethermind.Logging; namespace Nethermind.Network.Rlpx { - public class ZeroFrameDecoder : ByteToMessageDecoder + public class ZeroFrameDecoder(IFrameCipher frameCipher, FrameMacProcessor frameMacProcessor) + : ByteToMessageDecoder { - private readonly ILogger _logger; - private readonly IFrameCipher _cipher; - private readonly FrameMacProcessor _authenticator; + private readonly IFrameCipher _cipher = frameCipher ?? throw new ArgumentNullException(nameof(frameCipher)); + private readonly FrameMacProcessor _authenticator = frameMacProcessor ?? throw new ArgumentNullException(nameof(frameMacProcessor)); private readonly byte[] _headerBytes = new byte[Frame.HeaderSize]; private readonly byte[] _macBytes = new byte[Frame.MacSize]; @@ -27,13 +26,6 @@ public class ZeroFrameDecoder : ByteToMessageDecoder private int _frameSize; private int _remainingPayloadBlocks; - public ZeroFrameDecoder(IFrameCipher frameCipher, FrameMacProcessor frameMacProcessor, ILogManager logManager) - { - _cipher = frameCipher ?? throw new ArgumentNullException(nameof(frameCipher)); - _authenticator = frameMacProcessor ?? throw new ArgumentNullException(nameof(frameMacProcessor)); - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - } - public override void HandlerRemoved(IChannelHandlerContext context) { base.HandlerRemoved(context); @@ -45,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.Network/Rlpx/ZeroFrameEncoder.cs b/src/Nethermind/Nethermind.Network/Rlpx/ZeroFrameEncoder.cs index b5307cb624bb..c322c8eb491b 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/ZeroFrameEncoder.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/ZeroFrameEncoder.cs @@ -6,27 +6,21 @@ using DotNetty.Buffers; using DotNetty.Codecs; using DotNetty.Transport.Channels; -using Nethermind.Logging; namespace Nethermind.Network.Rlpx { - public class ZeroFrameEncoder : MessageToByteEncoder + public class ZeroFrameEncoder( + IFrameCipher frameCipher, + IFrameMacProcessor frameMacProcessor) + : MessageToByteEncoder { - private readonly ILogger _logger; - private readonly IFrameCipher _frameCipher; - private readonly IFrameMacProcessor _frameMacProcessor; + private readonly IFrameCipher _frameCipher = frameCipher ?? throw new ArgumentNullException(nameof(frameCipher)); + private readonly IFrameMacProcessor _frameMacProcessor = frameMacProcessor ?? throw new ArgumentNullException(nameof(frameMacProcessor)); private readonly FrameHeaderReader _headerReader = new(); private readonly byte[] _encryptBuffer = new byte[Frame.BlockSize]; private readonly byte[] _macBuffer = new byte[16]; - public ZeroFrameEncoder(IFrameCipher frameCipher, IFrameMacProcessor frameMacProcessor, ILogManager logManager) - { - _frameCipher = frameCipher ?? throw new ArgumentNullException(nameof(frameCipher)); - _frameMacProcessor = frameMacProcessor ?? throw new ArgumentNullException(nameof(frameMacProcessor)); - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - } - protected override void Encode(IChannelHandlerContext context, IByteBuffer input, IByteBuffer output) { while (input.IsReadable()) diff --git a/src/Nethermind/Nethermind.Network/Rlpx/ZeroFrameMerger.cs b/src/Nethermind/Nethermind.Network/Rlpx/ZeroFrameMerger.cs index 4e2b2b9f8cfb..6efe15175f9e 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/ZeroFrameMerger.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/ZeroFrameMerger.cs @@ -12,18 +12,13 @@ namespace Nethermind.Network.Rlpx { - public class ZeroFrameMerger : ByteToMessageDecoder + public class ZeroFrameMerger(ILogManager logManager) : ByteToMessageDecoder { - private readonly ILogger _logger; + private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); private ZeroPacket? _zeroPacket; private readonly FrameHeaderReader _headerReader = new(); - public ZeroFrameMerger(ILogManager logManager) - { - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - } - public override void HandlerRemoved(IChannelHandlerContext context) { base.HandlerRemoved(context); @@ -88,10 +83,12 @@ private void ReadFirstChunk(IChannelHandlerContext context, IByteBuffer input, i content = input.ReadRetainedSlice(frame.Size - 1); } - _zeroPacket = new ZeroPacket(content); - _zeroPacket.PacketType = GetPacketType(packetTypeRlp); + _zeroPacket = new ZeroPacket(content) + { + PacketType = GetPacketType(packetTypeRlp) + }; - // If not chunked then we already used a slice of the input, + // If not chunked, then we already used a slice of the input, // otherwise we need to read into the freshly allocated buffer. if (frame.IsChunked) { @@ -101,9 +98,6 @@ private void ReadFirstChunk(IChannelHandlerContext context, IByteBuffer input, i } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte GetPacketType(byte packetTypeRlp) - { - return packetTypeRlp == 128 ? (byte)0 : packetTypeRlp; - } + private static byte GetPacketType(byte packetTypeRlp) => packetTypeRlp == 128 ? (byte)0 : packetTypeRlp; } } diff --git a/src/Nethermind/Nethermind.Network/Rlpx/ZeroPacketSplitter.cs b/src/Nethermind/Nethermind.Network/Rlpx/ZeroPacketSplitter.cs index b3fcc5ad2e32..96b9bbcb1e4e 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/ZeroPacketSplitter.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/ZeroPacketSplitter.cs @@ -7,20 +7,12 @@ using DotNetty.Codecs; using DotNetty.Transport.Channels; using Nethermind.Core.Attributes; -using Nethermind.Logging; using Nethermind.Serialization.Rlp; namespace Nethermind.Network.Rlpx { - public class ZeroPacketSplitter : MessageToByteEncoder, IFramingAware + public class ZeroPacketSplitter() : MessageToByteEncoder, IFramingAware { - private readonly ILogger _logger; - - public ZeroPacketSplitter(ILogManager logManager) - { - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - } - public void DisableFraming() { MaxFrameSize = int.MaxValue; diff --git a/src/Nethermind/Nethermind.Network/Rlpx/ZeroSnappyEncoder.cs b/src/Nethermind/Nethermind.Network/Rlpx/ZeroSnappyEncoder.cs index 6de5a151c2b8..88b2f36d0f49 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/ZeroSnappyEncoder.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/ZeroSnappyEncoder.cs @@ -10,14 +10,9 @@ namespace Nethermind.Network.Rlpx; -public class ZeroSnappyEncoder : MessageToByteEncoder +public class ZeroSnappyEncoder(ILogManager logManager) : MessageToByteEncoder { - private readonly ILogger _logger; - - public ZeroSnappyEncoder(ILogManager logManager) - { - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - } + private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); protected override void Encode(IChannelHandlerContext context, IByteBuffer input, IByteBuffer output) { diff --git a/src/Nethermind/Nethermind.Network/StaticNodes/StaticNodesManager.cs b/src/Nethermind/Nethermind.Network/StaticNodes/StaticNodesManager.cs index 6c41afdabad5..20f48fc8d470 100644 --- a/src/Nethermind/Nethermind.Network/StaticNodes/StaticNodesManager.cs +++ b/src/Nethermind/Nethermind.Network/StaticNodes/StaticNodesManager.cs @@ -4,172 +4,111 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Net.Sockets; using System.Runtime.CompilerServices; -using System.Text.Json; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using Nethermind.Config; using Nethermind.Core.Crypto; using Nethermind.Logging; -using Nethermind.Serialization.Json; using Nethermind.Stats.Model; -namespace Nethermind.Network.StaticNodes +namespace Nethermind.Network.StaticNodes; + +public class StaticNodesManager(string staticNodesPath, ILogManager logManager) : NodesManager(staticNodesPath, logManager.GetClassLogger()), IStaticNodesManager { - public class StaticNodesManager : IStaticNodesManager + public IEnumerable Nodes => _nodes.Values; + + public async Task InitAsync() { - private ConcurrentDictionary _nodes = new(); + ConcurrentDictionary nodes = await ParseNodes("static-nodes.json"); - private readonly string _staticNodesPath; - private readonly ILogger _logger; + LogNodeList("Static nodes", nodes); + + _nodes = nodes; + } - public StaticNodesManager(string staticNodesPath, ILogManager logManager) + public async Task AddAsync(string enode, bool updateFile = true) + { + NetworkNode networkNode = new(enode); + if (!_nodes.TryAdd(networkNode.NodeId, networkNode)) { - _staticNodesPath = staticNodesPath.GetApplicationResourcePath(); - _logger = logManager.GetClassLogger(); + if (_logger.IsInfo) _logger.Info($"Static node was already added: {enode}"); + return false; } - public IEnumerable Nodes => _nodes.Values; + if (_logger.IsInfo) _logger.Info($"Static node added: {enode}"); - private static readonly char[] separator = new[] { '\r', '\n' }; + Node node = new(networkNode, isStatic: true); + NodeAdded?.Invoke(this, new NodeEventArgs(node)); - public async Task InitAsync() + if (updateFile) { - if (!File.Exists(_staticNodesPath)) - { - if (_logger.IsDebug) _logger.Debug($"Static nodes file was not found for path: {_staticNodesPath}"); - - return; - } - - string data = await File.ReadAllTextAsync(_staticNodesPath); - string[] nodes = GetNodes(data); - if (_logger.IsInfo) - _logger.Info($"Loaded {nodes.Length} static nodes from file: {Path.GetFullPath(_staticNodesPath)}"); - if (nodes.Length != 0) - { - if (_logger.IsDebug) _logger.Debug($"Static nodes: {Environment.NewLine}{data}"); - } + await SaveFileAsync(); + } - List networkNodes = new(); - foreach (string? n in nodes) - { - try - { - NetworkNode networkNode = new(n); - networkNodes.Add(networkNode); - } - catch (Exception exception) when (exception is ArgumentException or SocketException) - { - if (_logger.IsError) _logger.Error("Unable to process node. ", exception); - } - } + return true; + } - _nodes = new ConcurrentDictionary(networkNodes.ToDictionary(static n => n.NodeId, static n => n)); + public async Task RemoveAsync(string enode, bool updateFile = true) + { + NetworkNode networkNode = new(enode); + if (!_nodes.TryRemove(networkNode.NodeId, out _)) + { + if (_logger.IsInfo) _logger.Info($"Static node was not found: {enode}"); + return false; } - private static string[] GetNodes(string data) + if (_logger.IsInfo) _logger.Info($"Static node was removed: {enode}"); + Node node = new(networkNode); + NodeRemoved?.Invoke(this, new NodeEventArgs(node)); + if (updateFile) { - string[] nodes; - try - { - nodes = JsonSerializer.Deserialize(data) ?? []; - } - catch (JsonException) - { - nodes = data.Split(separator, StringSplitOptions.RemoveEmptyEntries); - } - - return nodes.Distinct().ToArray(); + await SaveFileAsync(); } - public async Task AddAsync(string enode, bool updateFile = true) - { - NetworkNode networkNode = new(enode); - if (!_nodes.TryAdd(networkNode.NodeId, networkNode)) - { - if (_logger.IsInfo) _logger.Info($"Static node was already added: {enode}"); - return false; - } + return true; + } - if (_logger.IsInfo) _logger.Info($"Static node added: {enode}"); - Node node = new(networkNode); - NodeAdded?.Invoke(this, new NodeEventArgs(node)); - if (updateFile) - { - await SaveFileAsync(); - } + public bool IsStatic(string enode) + { + NetworkNode node = new(enode); + return _nodes.TryGetValue(node.NodeId, out NetworkNode staticNode) && string.Equals(staticNode.Host, + node.Host, StringComparison.OrdinalIgnoreCase); + } - return true; - } + public async IAsyncEnumerable DiscoverNodes([EnumeratorCancellation] CancellationToken cancellationToken) + { + Channel ch = Channel.CreateBounded(128); // Some reasonably large value - public async Task RemoveAsync(string enode, bool updateFile = true) + foreach (Node node in _nodes.Values.Select(n => new Node(n))) { - NetworkNode networkNode = new(enode); - if (!_nodes.TryRemove(networkNode.NodeId, out _)) - { - if (_logger.IsInfo) _logger.Info($"Static node was not found: {enode}"); - return false; - } - - if (_logger.IsInfo) _logger.Info($"Static node was removed: {enode}"); - Node node = new(networkNode); - NodeRemoved?.Invoke(this, new NodeEventArgs(node)); - if (updateFile) - { - await SaveFileAsync(); - } - - return true; + cancellationToken.ThrowIfCancellationRequested(); + yield return node; } - public bool IsStatic(string enode) + void handler(object? _, NodeEventArgs args) { - NetworkNode node = new(enode); - return _nodes.TryGetValue(node.NodeId, out NetworkNode staticNode) && string.Equals(staticNode.Host, - node.Host, StringComparison.OrdinalIgnoreCase); + ch.Writer.TryWrite(args.Node); } - private Task SaveFileAsync() - => File.WriteAllTextAsync(_staticNodesPath, - JsonSerializer.Serialize(_nodes.Select(static n => n.Value.ToString()), EthereumJsonSerializer.JsonOptionsIndented)); - - public async IAsyncEnumerable DiscoverNodes([EnumeratorCancellation] CancellationToken cancellationToken) + try { - Channel ch = Channel.CreateBounded(128); // Some reasonably large value + NodeAdded += handler; - foreach (Node node in _nodes.Values.Select(n => new Node(n))) + await foreach (Node node in ch.Reader.ReadAllAsync(cancellationToken)) { - cancellationToken.ThrowIfCancellationRequested(); yield return node; } - - void handler(object? _, NodeEventArgs args) - { - ch.Writer.TryWrite(args.Node); - } - - try - { - NodeAdded += handler; - - await foreach (Node node in ch.Reader.ReadAllAsync(cancellationToken)) - { - yield return node; - } - } - finally - { - NodeAdded -= handler; - } } + finally + { + NodeAdded -= handler; + } + } - private event EventHandler? NodeAdded; + private event EventHandler? NodeAdded; - public event EventHandler? NodeRemoved; - } + public event EventHandler? NodeRemoved; } diff --git a/src/Nethermind/Nethermind.Network/StaticNodes/static-nodes.json b/src/Nethermind/Nethermind.Network/StaticNodes/static-nodes.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/StaticNodes/static-nodes.json @@ -0,0 +1 @@ +[] diff --git a/src/Nethermind/Nethermind.Network/Timeouts.cs b/src/Nethermind/Nethermind.Network/Timeouts.cs index 9ed3d1973cf1..2a2051d8ea81 100644 --- a/src/Nethermind/Nethermind.Network/Timeouts.cs +++ b/src/Nethermind/Nethermind.Network/Timeouts.cs @@ -14,11 +14,6 @@ public static class Timeouts public static readonly TimeSpan P2PHello = TimeSpan.FromSeconds(3); public static readonly TimeSpan Eth62Status = TimeSpan.FromSeconds(3); public static readonly TimeSpan Les3Status = TimeSpan.FromSeconds(3); - public static readonly TimeSpan NdmHi = TimeSpan.FromSeconds(3); - public static readonly TimeSpan NdmDeliveryReceipt = TimeSpan.FromSeconds(3); - public static readonly TimeSpan NdmDepositApproval = TimeSpan.FromSeconds(3); - public static readonly TimeSpan NdmEthRequest = TimeSpan.FromSeconds(3); - public static readonly TimeSpan NdmDataRequestResult = TimeSpan.FromSeconds(3); public static readonly TimeSpan Handshake = TimeSpan.FromSeconds(3); public static readonly TimeSpan Disconnection = TimeSpan.FromSeconds(1); } diff --git a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs index ad4b2cbd1dae..a76405c19f04 100644 --- a/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs +++ b/src/Nethermind/Nethermind.Network/TrustedNodes/TrustedNodesManager.cs @@ -1,192 +1,131 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Config; +using Nethermind.Core.Crypto; +using Nethermind.Logging; +using Nethermind.Stats.Model; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Sockets; using System.Runtime.CompilerServices; -using System.Text.Json; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; -using Nethermind.Config; -using Nethermind.Core.Crypto; -using Nethermind.Logging; -using Nethermind.Stats.Model; -namespace Nethermind.Network +namespace Nethermind.Network; + +public class TrustedNodesManager(string trustedNodesPath, ILogManager logManager) + : NodesManager(trustedNodesPath, logManager.GetClassLogger()), ITrustedNodesManager { - public class TrustedNodesManager : ITrustedNodesManager + private readonly Channel _nodeChannel = Channel.CreateBounded( + new BoundedChannelOptions(1 << 16) // capacity of 2^16 = 65536 { - private ConcurrentDictionary _nodes = new(); - private readonly string _trustedNodesPath; - private readonly ILogger _logger; - private readonly Channel _nodeChannel = Channel.CreateBounded( - new BoundedChannelOptions(1 << 16) // capacity of 2^16 = 65536 - { - // "Wait" to have writers wait until there is space. - FullMode = BoundedChannelFullMode.Wait - }); + // "Wait" to have writers wait until there is space. + FullMode = BoundedChannelFullMode.Wait + }); - public TrustedNodesManager(string trustedNodesPath, ILogManager logManager) - { - _trustedNodesPath = trustedNodesPath.GetApplicationResourcePath(); - _logger = logManager.GetClassLogger(); - } - - public IEnumerable Nodes => _nodes.Values; + public IEnumerable Nodes => _nodes.Values; - public async Task InitAsync() - { - if (!File.Exists(_trustedNodesPath)) - { - if (_logger.IsDebug) _logger.Debug($"Trusted nodes file not found at: {_trustedNodesPath}"); - return; - } - - var nodes = new ConcurrentDictionary(); + public async Task InitAsync() + { + ConcurrentDictionary nodes = await ParseNodes("trusted-nodes.json"); - await foreach (string line in File.ReadLinesAsync(_trustedNodesPath)) - { - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - try - { - NetworkNode node = new NetworkNode(line); - nodes.TryAdd(node.NodeId, node); - } - catch (Exception ex) when (ex is ArgumentException or SocketException) - { - if (_logger.IsError) - { - _logger.Error($"Failed to parse '{line}' as a trusted node.", ex); - } - } - } + LogNodeList("Trusted nodes", nodes); - if (_logger.IsInfo) - { - _logger.Info($"Loaded {nodes.Count} trusted nodes from: {Path.GetFullPath(_trustedNodesPath)}"); - } - if (_logger.IsDebug && !nodes.IsEmpty) - { - _logger.Debug("Trusted nodes:\n" + string.Join(Environment.NewLine, nodes.Values.Select(n => n.ToString()))); - } + _nodes = nodes; + } - _nodes = nodes; + public async IAsyncEnumerable DiscoverNodes([EnumeratorCancellation] CancellationToken cancellationToken) + { + // yield existing nodes. + foreach (NetworkNode netNode in _nodes.Values) + { + cancellationToken.ThrowIfCancellationRequested(); + yield return new Node(netNode) { IsTrusted = true }; } - - // ---- INodeSource requirement: IAsyncEnumerable ---- - public async IAsyncEnumerable DiscoverNodes([EnumeratorCancellation] CancellationToken cancellationToken) + // yield new nodes as they are added via the channel + await foreach (Node node in _nodeChannel.Reader.ReadAllAsync(cancellationToken)) { - // yield existing nodes. - foreach (NetworkNode netNode in _nodes.Values) - { - cancellationToken.ThrowIfCancellationRequested(); - yield return new Node(netNode) { IsTrusted = true }; - } - - // yield new nodes as they are added via the channel - await foreach (Node node in _nodeChannel.Reader.ReadAllAsync(cancellationToken)) - { - yield return node; - } + yield return node; } + } - public async Task AddAsync(Enode enode, bool updateFile = true) + public async Task AddAsync(Enode enode, bool updateFile = true) + { + NetworkNode networkNode = new(enode); + if (!_nodes.TryAdd(networkNode.NodeId, networkNode)) { - NetworkNode networkNode = new NetworkNode(enode); - if (!_nodes.TryAdd(networkNode.NodeId, networkNode)) - { - if (_logger.IsInfo) - { - _logger.Info($"Trusted node was already added: {enode}"); - } - return false; - } - if (_logger.IsInfo) { - _logger.Info($"Trusted node added: {enode}"); - } - - // Publish the newly added node to the channel so DiscoverNodes will yield it. - Node newNode = new Node(networkNode) { IsTrusted = true }; - await _nodeChannel.Writer.WriteAsync(newNode); - - if (updateFile) - { - await SaveFileAsync(); + _logger.Info($"Trusted node was already added: {enode}"); } - return true; + return false; } - public async Task RemoveAsync(Enode enode, bool updateFile = true) + if (_logger.IsInfo) { - NetworkNode networkNode = new(enode.ToString()); - if (!_nodes.TryRemove(networkNode.NodeId, out _)) - { - if (_logger.IsInfo) - { - _logger.Info($"Trusted node was not found: {enode}"); - } - return false; - } - - if (_logger.IsInfo) - { - _logger.Info($"Trusted node was removed: {enode}"); - } - - if (updateFile) - { - await SaveFileAsync(); - } + _logger.Info($"Trusted node added: {enode}"); + } - OnNodeRemoved(networkNode); + // Publish the newly added node to the channel so DiscoverNodes will yield it. + Node newNode = new(networkNode) { IsTrusted = true }; + await _nodeChannel.Writer.WriteAsync(newNode); - return true; + if (updateFile) + { + await SaveFileAsync(); } + return true; + } - public bool IsTrusted(Enode enode) + public async Task RemoveAsync(Enode enode, bool updateFile = true) + { + NetworkNode networkNode = new(enode.ToString()); + if (!_nodes.TryRemove(networkNode.NodeId, out _)) { - if (enode.PublicKey is null) - { - return false; - } - if (_nodes.TryGetValue(enode.PublicKey, out NetworkNode storedNode)) + if (_logger.IsInfo) { - // Compare not only the public key, but also the host and port. - return storedNode.Host == enode.HostIp?.ToString() && storedNode.Port == enode.Port; + _logger.Info($"Trusted node was not found: {enode}"); } return false; } + if (_logger.IsInfo) + { + _logger.Info($"Trusted node was removed: {enode}"); + } - - // ---- INodeSource requirement: event EventHandler ---- - public event EventHandler? NodeRemoved; - - private void OnNodeRemoved(NetworkNode node) + if (updateFile) { - Node nodeForEvent = new Node(node); - NodeRemoved?.Invoke(this, new NodeEventArgs(nodeForEvent)); + await SaveFileAsync(); } + OnNodeRemoved(networkNode); - private async Task SaveFileAsync() + return true; + } + + public bool IsTrusted(Enode enode) + { + if (enode.PublicKey is null) { - IEnumerable enodes = _nodes.Values.Select(n => n.ToString()); - using (FileStream stream = File.Create(_trustedNodesPath)) - { - await JsonSerializer.SerializeAsync(stream, enodes, new JsonSerializerOptions { WriteIndented = true }); - } + return false; + } + if (_nodes.TryGetValue(enode.PublicKey, out NetworkNode storedNode)) + { + // Compare not only the public key, but also the host and port. + return storedNode.Host == enode.HostIp?.ToString() && storedNode.Port == enode.Port; } + return false; + } + + public event EventHandler? NodeRemoved; + + private void OnNodeRemoved(NetworkNode node) + { + Node nodeForEvent = new(node); + NodeRemoved?.Invoke(this, new NodeEventArgs(nodeForEvent)); } } diff --git a/src/Nethermind/Nethermind.Network/TrustedNodes/trusted-nodes.json b/src/Nethermind/Nethermind.Network/TrustedNodes/trusted-nodes.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/TrustedNodes/trusted-nodes.json @@ -0,0 +1 @@ +[] diff --git a/src/Nethermind/Nethermind.Optimism.Test/CL/BatchDecoderTests.cs b/src/Nethermind/Nethermind.Optimism.Test/CL/BatchDecoderTests.cs index 012c94873776..6c9e36734f09 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/CL/DepositTransactionBuilderTest.cs b/src/Nethermind/Nethermind.Optimism.Test/CL/DepositTransactionBuilderTest.cs index 84d65490165e..ab4fc23545e6 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/CL/DepositTransactionBuilderTest.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/CL/DepositTransactionBuilderTest.cs @@ -9,7 +9,6 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Crypto; using Nethermind.Core.Test.Builders; -using Nethermind.Facade.Eth; using Nethermind.Optimism.CL; using Nethermind.Optimism.CL.Derivation; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Optimism.Test/CL/L1BlockInfoBuilderTests.cs b/src/Nethermind/Nethermind.Optimism.Test/CL/L1BlockInfoBuilderTests.cs index 3b061228d617..210383c4e636 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/CL/L1BlockInfoBuilderTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/CL/L1BlockInfoBuilderTests.cs @@ -8,7 +8,6 @@ using Nethermind.Core.Test.Builders; using Nethermind.Optimism.CL; using Nethermind.Optimism.CL.Derivation; -using Nethermind.Specs; using NUnit.Framework; namespace Nethermind.Optimism.Test.CL; diff --git a/src/Nethermind/Nethermind.Optimism.Test/CL/SystemConfigDeriverTests.cs b/src/Nethermind/Nethermind.Optimism.Test/CL/SystemConfigDeriverTests.cs index ed0564c00ea7..0ef5908b1183 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/CL/SystemConfigDeriverTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/CL/SystemConfigDeriverTests.cs @@ -9,7 +9,6 @@ using Nethermind.Evm; using Nethermind.Int256; using Nethermind.JsonRpc.Data; -using Nethermind.Optimism.CL; using Nethermind.Optimism.CL.Derivation; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Optimism.Test/Create2DeployerContractRewriterTests.cs b/src/Nethermind/Nethermind.Optimism.Test/Create2DeployerContractRewriterTests.cs index d33e1c306b6b..c4b8c95b0172 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/Create2DeployerContractRewriterTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/Create2DeployerContractRewriterTests.cs @@ -8,7 +8,6 @@ using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.Evm.State; -using Nethermind.State; using NUnit.Framework; namespace Nethermind.Optimism.Test; diff --git a/src/Nethermind/Nethermind.Optimism.Test/EIP1559ParametersTests.cs b/src/Nethermind/Nethermind.Optimism.Test/EIP1559ParametersTests.cs index 877200780245..1c5bc8b8640a 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/EIP1559ParametersTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/EIP1559ParametersTests.cs @@ -5,7 +5,6 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; using NUnit.Framework; -using FluentAssertions; using System; namespace Nethermind.Optimism.Test; @@ -15,6 +14,7 @@ public class EIP1559ParametersTests { private static IEnumerable<(string hexString, EIP1559Parameters expected)> DecodeBlockHeaderParametersCases() { + // V0 yield return ("0x000000000000000000", new(0, 0, 0)); yield return ("0x000000000100000000", new(0, 1, 0)); yield return ("0x0000000001000001bc", new(0, 1, 444)); @@ -22,6 +22,15 @@ public class EIP1559ParametersTests yield return ("0x00ffffffff00000000", new(0, UInt32.MaxValue, 0)); yield return ("0x00ffffffff000001bc", new(0, UInt32.MaxValue, 444)); yield return ("0x00ffffffffffffffff", new(0, UInt32.MaxValue, UInt32.MaxValue)); + + // V1 + yield return ("0x0100000000000000000000000000000000", new(1, 0, 0, 0)); + yield return ("0x0100000001000000000000000000000001", new(1, 1, 0, 1)); + yield return ("0x0100000001000000010000000000000001", new(1, 1, 1, 1)); + yield return ("0x01000000010000000100000000000001bc", new(1, 1, 1, 444)); + yield return ("0x01000000010000000100000000ffffffff", new(1, 1, 1, UInt32.MaxValue)); + yield return ("0x010000000100000001ffffffffffffffff", new(1, 1, 1, UInt64.MaxValue)); + yield return ("0x01ffffffffffffffffffffffffffffffff", new(1, UInt32.MaxValue, UInt32.MaxValue, UInt64.MaxValue)); } [TestCaseSource(nameof(DecodeBlockHeaderParametersCases))] public void DecodeBlockHeaderParameters((string HexString, EIP1559Parameters Expected) testCase) @@ -29,8 +38,10 @@ public void DecodeBlockHeaderParameters((string HexString, EIP1559Parameters Exp var bytes = Bytes.FromHexString(testCase.HexString); var blockHeader = Build.A.BlockHeader.WithExtraData(bytes).TestObject; - blockHeader.TryDecodeEIP1559Parameters(out EIP1559Parameters decoded, out _); - - decoded.Should().Be(testCase.Expected); + using (Assert.EnterMultipleScope()) + { + Assert.That(blockHeader.TryDecodeEIP1559Parameters(out EIP1559Parameters decoded, out _), Is.True); + Assert.That(decoded, Is.EqualTo(testCase.Expected)); + } } } diff --git a/src/Nethermind/Nethermind.Optimism.Test/Fork.cs b/src/Nethermind/Nethermind.Optimism.Test/Fork.cs new file mode 100644 index 000000000000..91efc95866d9 --- /dev/null +++ b/src/Nethermind/Nethermind.Optimism.Test/Fork.cs @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Nethermind.Optimism.Test; + +public record Fork(string Name, ulong Timestamp) +{ + // Aggregates all fork timestamps and names + public static readonly FrozenDictionary At = typeof(Spec) + .GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(f => + f is { IsLiteral: true, IsInitOnly: false } && + f.FieldType == typeof(ulong) && + f.Name.EndsWith("timestamp", StringComparison.OrdinalIgnoreCase) + ) + .Select(f => new Fork(f.Name[..^("timestamp".Length)], (ulong)f.GetRawConstantValue()!)) + .ToFrozenDictionary(f => f.Timestamp); + + public static readonly IReadOnlyList AllAndNextToGenesis = At.Values + .Select(f => f.Timestamp == Spec.GenesisTimestamp ? new("Genesis + 1", f.Timestamp + 1) : f) + .ToArray(); + + public override string ToString() => Name; +} diff --git a/src/Nethermind/Nethermind.Optimism.Test/ForkInfoTests.cs b/src/Nethermind/Nethermind.Optimism.Test/ForkInfoTests.cs new file mode 100644 index 000000000000..4daa3d1c09e3 --- /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 4cdd5dc0f59c..97b69e3b902e 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.Test/OptimismBaseFeeCalculatorTests.cs b/src/Nethermind/Nethermind.Optimism.Test/OptimismBaseFeeCalculatorTests.cs index 8672907ee05e..15eff2fad062 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/OptimismBaseFeeCalculatorTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/OptimismBaseFeeCalculatorTests.cs @@ -31,11 +31,11 @@ public void CalculatesBaseFee_AfterHolocene_UsingExtraDataParameters(long gasUse { IsEip1559Enabled = true, IsOpHoloceneEnabled = true, - BaseFeeCalculator = new OptimismBaseFeeCalculator(HoloceneTimestamp, new DefaultBaseFeeCalculator()) + BaseFeeCalculator = new OptimismBaseFeeCalculator(HoloceneTimestamp, null, new DefaultBaseFeeCalculator()) }; - var extraData = new byte[EIP1559Parameters.ByteLength]; var parameters = new EIP1559Parameters(0, denominator, elasticity); + var extraData = new byte[parameters.ByteLength]; parameters.WriteTo(extraData); BlockHeader blockHeader = Build.A.BlockHeader @@ -50,4 +50,60 @@ public void CalculatesBaseFee_AfterHolocene_UsingExtraDataParameters(long gasUse actualBaseFee.Should().Be((UInt256)expectedBaseFee); } + + private static class JovianTest + { + public const long GasLimit = 30_000_000; + public const uint Denominator = 50; + public const uint Elasticity = 3; + public const long GasTarget = GasLimit / Elasticity; + } + + /// + /// Tests sourced from + /// + [TestCase(1L, JovianTest.GasTarget - 1_000_000, 0L, Spec.HoloceneTimeStamp, 1_000_000_000, 1L)] + [TestCase(1L, JovianTest.GasTarget, 0L, Spec.JovianTimeStamp, 1_000_000_000, 1_000_000_000)] + [TestCase(1L, JovianTest.GasTarget + 1_000_000, 0L, Spec.JovianTimeStamp, 1_000_000_000, 1_000_000_000)] + [TestCase(2_000_000_000, JovianTest.GasTarget + 10_000_000, 0L, Spec.JovianTimeStamp, 1_000_000_000, 2_040_000_000)] + [TestCase(1, JovianTest.GasTarget - 1_000_000, 0L, Spec.JovianTimeStamp, 1_000_000_000, 1_000_000_000)] + [TestCase(2_097_152, JovianTest.GasTarget - 1_000_000, 0L, Spec.JovianTimeStamp, 2_000_000, 2_092_958)] + [TestCase(10_000, JovianTest.GasTarget - 1, 0L, Spec.JovianTimeStamp, 10_000, 10_000)] + [TestCase(10_000, JovianTest.GasTarget + 1, 0L, Spec.JovianTimeStamp, 10_000, 10_000 + 1)] + [TestCase(10_000, JovianTest.GasTarget, JovianTest.GasLimit, Spec.HoloceneTimeStamp, 1_000_000, 10_000)] + [TestCase(10_000, JovianTest.GasTarget, JovianTest.GasTarget + 1, Spec.JovianTimeStamp, 10_000, 10_000 + 1)] + [TestCase(2_000_000_000, JovianTest.GasTarget, JovianTest.GasTarget + 10_000_000, Spec.JovianTimeStamp, 1_000_000_000, 2_040_000_000)] + public void CalculatesBaseFee_AfterJovian_Using( + long baseFee, long gasUsed, long blobGasUsed, ulong timestamp, + long minBaseFee, long expectedBaseFee + ) + { + IReleaseSpec releaseSpec = new ReleaseSpec + { + IsEip1559Enabled = true, + IsOpHoloceneEnabled = timestamp >= Spec.HoloceneTimeStamp, + IsOpJovianEnabled = timestamp >= Spec.JovianTimeStamp, + BaseFeeCalculator = new OptimismBaseFeeCalculator(Spec.HoloceneTimeStamp, Spec.JovianTimeStamp, new DefaultBaseFeeCalculator()) + }; + + EIP1559Parameters parameters = releaseSpec.IsOpJovianEnabled + ? new(1, JovianTest.Denominator, JovianTest.Elasticity, (ulong)minBaseFee) + : new(0, JovianTest.Denominator, JovianTest.Elasticity); + + var extraData = new byte[parameters.ByteLength]; + parameters.WriteTo(extraData); + + BlockHeader blockHeader = Build.A.BlockHeader + .WithGasLimit(JovianTest.GasLimit) + .WithGasUsed(gasUsed) + .WithBlobGasUsed((ulong)blobGasUsed) + .WithBaseFee((UInt256)baseFee) + .WithTimestamp((ulong)timestamp) + .WithExtraData(extraData) + .TestObject; + + UInt256 actualBaseFee = BaseFeeCalculator.Calculate(blockHeader, releaseSpec); + + actualBaseFee.Should().Be((UInt256)expectedBaseFee); + } } diff --git a/src/Nethermind/Nethermind.Optimism.Test/OptimismBlockValidatorTests.cs b/src/Nethermind/Nethermind.Optimism.Test/OptimismBlockValidatorTests.cs index 14a0f1d91867..2baf159d5cb6 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/OptimismBlockValidatorTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/OptimismBlockValidatorTests.cs @@ -3,125 +3,144 @@ using System; using System.Collections.Generic; -using FluentAssertions; using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Logging; -using NSubstitute; using NUnit.Framework; namespace Nethermind.Optimism.Test; -public class OptimismBlockValidatorTests +[Parallelizable(ParallelScope.All)] +[TestFixtureSource(typeof(Fork), nameof(Fork.AllAndNextToGenesis))] +public class OptimismBlockValidatorTests(Fork fork) { + private readonly ulong _timestamp = fork.Timestamp; + + private Hash256? GetWithdrawalsRoot() => _timestamp switch + { + >= Spec.IsthmusTimeStamp => TestWithdrawalsRoot, + >= Spec.CanyonTimestamp => Keccak.EmptyTreeHash, + _ => null + }; + private static readonly Hash256 TestWithdrawalsRoot = new("0x1234567890123456789012345678901234567890123456789012345678901234"); + private (BlockHeader parentHeader, Block header) BuildBlock(Action? postBuild = null) + { + var parentBlock = Build.A.BlockHeader.TestObject; + var builder = Build.A.Block + .WithWithdrawals(_timestamp >= Spec.IsthmusTimeStamp ? [] : null) + .WithHeader(Build.A.BlockHeader + .WithParent(parentBlock) + .WithTimestamp(_timestamp) + .WithBlobGasUsed(0) + .WithWithdrawalsRoot(GetWithdrawalsRoot()) + .TestObject); + + postBuild?.Invoke(builder); + + return (parentBlock, builder.TestObject); + } + private static IEnumerable WithdrawalsRootTestCases() { // Pre-Canyon: WithdrawalsRoot should be null - yield return new TestCaseData(Spec.CanyonTimestamp - 1, null, true) - .SetName("Pre-Canyon: Valid null withdrawals root"); - yield return new TestCaseData(Spec.CanyonTimestamp - 1, TestWithdrawalsRoot, false) - .SetName("Pre-Canyon: Invalid non-null withdrawals root"); + yield return new TestCaseData(null, Valid.Before(Spec.CanyonTimestamp)) + .SetName("Null withdrawals root"); // Canyon: WithdrawalsRoot should be Keccak.EmptyTreeHash - yield return new TestCaseData(Spec.CanyonTimestamp, Keccak.EmptyTreeHash, true) - .SetName("Canyon: Valid empty tree hash withdrawals root"); - yield return new TestCaseData(Spec.CanyonTimestamp, null, false) - .SetName("Canyon: Invalid null withdrawals root"); - yield return new TestCaseData(Spec.CanyonTimestamp, TestWithdrawalsRoot, false) - .SetName("Canyon: Invalid non-empty tree hash withdrawals root"); + yield return new TestCaseData(Keccak.EmptyTreeHash, Valid.Between(Spec.CanyonTimestamp, Spec.IsthmusTimeStamp)) + .SetName("Empty tree hash withdrawals root"); // Isthmus: WithdrawalsRoot should be 32 bytes non-null - yield return new TestCaseData(Spec.IsthmusTimeStamp, TestWithdrawalsRoot, true) - .SetName("Isthmus: Valid non-null withdrawals root"); - yield return new TestCaseData(Spec.IsthmusTimeStamp, null, false) - .SetName("Isthmus: Invalid null withdrawals root"); - yield return new TestCaseData(Spec.IsthmusTimeStamp, Keccak.EmptyTreeHash, false) - .SetName("Isthmus: Invalid withdrawals root of an empty tree"); + yield return new TestCaseData(TestWithdrawalsRoot, Valid.Since(Spec.IsthmusTimeStamp)) + .SetName("Non-null withdrawals root"); } [TestCaseSource(nameof(WithdrawalsRootTestCases))] - public void ValidateSuggestedBlock_ValidateWithdrawalsRoot(ulong timestamp, Hash256? withdrawalsRoot, bool isValid) + public void ValidateSuggestedBlock_ValidateWithdrawalsRoot(Hash256? withdrawalsRoot, Valid isValid) { - var specProvider = Substitute.For(); - var specHelper = Substitute.For(); - specHelper.IsIsthmus(Arg.Any()).Returns(timestamp >= Spec.IsthmusTimeStamp); - specHelper.IsCanyon(Arg.Any()).Returns(timestamp >= Spec.CanyonTimestamp); - - var parentBlock = Build.A.BlockHeader.TestObject; - var block = Build.A.Block - .WithWithdrawals(timestamp >= Spec.IsthmusTimeStamp ? [] : null) - .WithHeader(Build.A.BlockHeader - .WithParent(parentBlock) - .WithTimestamp(timestamp) - .WithWithdrawalsRoot(withdrawalsRoot) - .TestObject) - .TestObject; + (BlockHeader parentHeader, Block block) = BuildBlock(b => b + .WithWithdrawalsRoot(withdrawalsRoot) + ); var validator = new OptimismBlockValidator( Always.Valid, Always.Valid, Always.Valid, - specProvider, - specHelper, + Spec.BuildFor(block.Header), + Spec.Instance, TestLogManager.Instance); - var result = validator.ValidateSuggestedBlock(block, parentBlock, out string? error); - - result.Should().Be(isValid); - if (!isValid) - { - error.Should().NotBeNull(); - } + Assert.That( + validator.ValidateSuggestedBlock(block, parentHeader, out string? error), + Is.EqualTo(isValid.On(_timestamp)), + () => error!); } private static IEnumerable WithdrawalsListTestCases() { - yield return new TestCaseData(Array.Empty(), true) - .SetName("Valid empty withdrawals list"); - yield return new TestCaseData(null, false) - .SetName("Invalid null withdrawals list"); - yield return new TestCaseData(new[] { TestItem.WithdrawalA_1Eth }, false) - .SetName("Invalid non-empty withdrawals list"); + yield return new TestCaseData(Array.Empty(), Valid.Always) + .SetName("Empty withdrawals list"); + yield return new TestCaseData(null, Valid.Before(Spec.IsthmusTimeStamp)) + .SetName("Null withdrawals list"); + yield return new TestCaseData(new[] { TestItem.WithdrawalA_1Eth }, Valid.Before(Spec.IsthmusTimeStamp)) + .SetName("Non-empty withdrawals list"); } [TestCaseSource(nameof(WithdrawalsListTestCases))] - public void ValidateSuggestedBlock_ValidateWithdrawalsList_PostIsthmus(Withdrawal[]? withdrawals, bool isValid) + public void ValidateSuggestedBlock_ValidateWithdrawalsList(Withdrawal[]? withdrawals, Valid isValid) { - var specProvider = Substitute.For(); - var specHelper = Substitute.For(); - specHelper.IsIsthmus(Arg.Any()).Returns(true); - specHelper.IsCanyon(Arg.Any()).Returns(true); - - var parentBlock = Build.A.BlockHeader.TestObject; - var block = Build.A.Block + (BlockHeader parentHeader, Block block) = BuildBlock(b => b .WithWithdrawals(withdrawals) - .WithHeader(Build.A.BlockHeader - .WithParent(parentBlock) - .WithTimestamp(Spec.IsthmusTimeStamp) - .WithWithdrawalsRoot(TestWithdrawalsRoot) - .TestObject) - .TestObject; + .WithWithdrawalsRoot(GetWithdrawalsRoot()) + ); var validator = new OptimismBlockValidator( Always.Valid, Always.Valid, Always.Valid, - specProvider, - specHelper, + Spec.BuildFor(block.Header), + Spec.Instance, TestLogManager.Instance); - var result = validator.ValidateSuggestedBlock(block, parentBlock, out string? error); + Assert.That( + validator.ValidateSuggestedBlock(block, parentHeader, out string? error), + Is.EqualTo(isValid.On(_timestamp)), + () => error!); + } + + private static IEnumerable BlobGasUsedTestCases() + { + yield return new TestCaseData(null, Valid.Since(Spec.EcotoneTimestamp)) + .SetName("Null blob gas used"); + yield return new TestCaseData(0, Valid.Always) + .SetName("Zero blob gas used"); + yield return new TestCaseData(10_000, Valid.Since(Spec.EcotoneTimestamp)) + .SetName("Positive blob gas used"); + } + + [TestCaseSource(nameof(BlobGasUsedTestCases))] + public void ValidateSuggestedBlock_ValidatesBlobGasUsed(int? blobGasUsed, Valid isValid) + { + (BlockHeader parentHeader, Block block) = BuildBlock(b => b + .WithBlobGasUsed((ulong?)blobGasUsed) + ); + + var validator = new OptimismBlockValidator( + Always.Valid, + Always.Valid, + Always.Valid, + Spec.BuildFor(block.Header), + Spec.Instance, + TestLogManager.Instance); - result.Should().Be(isValid); - if (!isValid) - { - error.Should().NotBeNull(); - } + Assert.That( + validator.ValidateSuggestedBlock(block, parentHeader, out string? error), + Is.EqualTo(isValid.On(_timestamp)), + () => error!); } } diff --git a/src/Nethermind/Nethermind.Optimism.Test/OptimismCostHelperTests.cs b/src/Nethermind/Nethermind.Optimism.Test/OptimismCostHelperTests.cs new file mode 100644 index 000000000000..4c032a4b942a --- /dev/null +++ b/src/Nethermind/Nethermind.Optimism.Test/OptimismCostHelperTests.cs @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; +using Nethermind.Facade.Eth.RpcTransaction; +using Nethermind.Optimism.Rpc; +using Nethermind.Serialization.Rlp; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Optimism.Test; + +public class OptimismCostHelperTests +{ + [SetUp] + public void Setup() + { + TransactionForRpc.RegisterTransactionType(); + TxDecoder.Instance.RegisterDecoder(new OptimismTxDecoder()); + TxDecoder.Instance.RegisterDecoder(new OptimismLegacyTxDecoder()); + } + + // Taken from Jovian alpha devnet + private static IEnumerable>> DaFootprintTestCases() + { + yield return new( + () => Rlp.Decode(Bytes.FromHexString("f9038af90275a02877b2266e1289b448dd45149b03f948a43c5549e4afdd61d10008dbdd839714a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944200000000000000000000000000000000000011a084b8e1cbd2b4d12ccbe326a8cc76147bade47c03e0a303d975debdba6649c8b8a065f0c9e84ef06aef7ebbc695c789e07136cc9dd9394c7c2dc0dc7a813350a4a4a0a937269931d3b4de64b91cd07c3d0d2af0f392c219a975877836b23152986caab901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080830948a1840393870082b48a84690e77f69101000000fa000000060000000000000000a0e5b3cf85d39b4253b039b9c7078b7f8a106383340d6bee4968d09b349931714b880000000000000000840bbf437ba007e4ba57a118eb434e47a55ef1fafa1e809bf6eb9aa56e8e027542db1b2150bc8080a081040b2fcc3753cbdf7d4d8f5375f294400ed1024603cfaced27c077072e019ea0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855f9010db9010a7ef90106a0f53166e2aa01da020685f0cad891915e9d170ed5660d31fa32654ec566bfea5994deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8b23db6be2b00000558000c3c9d000000000000000400000000690e77a4000000000092371500000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001b1bf0a4b07d08b24382f0c0375a493879ff207e15df84abc5ee70261d962e9ac0000000000000000000000001fa33f4830c6939a7e48550c0c2b6b779293a3e305f5e10000000000000001f40190c0c0")) + ) + { TestName = "Block #608417, no non-deposit txs" }; + + yield return new( + () => Rlp.Decode(Bytes.FromHexString("f90400f90278a043b245d5a45f3c8a47f4708535dff1569ab4900a0141e9762c27980d11dde5e1a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944200000000000000000000000000000000000011a0d26432a00e0362a3905ca57fb967b649a865dd56bc57885097743162b7adf96ca0b6d7f2049b1d82cb5f3189fb3fc64e0fd37029c0e30ca1f9d130efe5089b1f0ea094b34c410fff6460c21d495dd945214d28a712e0f7e23e6ba323679eb6d6568cb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080830948a3840393870083019f3084690e77fa9101000000fa000000060000000000000000a0a20f72a960e203f644c258f6471ffc187dc94d70b10304039915a18fd2163cd3880000000000000000840ba777a5a007e4ba57a118eb434e47a55ef1fafa1e809bf6eb9aa56e8e027542db1b2150bc829c4080a0800f146027284dae46a5a6cf14a9120084791267e67fe088dea7456a93d14a4ba0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855f90180b9010a7ef90106a00b2bd82bd3369b88c7e3c402acb0f7ed79e9fc00035b34417409ca736d7a7cc494deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8b23db6be2b00000558000c3c9d000000000000000000000000690e77b000000000009237160000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000118d0bf5ef2941afc565c5c8c1dcaf4f3c3dfd3bcbb30e4ed7763bd0c6b21055e0000000000000000000000001fa33f4830c6939a7e48550c0c2b6b779293a3e305f5e10000000000000001f40190b87102f86e84190a37a680843b9aca0084474e1447830f4240808093600661000d60003960066000f361beef600055c080a0c4b911fe7306728641bdf90b398bc596d01c4677486c6ce5cb862bb145b3e251a03b31f488019dae760ab73708d41c8da8e2735e04b3d2fbedd727fcf4ae37cd8cc0c0")) + ) + { TestName = "Block #608419" }; + + yield return new( + () => Rlp.Decode(Bytes.FromHexString("f90463f90278a0d139f43e1ef1dbd1dc158ece6dcee7ec4b5a0e4ee60140d3c96c5cc1d3508069a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944200000000000000000000000000000000000011a0835d1756d995d0b561ec2fdc109112d700dd57d920023abb5a5efbc7e63ac5cba030f6c21d4a1a7b67a1283eefc070523de4af12004abab250a0a1222229f9eac1a0bd1344a3af8432513780576c8d7a39871fd9c4d88cf53c4e7b98c10951bcbf85b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080830948a4840393870083018dc084690e77fc9101000000fa000000060000000000000000a0a20f72a960e203f644c258f6471ffc187dc94d70b10304039915a18fd2163cd3880000000000000000840b9ba90ca007e4ba57a118eb434e47a55ef1fafa1e809bf6eb9aa56e8e027542db1b2150bc82d7a080a0800f146027284dae46a5a6cf14a9120084791267e67fe088dea7456a93d14a4ba0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855f901e3b9010a7ef90106a0d8e29b7d16f95249d13aac79b119ce6726bd2c8b05ee70323ada20b86d7441ff94deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8b23db6be2b00000558000c3c9d000000000000000100000000690e77b000000000009237160000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000118d0bf5ef2941afc565c5c8c1dcaf4f3c3dfd3bcbb30e4ed7763bd0c6b21055e0000000000000000000000001fa33f4830c6939a7e48550c0c2b6b779293a3e305f5e10000000000000001f40190b8d404f8d184190a37a601843b9aca0084474241a5830124f89450c96a7e536f89417891ac992b248d2e883178408080c0f860f85e84190a37a694b56379fb2a6cf01281b7f749b3aa651d9098f0dd0280a058294c13e08193e5141709e453fdccee7c4b2d2a35c88060b4d60d61e2b1100ea0731c9bb8d3bacca7d60c1393c73b667a365bc95986245325b728ad2e3590bc1101a0bcbf7bdf67b5ea32a8e6a6ccbd6878c6d8c71d90fe1edf2da3bce16edc1129caa03329ec62005b049465b78818084a0beb7da56800892e5a508ed2f1bb662e09cdc0c0")) + ) + { TestName = "Block #608420" }; + + yield return new( + () => Rlp.Decode(Bytes.FromHexString("f90c06f90279a0347f8f0939dd3332436fb617a37ce7cecaa31dc14335181104587fd44da77200a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944200000000000000000000000000000000000011a038d7d1a9432f41681a1b122e5fae9f511cf1de739bc5875f2c61a53560dacf97a07903266fe205f14c8222192dc8a816f6c626acc047133e818a22d3d65b4aa55ea0c8e7da2d8bfb098323abac42f1f3550dca8b983cdf70bdcc2cde8f83f5aeffd6b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080830948a58403938700830245fc84690e77fe9101000000fa000000060000000000000000a0a20f72a960e203f644c258f6471ffc187dc94d70b10304039915a18fd2163cd3880000000000000000840b8fe50ea007e4ba57a118eb434e47a55ef1fafa1e809bf6eb9aa56e8e027542db1b2150bc830b0f4080a0800f146027284dae46a5a6cf14a9120084791267e67fe088dea7456a93d14a4ba0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855f90985b9010a7ef90106a05099eb0104027af3672c4f59bcefdf80a951453ecc705aa829be99948b4637f294deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8b23db6be2b00000558000c3c9d000000000000000200000000690e77b000000000009237160000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000118d0bf5ef2941afc565c5c8c1dcaf4f3c3dfd3bcbb30e4ed7763bd0c6b21055e0000000000000000000000001fa33f4830c6939a7e48550c0c2b6b779293a3e305f5e10000000000000001f40190b9087502f9087184190a37a603843b9aca00844736730c8301955194000000000000000000000000000000000000000480b908007aa771b811222b967cbdf61cd4cab7f29b6afd39d5d5f706a28f24087573ba597ae05ba80a7825e05fa1305b5099b974daf0402f41f2067ad63028555fdad76ec1080651374ae5968c37ce16b0e96bad54c479f88329d3075f21fe51a73bd585aa8cb23938894e5c77f40e1b99de67e3a7a933e6a385cb1d5d9395707dc0549394533d3e581f47cea1f28a2e645dbefd300e2011a30b6f99c704ac919d4739f9dba32ee0576dd0966541dbada0421bdacf4de335dd42511e78f817159a10626f297d3315b097f0f48789ae5450927cdbbf15350f94fe82518ed5979aca29b858cdd285eb5736fdaa02079cf9c55b24492088fbbb0df30fbcd1da98a9473b70032d5864ff9f5a4aa70c329aa17171f007e27d08fb583d894ebefbb84551bd6e6094da77a52f2f3fb0494fa58e1a5583d100a55859cbe0a39f40200c913a46e0f789ccbdd7ec139e79f273a3071a85606606075f53038de1cde2a8fb018fde72c0c319e4ae0486fefcb6f2a4f3c5363304a5236d82f515c8c4c35142dab29b3bdc020b39b9aa9d101bd84d1620b3228f977fb1ff5cec2fc9022f3beb760565eb35919bac1e17b530499c05fa350e99b691880169f8854051cd0bd1cc4802613527d09ed1665cd7bc0335ead7e080357f7b97fe4a0c098a62d7ec634f8f4fbfca4b9778369c12fa734989b85332d519a50f673db4fe1792d6cf6252a5a60c91c994c1be11a53a7a9dc93fbfe4fb0c32921f55e158f267b00fd9ab7186d8028fca6df858fa015dd5b10ed5c0f34b5a604e82ff0ee6cf2ec1d4613b8d5ca22405a4c1e0b855e31b07c1e3b6d5bb11ec68a8ce39d8e99c61512fc7586b9dc748b7e8902eecbb44cb9668d8c4a2bb5fd9c6d1f8996aa903760926e3f6ac97f08dd9099dcb6db2b0cfec6b2b541c61b401942c42bdbb86d55ff2c069e1d8d0cfddc88cf95cb85e92f621d9c3a932daa777de8d70ddf096cabe95981396890ada4882bf0edc1666c9dc6486ca7d92a05f1e4be604c6cbd298656a8324656dc717c64d36f646b1733dbfe1741190a1dbc5aa6cff55db7a0699e084be3878e13f4408116bfa8c9fca5bda85b8b4ef04e52784cf385701b3c62398a71ca546789efbbbe5ff6f1ea2a13e8d158bf98769fe648c04f43ce472a4e027b7d3c525ff859a6b61fc63045672a090036d47fbd91e19886bb1999ae50c88ac240cc4641d3ad8878cd0c2b68860c11e863c8e0ffcd08384159eb1ce470d735a90ee09e22f265e288687beabe5d66a1d4219a079482fac4dd1b6e1c87b974c254130cd16f354cdd08d1f0bd1c31e8b53492d3d2856daabc64ffe048556b589a6bc7ad374a4ff9b93bfbf583c619999c861ec5eb62fcbb37519653cb641ea1cd7386e57981e3929ba73ad7ef7d2c52da130d7bd1123f899dbd6e8a5ee54def127a4ef66287f4ca0d8ebae807a5f347d8bf5e67b723f76e165fca9df2e91bbe3cc08822819c6781d4796464053181296e96acb100ab4ff3a0a4c731eb2b3e7c47f7b3580d0e451e1f0e7ca78b7bad43189967e9edefcb08ec4ed07eb0227653f926924d0ee45befa18ba5733ac3b8db632f80f775880be257d39b83c2184f006909beb707e6c7070ff04bc5b684be13f6b48bb3c1681c6ee9f17a2674f92551381c929dbfc4e7587cb259637dcdad85ee150a6a282a3e8be7f0ef684f38b2fe0d316233c99ee886940dfe85d872e92224159560865b6ee043ba5e73f20f23382ccafdd039dd46e79aaaa20913ae9a90bc87489e7e88e938a96ea685bb80668fce0549df4af6ef5a166101926cdbbb8ebb6e1d972b58989e36b6597ae047d900b9f958ac26fe12d8d18c2d2b0f6778349f723068499e6bb31c39aadab7e318babb7187c7b851fbfaff0b8bae72044e69c0a9cf52771d0d0951420127c248182792473b125a04df57ea3d017dd9ac573ba5a1085b34239d8fa993ef5292f3e66c7ef7be2048d69ea8f318955da52256d406f3280f3703488d343a248fcc1024225c0f3a93369443b693e6dcffe02b7b02d52e5a0c2222ff3290c00962669ebb5e9b801be0c021added34b4d981a5e4305a4bcb206089ffe5077bd722859f1ab23e2b660540f8b4bb086c1b9b2b7307fedd7f4c01184342469d863c849a652510bce7d2a3edba048bb9dccd0db1c74e6b95a079ae96222bce0ac32b6eb7ba0910b17d7587d89c624827969eab8f3a6e66ae81d8420bdb56e257e2b4013e57ac5552046be9fddd6c32f04ef6cb3a553dba90fc4b00a999c72c4c09fc0688b790ad4ee6c8fa15919ec67ceeab194d1c48a28c618b0bce4a521bba1b258d5be368f3c69f8f037e7d1bb4066f92272a2b0583afada2932e7a7b112d40176c33c3ed397d14438888ef858bca5b894ffef770cd60762618caa6853256267416848eaf3b2aba2caa89bc3928bbbe8923e83795c41e9ce4893f04a669fdeb71e2e5de1c16525547abc7a3d8644cd9de00f7f1a3fa120016720c523104dd659bb84b6849a0371c3fa6b5d651cb4d1114fcc7a7a91535322b9ba91c0bad0eef60dcadaded15bb92d179dd7fc80b1a05d457b4ca5d1be0a5c9536d2f562a89fc64fa63025687e70d95461663bfbbe6761b1fc9fbae2c29bda1eccb706d16177e070510254080f63c985fecf0f63eb9c18382d8ac36ffae949ac5eec8a6a26178c28ed0699a542a68e222133078b15179448df9a86347e2a64e752bddee9047c3d251acc5ee8be0184d8e1d57e15dfbff209b76fb261de2df3d2975f7c50152e84bd5710bbd3323e0fcfac2b5f20229a282ca1811ade19ab6b6cf29ae348b27040d946a0509b8bf02dfff98ff6706802c7b461c93e524f0ec07950e0e6cf19ceed24cc8ac2de191146dc344985dd5e09150a5bfc080a0be7b84dd6b461c8f1d630f54c6c05d961589ccf036455fc082e8896132c24e1da0247fad718515d3d8cbffa20c4bf23c997681fbed69b2df3ff346ea532c6adc86c0c0")) + ) + { TestName = "Block #608421" }; + } + + [TestCaseSource(nameof(DaFootprintTestCases))] + public void ComputeDaFootprint(Func blockFactory) + { + Address l1BlockAddr = Build.An.Address.TestObject; + var specHelper = Substitute.For(); + + Block block = blockFactory(); + var helper = new OptimismCostHelper(specHelper, l1BlockAddr); + + Assert.That((long)helper.ComputeDaFootprint(block), Is.EqualTo(block.BlobGasUsed)); + } +} diff --git a/src/Nethermind/Nethermind.Optimism.Test/OptimismGenesisLoaderTests.cs b/src/Nethermind/Nethermind.Optimism.Test/OptimismGenesisLoaderTests.cs index 591f85444953..7a8852f45d1d 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/OptimismGenesisLoaderTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/OptimismGenesisLoaderTests.cs @@ -12,7 +12,6 @@ using Nethermind.Evm.TransactionProcessing; using Nethermind.Logging; using Nethermind.Specs.ChainSpecStyle; -using Nethermind.State; using NSubstitute; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Optimism.Test/OptimismHeaderValidatorTests.cs b/src/Nethermind/Nethermind.Optimism.Test/OptimismHeaderValidatorTests.cs index f8858d8632b4..c2201ac65730 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/OptimismHeaderValidatorTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/OptimismHeaderValidatorTests.cs @@ -1,152 +1,157 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; -using FluentAssertions; +using System.Linq; using Nethermind.Blockchain; using Nethermind.Consensus; using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; -using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Logging; -using Nethermind.Specs; using NSubstitute; using NUnit.Framework; namespace Nethermind.Optimism.Test; [Parallelizable(ParallelScope.All)] -public class OptimismHeaderValidatorTests +[TestFixtureSource(typeof(Fork), nameof(Fork.AllAndNextToGenesis))] +public class OptimismHeaderValidatorTests(Fork fork) { + private readonly ulong _timestamp = fork.Timestamp; + private static readonly Hash256 PostCanyonWithdrawalsRoot = Keccak.OfAnEmptySequenceRlp; - [TestCaseSource(nameof(EIP1559ParametersExtraData))] - public void Validates_EIP1559Parameters_InExtraData_AfterHolocene((string HexString, bool IsValid) testCase) + private (BlockHeader genesis, BlockHeader header) BuildHeaders(Action? postBuild = null) { - var genesis = Build.A.BlockHeader + BlockHeader genesis = Build.A.BlockHeader .WithNumber(0) .WithTimestamp(Spec.GenesisTimestamp) .TestObject; - var header = Build.A.BlockHeader + + BlockHeaderBuilder builder = Build.A.BlockHeader .WithNumber(1) .WithParent(genesis) - .WithTimestamp(Spec.HoloceneTimeStamp) + .WithTimestamp(_timestamp) .WithDifficulty(0) .WithNonce(0) + .WithBlobGasUsed(0) + .WithExcessBlobGas(0) .WithUnclesHash(Keccak.OfAnEmptySequenceRlp) .WithWithdrawalsRoot(PostCanyonWithdrawalsRoot) - .WithExtraData(Bytes.FromHexString(testCase.HexString)).TestObject; + .WithRequestsHash(_timestamp >= Spec.IsthmusTimeStamp + ? OptimismPostMergeBlockProducer.PostIsthmusRequestHash + : null + ) + .WithExtraDataHex(_timestamp >= Spec.JovianTimeStamp + ? "0x0100000001000001bc0000000000000000" + : "0x0000000001000001bc" + ); + + postBuild?.Invoke(builder); + + return (genesis, builder.TestObject); + } + + private static IEnumerable<(string data, Valid isValid)> EIP1559ParametersExtraData => new (string data, Valid isValid)[] + { + new("0x0100000001000000000000000000000000", Valid.Since(Spec.JovianTimeStamp)), + new("0x0100000001000001bc0000000000000001", Valid.Since(Spec.JovianTimeStamp)), + new("0x0100000001ffffffff00000000000000ff", Valid.Since(Spec.JovianTimeStamp)), + new("0x01ffffffff0000000000000000000001ff", Valid.Since(Spec.JovianTimeStamp)), + new("0x01ffffffff000001bc01000000000000ff", Valid.Since(Spec.JovianTimeStamp)), + new("0x01ffffffffffffffffffffffffffffffff", Valid.Since(Spec.JovianTimeStamp)), + + new("0x000000000100000000", Valid.Between(Spec.HoloceneTimeStamp, Spec.JovianTimeStamp)), + new("0x0000000001000001bc", Valid.Between(Spec.HoloceneTimeStamp, Spec.JovianTimeStamp)), + new("0x0000000001ffffffff", Valid.Between(Spec.HoloceneTimeStamp, Spec.JovianTimeStamp)), + new("0x00ffffffff00000000", Valid.Between(Spec.HoloceneTimeStamp, Spec.JovianTimeStamp)), + new("0x00ffffffff000001bc", Valid.Between(Spec.HoloceneTimeStamp, Spec.JovianTimeStamp)), + new("0x00ffffffffffffffff", Valid.Between(Spec.HoloceneTimeStamp, Spec.JovianTimeStamp)), + + new("0x0", Valid.Never), + new("0xffffaaaa", Valid.Never), + new("0x01ffffffff00000000", Valid.Never), + new("0xff0000000100000001", Valid.Never), + new("0x000000000000000000", Valid.Never), + new("0x000000000000000001", Valid.Never), + new("0x01ffffffff000001bc00000000000000", Valid.Never), + new("0x01ffffffff000001bc000000000000000000", Valid.Never), + }.Select(x => + (x.data, Valid.Before(Spec.HoloceneTimeStamp) | x.isValid) // No validation before Holocene + ); + + [TestCaseSource(nameof(EIP1559ParametersExtraData))] + public void Validates_EIP1559Parameters_InExtraData((string hexString, Valid isValid) testCase) + { + (BlockHeader genesis, BlockHeader header) = BuildHeaders(b => b.WithExtraDataHex(testCase.hexString)); var validator = new OptimismHeaderValidator( AlwaysPoS.Instance, Substitute.For(), - Always.Valid, Spec.Instance, - Spec.BuildFor(header), + Always.Valid, Spec.Instance, Spec.BuildFor(header), TestLogManager.Instance); - var valid = validator.Validate(header, genesis); + Assert.That(() => validator.Validate(header, genesis), Is.EqualTo(testCase.isValid.On(_timestamp))); + } - valid.Should().Be(testCase.IsValid); + private static IEnumerable<(Hash256? requestHash, Valid isValid)> WithdrawalsRequestHashTestCases() + { + yield return (null, Valid.Before(Spec.IsthmusTimeStamp)); + yield return (TestItem.KeccakA, Valid.Before(Spec.IsthmusTimeStamp)); + yield return (OptimismPostMergeBlockProducer.PostIsthmusRequestHash, Valid.Always); } - [TestCaseSource(nameof(EIP1559ParametersExtraData))] - public void Ignores_ExtraData_BeforeHolocene((string HexString, bool _) testCase) + [TestCaseSource(nameof(WithdrawalsRequestHashTestCases))] + public void ValidateRequestHash((Hash256? requestHash, Valid isValid) testCase) { - var genesis = Build.A.BlockHeader - .WithNumber(0) - .WithTimestamp(Spec.GenesisTimestamp) - .TestObject; - var header = Build.A.BlockHeader - .WithNumber(1) - .WithParent(genesis) - .WithTimestamp(Spec.HoloceneTimeStamp - 1) - .WithDifficulty(0) - .WithNonce(0) - .WithUnclesHash(Keccak.OfAnEmptySequenceRlp) - .WithWithdrawalsRoot(PostCanyonWithdrawalsRoot) - .WithExtraData(Bytes.FromHexString(testCase.HexString)).TestObject; + var (genesis, header) = BuildHeaders(b => b.WithRequestsHash(testCase.requestHash)); var validator = new OptimismHeaderValidator( AlwaysPoS.Instance, Substitute.For(), - Always.Valid, Spec.Instance, - Spec.BuildFor(header), + Always.Valid, Spec.Instance, Spec.BuildFor(header), TestLogManager.Instance); - var valid = validator.Validate(header, genesis); - - valid.Should().BeTrue(); + string? error = null; + Assert.That(() => validator.Validate(header, genesis, false, out error), Is.EqualTo(testCase.isValid.On(_timestamp)), () => error!); } - private static IEnumerable WithdrawalsRequestHashTestCases() + private static IEnumerable GasLimitTestCases() { - yield return new TestCaseData(Spec.CanyonTimestamp - 1, null, true) - .SetName("Pre Canyon - null request hash"); - yield return new TestCaseData(Spec.CanyonTimestamp - 1, null, true) - .SetName("Pre Canyon - some request hash"); - - yield return new TestCaseData(Spec.CanyonTimestamp, null, true) - .SetName("Post Canyon - null request hash"); - yield return new TestCaseData(Spec.CanyonTimestamp, TestItem.KeccakA, true) - .SetName("Post Canyon - some request hash"); - - yield return new TestCaseData(Spec.IsthmusTimeStamp, OptimismPostMergeBlockProducer.PostIsthmusRequestHash, true) - .SetName("Post Isthmus - expected request hash"); - yield return new TestCaseData(Spec.IsthmusTimeStamp, null, false).SetName( - "Post Isthmus - invalid request hash"); + yield return new(1_000, 500, 0, Valid.Always); + yield return new(1_000, 1_000, 0, Valid.Always); + yield return new(1_000, 1_000, 500, Valid.Always); + yield return new(1_000, 1_000, 1_000, Valid.Always); + + yield return new TestCaseData(1_000, 500, null, Valid.Never).SetName("blobGasUsed missing"); + yield return new TestCaseData(1_000, 1_000, null, Valid.Never).SetName("blobGasUsed missing, gasUsed = gasLimit"); + + yield return new TestCaseData(1_000, 1_000 + 1, 500, Valid.Never).SetName("gasUsed > gasLimit"); + yield return new TestCaseData(1_000, 1_000 + 1, 1_000, Valid.Never).SetName("gasUsed > gasLimit, blobGasUsed = gasLimit"); + yield return new TestCaseData(1_000, 1_000 + 1, 1_000 + 1, Valid.Never).SetName("blobGasUsed & gasUsed > gasLimit"); + + yield return new TestCaseData(1_000, 1_000, 1_000 + 1, Valid.Before(Spec.JovianTimeStamp)).SetName("blobGasUsed > gasLimit post Jovian"); } - [TestCaseSource(nameof(WithdrawalsRequestHashTestCases))] - public void ValidateRequestHash(ulong timestamp, Hash256? requestHash, bool isValid) + [TestCaseSource(nameof(GasLimitTestCases))] + public void ValidateGasLimit(int gasLimit, int gasUsed, int? blobGasUsed, Valid isValid) { - var genesis = Build.A.BlockHeader - .WithNumber(0) - .WithTimestamp(Spec.GenesisTimestamp) - .TestObject; - - var header = Build.A.BlockHeader - .WithNumber(1) - .WithParent(genesis) - .WithTimestamp(timestamp) - .WithDifficulty(0) - .WithNonce(0) - .WithUnclesHash(Keccak.OfAnEmptySequenceRlp) - .WithExtraData(Bytes.FromHexString("0x00ffffffffffffffff")) - .WithRequestsHash(requestHash) - .TestObject; + (BlockHeader genesis, BlockHeader header) = BuildHeaders(b => b + .WithGasLimit(gasLimit) + .WithBlobGasUsed((ulong?)blobGasUsed) + .WithGasUsed(gasUsed) + ); var validator = new OptimismHeaderValidator( AlwaysPoS.Instance, Substitute.For(), - Always.Valid, Spec.Instance, - Spec.BuildFor(header), + Always.Valid, Spec.Instance, Spec.BuildFor(header), TestLogManager.Instance); - var valid = validator.Validate(header, genesis); - - valid.Should().Be(isValid); - } - - private static IEnumerable<(string, bool)> EIP1559ParametersExtraData() - { - // Valid - yield return ("0x000000000100000000", true); - yield return ("0x0000000001000001bc", true); - yield return ("0x0000000001ffffffff", true); - yield return ("0x00ffffffff00000000", true); - yield return ("0x00ffffffff000001bc", true); - yield return ("0x00ffffffffffffffff", true); - - // Invalid - yield return ("0x0", false); - yield return ("0xffffaaaa", false); - yield return ("0x01ffffffff00000000", false); - yield return ("0xff0000000100000001", false); - yield return ("0x000000000000000000", false); - yield return ("0x000000000000000001", false); - yield return ("0x00ffffffff000001bc00", false); + string? error = null; + Assert.That(() => validator.Validate(header, genesis, false, out error), Is.EqualTo(isValid.On(_timestamp)), () => error!); } } diff --git a/src/Nethermind/Nethermind.Optimism.Test/OptimismPayloadAttributesTests.cs b/src/Nethermind/Nethermind.Optimism.Test/OptimismPayloadAttributesTests.cs index 508c42b4b28e..d977fc69de39 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/OptimismPayloadAttributesTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/OptimismPayloadAttributesTests.cs @@ -2,14 +2,13 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using FluentAssertions; +using Nethermind.Consensus; using Nethermind.Consensus.Producers; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Optimism.Rpc; -using NSubstitute; using NUnit.Framework; namespace Nethermind.Optimism.Test; @@ -19,12 +18,20 @@ public class OptimismPayloadAttributesTests { private static IEnumerable<(string, string)> PayloadIdTestCases() { + // V0 yield return ("0x000000000100000000", "0x00dea77451f10b20"); yield return ("0x0000000001000001bc", "0xf2975f6725d5f2e5"); yield return ("0x0000000001ffffffff", "0x6b09fc2a90d6c067"); yield return ("0x00ffffffff00000000", "0x9787e23f29594f18"); yield return ("0x00ffffffff000001bc", "0x2cb414f72aac7824"); yield return ("0x00ffffffffffffffff", "0xe411646692277df5"); + // V1 + yield return ("0x0100000001000000000000000000000001", "0xb1be2b369ffc937d"); + yield return ("0x0100000001000001bc0000000000000abc", "0x3227f4be2903c6ec"); + yield return ("0x0100000001000001bc0000000000000def", "0xe471f88f2ef8553d"); + yield return ("0x0100000001ffffffff00000000ffffffff", "0xe56c5af8cb83c757"); + yield return ("0x01ffffffff00000000ffffffff00000000", "0x34dec71cdbff4bbe"); + yield return ("0x01ffffffffffffffffffffffffffffffff", "0x2d20df1e01fc582a"); } [TestCaseSource(nameof(PayloadIdTestCases))] public void Compute_PayloadID_with_EIP1559Params((string HexStringEIP1559Params, string PayloadId) testCase) @@ -39,20 +46,25 @@ public void Compute_PayloadID_with_EIP1559Params((string HexStringEIP1559Params, EIP1559Params = Bytes.FromHexString(testCase.HexStringEIP1559Params) }; - var payloadId = payloadAttributes.GetPayloadId(blockHeader); - - payloadId.Should().Be(testCase.PayloadId); + Assert.That(payloadAttributes.GetPayloadId(blockHeader), Is.EqualTo(testCase.PayloadId)); } - private static IEnumerable<(byte[]?, PayloadAttributesValidationResult, PayloadAttributesValidationResult)> Validate_EIP1559Params_Holocene_TestCases() + private static IEnumerable<(int? length, Valid isValid)> Validate_EIP1559Params_TestCases() { - yield return (null, PayloadAttributesValidationResult.Success, PayloadAttributesValidationResult.InvalidPayloadAttributes); - yield return (new byte[7], PayloadAttributesValidationResult.InvalidPayloadAttributes, PayloadAttributesValidationResult.InvalidPayloadAttributes); - yield return (new byte[8], PayloadAttributesValidationResult.InvalidPayloadAttributes, PayloadAttributesValidationResult.Success); - yield return (new byte[9], PayloadAttributesValidationResult.InvalidPayloadAttributes, PayloadAttributesValidationResult.InvalidPayloadAttributes); + yield return (null, Valid.Before(Spec.HoloceneTimeStamp)); + yield return (7, Valid.Never); + yield return (8, Valid.Between(Spec.HoloceneTimeStamp, Spec.JovianTimeStamp)); + yield return (9, Valid.Never); + yield return (15, Valid.Never); + yield return (16, Valid.Since(Spec.JovianTimeStamp)); + yield return (17, Valid.Never); } - [TestCaseSource(nameof(Validate_EIP1559Params_Holocene_TestCases))] - public void Validate_EIP1559Params_Holocene((byte[]? Eip1559Params, PayloadAttributesValidationResult BeforeHolocene, PayloadAttributesValidationResult AfterHolocene) testCase) + + [Test] + public void Validate_EIP1559Params( + [ValueSource(nameof(Validate_EIP1559Params_TestCases))] (int? length, Valid isValid) testCase, + [ValueSource(typeof(Fork), nameof(Fork.AllAndNextToGenesis))] Fork fork + ) { var payloadAttributes = new OptimismPayloadAttributes { @@ -60,22 +72,20 @@ public void Validate_EIP1559Params_Holocene((byte[]? Eip1559Params, PayloadAttri Transactions = [], PrevRandao = Hash256.Zero, SuggestedFeeRecipient = TestItem.AddressA, - EIP1559Params = testCase.Eip1559Params + Timestamp = fork.Timestamp, + EIP1559Params = testCase.length is { } length ? new byte[length] : null, + ParentBeaconBlockRoot = Hash256.Zero, + Withdrawals = [] }; - static ISpecProvider BuildSpecProvider(bool isHolocene) - { - var releaseSpec = Substitute.For(); - releaseSpec.IsOpHoloceneEnabled.Returns(isHolocene); - var specProvider = Substitute.For(); - specProvider.GetSpec(Arg.Any()).Returns(releaseSpec); - return specProvider; - } - - var beforeHolocene = payloadAttributes.Validate(BuildSpecProvider(isHolocene: false), 1, out var _); - beforeHolocene.Should().Be(testCase.BeforeHolocene); + ISpecProvider spec = Spec.BuildFor(fork.Timestamp); - var afterHolocene = payloadAttributes.Validate(BuildSpecProvider(isHolocene: true), 1, out var _); - afterHolocene.Should().Be(testCase.AfterHolocene); + Assert.That( + payloadAttributes.Validate(spec, EngineApiVersions.Cancun, out var error), + testCase.isValid.On(fork.Timestamp) + ? Is.EqualTo(PayloadAttributesValidationResult.Success) + : Is.EqualTo(PayloadAttributesValidationResult.InvalidPayloadAttributes), + () => error! + ); } } diff --git a/src/Nethermind/Nethermind.Optimism.Test/OptimismPayloadPreparationServiceTests.cs b/src/Nethermind/Nethermind.Optimism.Test/OptimismPayloadPreparationServiceTests.cs index baf68ac3ca90..1ae7828fe40b 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/OptimismPayloadPreparationServiceTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/OptimismPayloadPreparationServiceTests.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using System.Collections.Generic; -using System; using NUnit.Framework; using NSubstitute; using Nethermind.Evm.State; @@ -36,10 +35,17 @@ public class OptimismPayloadPreparationServiceTests { foreach (var noTxPool in (bool[])[true, false]) { - yield return (new OptimismPayloadAttributes { EIP1559Params = [0, 0, 0, 8, 0, 0, 0, 2], NoTxPool = noTxPool }, new EIP1559Parameters(0, 8, 2)); - yield return (new OptimismPayloadAttributes { EIP1559Params = [0, 0, 0, 2, 0, 0, 0, 2], NoTxPool = noTxPool }, new EIP1559Parameters(0, 2, 2)); - yield return (new OptimismPayloadAttributes { EIP1559Params = [0, 0, 0, 2, 0, 0, 0, 10], NoTxPool = noTxPool }, new EIP1559Parameters(0, 2, 10)); - yield return (new OptimismPayloadAttributes { EIP1559Params = [0, 0, 0, 0, 0, 0, 0, 0], NoTxPool = noTxPool }, new EIP1559Parameters(0, 250, 6)); + // V0 + yield return (new() { EIP1559Params = [0, 0, 0, 8, 0, 0, 0, 2], NoTxPool = noTxPool }, new EIP1559Parameters(0, 8, 2)); + yield return (new() { EIP1559Params = [0, 0, 0, 2, 0, 0, 0, 2], NoTxPool = noTxPool }, new EIP1559Parameters(0, 2, 2)); + yield return (new() { EIP1559Params = [0, 0, 0, 2, 0, 0, 0, 10], NoTxPool = noTxPool }, new EIP1559Parameters(0, 2, 10)); + yield return (new() { EIP1559Params = [0, 0, 0, 0, 0, 0, 0, 0], NoTxPool = noTxPool }, new EIP1559Parameters(0, 250, 6)); + + // V1 + yield return (new() { EIP1559Params = [0, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0], NoTxPool = noTxPool }, new EIP1559Parameters(1, 8, 2, 0)); + yield return (new() { EIP1559Params = [0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 255], NoTxPool = noTxPool }, new EIP1559Parameters(1, 2, 2, 255)); + yield return (new() { EIP1559Params = [0, 0, 0, 2, 0, 0, 0, 10, 255, 255, 255, 255, 255, 255, 255, 255], NoTxPool = noTxPool }, new EIP1559Parameters(1, 2, 10, ulong.MaxValue)); + yield return (new() { EIP1559Params = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], NoTxPool = noTxPool }, new EIP1559Parameters(1, 250, 6, 0)); } } [TestCaseSource(nameof(TestCases))] @@ -73,7 +79,7 @@ public async Task Writes_EIP1559Params_Into_HeaderExtraData((OptimismPayloadAttr gasLimitCalculator: Substitute.For(), sealEngine: Substitute.For(), timestamper: Substitute.For(), - miningConfig: Substitute.For(), + blocksConfig: Substitute.For(), logManager: TestLogManager.Instance ), txPool: Substitute.For(), diff --git a/src/Nethermind/Nethermind.Optimism.Test/OptimismProtocolVersionTest.cs b/src/Nethermind/Nethermind.Optimism.Test/OptimismProtocolVersionTest.cs index 0a56c5ac36ba..ad7648253354 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/OptimismProtocolVersionTest.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/OptimismProtocolVersionTest.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using FluentAssertions; -using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Optimism.ProtocolVersion; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Optimism.Test/OptimismWithdrawalTests.cs b/src/Nethermind/Nethermind.Optimism.Test/OptimismWithdrawalTests.cs index f79ffa67c2f6..bc1947abfc5a 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/OptimismWithdrawalTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/OptimismWithdrawalTests.cs @@ -12,7 +12,6 @@ using Nethermind.Evm.State; using Nethermind.Int256; using Nethermind.Logging; -using Nethermind.State; using NSubstitute; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Optimism.Test/ReceiptDecoderTests.cs b/src/Nethermind/Nethermind.Optimism.Test/ReceiptDecoderTests.cs index 37e1a5e4df79..1ece15c4c294 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/ReceiptDecoderTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/ReceiptDecoderTests.cs @@ -16,7 +16,7 @@ public void Test_tx_network_form_receipts_properly_encoded_for_trie(byte[] rlp, static OptimismTxReceipt TestNetworkEncodingRoundTrip(byte[] rlp, bool includesNonce, bool includesVersion) { OptimismReceiptMessageDecoder decoder = new(); - OptimismTxReceipt decodedReceipt = decoder.Decode(new RlpStream(rlp), RlpBehaviors.SkipTypedWrapping); + OptimismTxReceipt decodedReceipt = (OptimismTxReceipt)decoder.Decode(new RlpStream(rlp), RlpBehaviors.SkipTypedWrapping); RlpStream encodedRlp = new(decoder.GetLength(decodedReceipt, RlpBehaviors.SkipTypedWrapping)); decoder.Encode(encodedRlp, decodedReceipt, RlpBehaviors.SkipTypedWrapping); @@ -67,7 +67,7 @@ static void TestTrieEncoding(OptimismTxReceipt decodedReceipt, bool shouldInclud trieDecoder.Encode(encodedTrieRlp, decodedReceipt, RlpBehaviors.SkipTypedWrapping); encodedTrieRlp.Position = 0; - OptimismTxReceipt decodedTrieReceipt = trieDecoder.Decode(encodedTrieRlp, RlpBehaviors.SkipTypedWrapping); + OptimismTxReceipt decodedTrieReceipt = (OptimismTxReceipt)trieDecoder.Decode(encodedTrieRlp, RlpBehaviors.SkipTypedWrapping); Assert.Multiple(() => { diff --git a/src/Nethermind/Nethermind.Optimism.Test/Rpc/DepositTransactionForRpcTests.cs b/src/Nethermind/Nethermind.Optimism.Test/Rpc/DepositTransactionForRpcTests.cs index 6f199a5f6baf..4c4434e76f09 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/Rpc/DepositTransactionForRpcTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/Rpc/DepositTransactionForRpcTests.cs @@ -17,7 +17,7 @@ namespace Nethermind.Optimism.Test.Rpc; public class DepositTransactionForRpcTests { - private readonly IJsonSerializer serializer = new EthereumJsonSerializer(); + private readonly IJsonSerializer _serializer = new EthereumJsonSerializer(); private static TransactionBuilder Build => Core.Test.Builders.Build.A.Transaction.WithType(TxType.DepositTx); public static readonly Transaction[] Transactions = [ @@ -46,7 +46,7 @@ public void SetUp() public void Always_satisfies_schema(Transaction transaction) { TransactionForRpc rpcTransaction = TransactionForRpc.FromTransaction(transaction); - string serialized = serializer.Serialize(rpcTransaction); + string serialized = _serializer.Serialize(rpcTransaction); using var jsonDocument = JsonDocument.Parse(serialized); JsonElement json = jsonDocument.RootElement; ValidateSchema(json); @@ -85,10 +85,10 @@ private static void ValidateSchema(JsonElement json) [TestCaseSource(nameof(MalformedJsonTransactions))] public void Rejects_malformed_transaction_missing_field((string missingField, string json) testCase) { - var rpcTx = serializer.Deserialize(testCase.json); + var rpcTx = _serializer.Deserialize(testCase.json); rpcTx.Should().NotBeNull(); - var toTransaction = rpcTx.ToTransaction; + var toTransaction = () => rpcTx.ToTransaction(); toTransaction.Should().Throw().WithParameterName(testCase.missingField); } @@ -100,10 +100,10 @@ public void Rejects_malformed_transaction_missing_field((string missingField, st [TestCaseSource(nameof(ValidJsonTransactions))] public void Accepts_valid_transaction_missing_field((string missingField, string json) testCase) { - var rpcTx = serializer.Deserialize(testCase.json); + var rpcTx = _serializer.Deserialize(testCase.json); rpcTx.Should().NotBeNull(); - var toTransaction = rpcTx.ToTransaction; + var toTransaction = () => rpcTx.ToTransaction(); toTransaction.Should().NotThrow(); } } diff --git a/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs b/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs index adfac516a0c7..1dcfe7c78d52 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs @@ -12,6 +12,7 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; +using Nethermind.Db.LogIndex; using Nethermind.Evm; using Nethermind.Facade; using Nethermind.Facade.Eth; @@ -88,7 +89,22 @@ public async Task GetTransactionByHash_ReturnsCorrectTransactionType() }; IBlockchainBridge bridge = Substitute.For(); - bridge.GetTransaction(TestItem.KeccakA, checkTxnPool: true).Returns((receipt, tx, (UInt256?)0)); + TransactionLookupResult lookupResult = new( + tx, + new( + chainId: 0, + blockHash: receipt.BlockHash!, + blockNumber: receipt.BlockNumber, + txIndex: receipt.Index, + blockTimestamp: 0, + baseFee: 0, + receipt: receipt)); + bridge.TryGetTransaction(TestItem.KeccakA, out Arg.Any(), checkTxnPool: true) + .Returns(callInfo => + { + callInfo[1] = lookupResult; + return true; + }); TestRpcBlockchain rpcBlockchain = await TestRpcBlockchain .ForTest(sealEngineType: SealEngineType.Optimism) @@ -121,6 +137,7 @@ public async Task GetTransactionByHash_ReturnsCorrectTransactionType() "hash": "{{TestItem.KeccakA.Bytes.ToHexString(withZeroX: true)}}", "blockHash": "{{TestItem.KeccakB.Bytes.ToHexString(withZeroX: true)}}", "blockNumber": "0x10", + "blockTimestamp": "0x0", "transactionIndex": "0x20" }, "id":67 @@ -146,7 +163,22 @@ public async Task GetTransactionByHash_IncludesDepositReceiptVersion() }; IBlockchainBridge bridge = Substitute.For(); - bridge.GetTransaction(TestItem.KeccakA, checkTxnPool: true).Returns((receipt, tx, (UInt256?)0)); + TransactionLookupResult lookupResult = new( + tx, + new( + chainId: 0, + blockHash: receipt.BlockHash!, + blockNumber: receipt.BlockNumber, + txIndex: receipt.Index, + blockTimestamp: 0, + baseFee: 0, + receipt: receipt)); + bridge.TryGetTransaction(TestItem.KeccakA, out Arg.Any(), checkTxnPool: true) + .Returns(callInfo => + { + callInfo[1] = lookupResult; + return true; + }); TestRpcBlockchain rpcBlockchain = await TestRpcBlockchain .ForTest(sealEngineType: SealEngineType.Optimism) @@ -179,6 +211,7 @@ public async Task GetTransactionByHash_IncludesDepositReceiptVersion() "hash": "{{TestItem.KeccakA.Bytes.ToHexString(withZeroX: true)}}", "blockHash": "{{TestItem.KeccakB.Bytes.ToHexString(withZeroX: true)}}", "blockNumber": "0x10", + "blockTimestamp": "0x0", "transactionIndex": "0x20" }, "id":67 @@ -247,7 +280,8 @@ public async Task GetTransactionByBlockAndIndex_ReturnsCorrectTransactionType() "hash": "{{tx.Hash!.Bytes.ToHexString(withZeroX: true)}}", "blockHash": "{{block.Hash!.Bytes.ToHexString(withZeroX: true)}}", "blockNumber": "0x10", - "transactionIndex": "0x20" + "blockTimestamp": "0xf4240", + "transactionIndex": "0x0" }, "id":67 } @@ -324,7 +358,8 @@ public async Task GetTransactionByBlockAndIndex_IncludesDepositReceiptVersion() "hash": "{{tx.Hash!.Bytes.ToHexString(withZeroX: true)}}", "blockHash": "{{block.Hash!.Bytes.ToHexString(withZeroX: true)}}", "blockNumber": "0x10", - "transactionIndex": "0x20" + "blockTimestamp": "0xf4240", + "transactionIndex": "0x0" }, "id":67 } @@ -559,8 +594,7 @@ public static TestRpcBlockchain.Builder WithOptimismEthRpcMod blockchain.ProtocolsManager, blockchain.ForkInfo, new BlocksConfig().SecondsPerSlot, - - sequencerRpcClient, ecdsa, sealer, opSpecHelper + sequencerRpcClient, ecdsa, sealer, new LogIndexConfig(), opSpecHelper )); } } diff --git a/src/Nethermind/Nethermind.Optimism.Test/Spec.cs b/src/Nethermind/Nethermind.Optimism.Test/Spec.cs index c9636bd78b87..c3c53f0b4032 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/Spec.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/Spec.cs @@ -15,27 +15,57 @@ public static class Spec { public const ulong GenesisTimestamp = 1_000; public const ulong CanyonTimestamp = 1_300; + public const ulong EcotoneTimestamp = 1_600; public const ulong HoloceneTimeStamp = 2_000; public const ulong IsthmusTimeStamp = 2_100; + public const ulong JovianTimeStamp = 2_200; public static readonly IOptimismSpecHelper Instance = new OptimismSpecHelper(new OptimismChainSpecEngineParameters { CanyonTimestamp = CanyonTimestamp, + EcotoneTimestamp = EcotoneTimestamp, HoloceneTimestamp = HoloceneTimeStamp, - IsthmusTimestamp = IsthmusTimeStamp + IsthmusTimestamp = IsthmusTimeStamp, + JovianTimestamp = JovianTimeStamp, }); - public static ISpecProvider BuildFor(BlockHeader header) + public static ISpecProvider BuildFor(params BlockHeader[] headers) { - var spec = Substitute.For(); + var specProvider = Substitute.For(); + + foreach (BlockHeader header in headers) + { + var spec = Substitute.For(); + + spec.IsEip4844Enabled = true; + spec.IsOpHoloceneEnabled = Instance.IsHolocene(header); + spec.IsOpGraniteEnabled = Instance.IsGranite(header); + spec.IsOpIsthmusEnabled = Instance.IsIsthmus(header); + spec.IsOpJovianEnabled = Instance.IsJovian(header); - spec.IsOpHoloceneEnabled = Instance.IsHolocene(header); - spec.IsOpGraniteEnabled = Instance.IsGranite(header); - spec.IsOpIsthmusEnabled = Instance.IsIsthmus(header); + specProvider.GetSpec(header).Returns(spec); + } + + return specProvider; + } + public static ISpecProvider BuildFor(params ulong[] timestamps) + { var specProvider = Substitute.For(); - specProvider.GetSpec(header).Returns(spec); + + foreach (ulong timestamp in timestamps) + { + var spec = Substitute.For(); + + spec.IsEip4844Enabled = true; + spec.IsOpHoloceneEnabled = timestamp >= HoloceneTimeStamp; + spec.IsOpIsthmusEnabled = timestamp >= IsthmusTimeStamp; + spec.IsOpJovianEnabled = timestamp >= JovianTimeStamp; + + specProvider.GetSpec(Arg.Is(f => f.Timestamp == timestamp)).Returns(spec); + } + return specProvider; } } diff --git a/src/Nethermind/Nethermind.Optimism.Test/Valid.cs b/src/Nethermind/Nethermind.Optimism.Test/Valid.cs new file mode 100644 index 000000000000..a3fa7c4873b9 --- /dev/null +++ b/src/Nethermind/Nethermind.Optimism.Test/Valid.cs @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Nethermind.Optimism.Test; + +/// +/// Explicitly describes at which timestamp ranges a test case should be valid. +/// +// Not worth optimizing as it's expected to work with just a few intervals on average. +public class Valid +{ + // From - inclusive, To - exclusive + // Null in From means negative infinity, in To - positive infinity + internal readonly record struct Interval(ulong? From, ulong? To) : IComparable + { + public static readonly Interval Empty = new(0, 0); + public static readonly Interval Full = new(null, null); + + public bool Contains(ulong value) + { + if (From is null) return To is null || value < To; + if (To is null) return value >= From; + return value >= From && value < To; + } + + // next must be same or larger in comparison + public bool TryUnionWithNext(Interval next, out Interval combined) + { + combined = (To, next.From) switch + { + (null, _) => new(MinFrom(From, next.From), null), + (_, null) => new(null, MaxTo(To, next.To)), + var (to, from) when to >= from => new(MinFrom(From, next.From), MaxTo(To, next.To)), + _ => Empty + }; + + return combined != Empty; + } + + public int CompareTo(Interval other) => (From, other.From) switch + { + var (f1, f2) when f1 == f2 => CompareTo(To, other.To), + (null, var f2) => -1, + (var f1, null) => 1, + (ulong f1, ulong f2) => f1.CompareTo(f2) + }; + + private static int CompareTo(ulong? to1, ulong? to2) => (to1, to2) switch + { + var (t1, t2) when t1 == t2 => 0, + (null, var t2) => 1, + (var t1, null) => -1, + (ulong t1, ulong t2) => t1.CompareTo(t2) + }; + + private static ulong? MinFrom(ulong? a, ulong? b) => + !a.HasValue || !b.HasValue ? null : Math.Min(a.Value, b.Value); + + private static ulong? MaxTo(ulong? a, ulong? b) => + !a.HasValue || !b.HasValue ? null : Math.Max(a.Value, b.Value); + + public override string ToString() => (From, To) switch + { + (null, null) => "always", + (0, 0) => "never", + (ulong from, null) => $"since {ForkName(from)}", + (null, ulong to) => $"before {ForkName(to)}", + (ulong from, ulong to) => $"between {ForkName(from)} and {ForkName(to)}" + }; + } + + private static string ForkName(ulong value) => Fork.At.TryGetValue(value, out Fork? fork) ? fork.Name : $"{value}"; + + private readonly List _intervals; + + private Valid(List intervals) => _intervals = intervals; + private Valid(Interval interval) : this([interval]) { } + + public static readonly Valid Always = new(Interval.Full); + public static readonly Valid Never = new(Interval.Empty); + + public static Valid Since(ulong from) => new(new Interval(from, null)); + public static Valid Before(ulong to) => new(new Interval(null, to)); + public static Valid Between(ulong from, ulong to) => new(new Interval(from, to)); + + public static Valid operator |(Valid v1, Valid v2) + { + IEnumerable sorted = v1._intervals.Concat(v2._intervals).Order(); + List merged = new(v1._intervals.Count + v2._intervals.Count); + + Interval current = Interval.Empty; + foreach (Interval next in sorted) + { + if (current.TryUnionWithNext(next, out Interval combined)) + { + current = combined; + } + else + { + if (current != Interval.Empty) merged.Add(current); + current = next; + } + } + + merged.Add(current); + return new Valid(merged); + } + + public bool On(ulong timestamp) => _intervals.Any(i => i.Contains(timestamp)); + + public override string ToString() => $"Valid: {string.Join("; ", _intervals)}"; +} diff --git a/src/Nethermind/Nethermind.Optimism.Test/ValidTests.cs b/src/Nethermind/Nethermind.Optimism.Test/ValidTests.cs new file mode 100644 index 000000000000..4db80cba1914 --- /dev/null +++ b/src/Nethermind/Nethermind.Optimism.Test/ValidTests.cs @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; + +namespace Nethermind.Optimism.Test; + +public class ValidTests +{ + private static IEnumerable TestCases() + { + yield return new(new[] { Valid.Never, Valid.Never }, "Valid: never"); + yield return new(new[] { Valid.Since(10), Valid.Never }, "Valid: since 10"); + yield return new(new[] { Valid.Before(10), Valid.Never }, "Valid: before 10"); + yield return new(new[] { Valid.Between(10, 20), Valid.Never }, "Valid: between 10 and 20"); + + yield return new(new[] { Valid.Never, Valid.Always }, "Valid: always"); + yield return new(new[] { Valid.Always, Valid.Always }, "Valid: always"); + yield return new(new[] { Valid.Always, Valid.Always }, "Valid: always"); + yield return new(new[] { Valid.Since(10), Valid.Always }, "Valid: always"); + yield return new(new[] { Valid.Before(10), Valid.Always }, "Valid: always"); + yield return new(new[] { Valid.Between(10, 20), Valid.Always }, "Valid: always"); + + yield return new(new[] { Valid.Before(10), Valid.Since(20) }, "Valid: before 10; since 20"); + yield return new(new[] { Valid.Before(10), Valid.Since(10) }, "Valid: always"); + yield return new(new[] { Valid.Before(20), Valid.Since(10) }, "Valid: always"); + + yield return new(new[] { Valid.Between(10, 20), Valid.Since(30) }, "Valid: between 10 and 20; since 30"); + yield return new(new[] { Valid.Between(10, 20), Valid.Since(20) }, "Valid: since 10"); + yield return new(new[] { Valid.Between(10, 30), Valid.Since(20) }, "Valid: since 10"); + + yield return new(new[] { Valid.Between(10, 20), Valid.Between(30, 40) }, "Valid: between 10 and 20; between 30 and 40"); + yield return new(new[] { Valid.Between(10, 20), Valid.Between(20, 40) }, "Valid: between 10 and 40"); + yield return new(new[] { Valid.Between(10, 30), Valid.Between(20, 40) }, "Valid: between 10 and 40"); + } + + [TestCaseSource(nameof(TestCases))] + public void CombinesCorrectly(Valid[] values, string expected) + { + Assert.That($"{values.Aggregate((v1, v2) => v1 | v2)}", Is.EqualTo(expected)); + Assert.That($"{values.Reverse().Aggregate((v1, v2) => v1 | v2)}", Is.EqualTo(expected)); + } +} diff --git a/src/Nethermind/Nethermind.Optimism/CL/Decoding/BatchDecoder.cs b/src/Nethermind/Nethermind.Optimism/CL/Decoding/BatchDecoder.cs index 9b4abf505013..49f9381bff68 100644 --- a/src/Nethermind/Nethermind.Optimism/CL/Decoding/BatchDecoder.cs +++ b/src/Nethermind/Nethermind.Optimism/CL/Decoding/BatchDecoder.cs @@ -11,6 +11,7 @@ namespace Nethermind.Optimism.CL.Decoding; public static class BatchDecoder { + private const ulong MaxSpanBatchElementCount = 10_000_000; public static IEnumerable DecodeSpanBatches(ReadOnlyMemory source) { var reader = new BinaryMemoryReader(source); @@ -36,8 +37,15 @@ private static BatchV1 DecodeSpanBatch(BinaryMemoryReader reader) var l1OriginCheck = reader.Take(20); // payload - // TODO: `blockCount`: This is at least 1, empty span batches are invalid. var blockCount = reader.Read(Protobuf.DecodeULong); + if (blockCount < 1) + { + throw new FormatException("Invalid span batch: block_count must be >= 1"); + } + if (blockCount > MaxSpanBatchElementCount) + { + throw new FormatException($"Invalid span batch: block_count exceeds MAX_SPAN_BATCH_ELEMENT_COUNT ({MaxSpanBatchElementCount})"); + } var originBits = reader.Read(Protobuf.DecodeBitList, blockCount); var blockTransactionCounts = new ulong[blockCount]; @@ -45,7 +53,15 @@ private static BatchV1 DecodeSpanBatch(BinaryMemoryReader reader) for (var i = 0; i < (int)blockCount; ++i) { blockTransactionCounts[i] = reader.Read(Protobuf.DecodeULong); + if (blockTransactionCounts[i] > MaxSpanBatchElementCount) + { + throw new FormatException($"Invalid span batch: tx count for block {i} exceeds MAX_SPAN_BATCH_ELEMENT_COUNT ({MaxSpanBatchElementCount})"); + } totalTxCount += blockTransactionCounts[i]; + if (totalTxCount > MaxSpanBatchElementCount) + { + throw new FormatException($"Invalid span batch: totalTxCount exceeds MAX_SPAN_BATCH_ELEMENT_COUNT ({MaxSpanBatchElementCount})"); + } } // TODO: // `totalTxCount` in BatchV1 cannot be greater than MaxSpanBatchElementCount (`10_000_000`). @@ -73,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++; @@ -114,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 875aa9be2883..c94d04d43183 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/Decoding/FrameDecoder.cs b/src/Nethermind/Nethermind.Optimism/CL/Decoding/FrameDecoder.cs index c81c013dbff4..07db9d9c4d02 100644 --- a/src/Nethermind/Nethermind.Optimism/CL/Decoding/FrameDecoder.cs +++ b/src/Nethermind/Nethermind.Optimism/CL/Decoding/FrameDecoder.cs @@ -4,7 +4,6 @@ using System; using System.Buffers.Binary; using System.Collections.Generic; -using System.Linq; using Nethermind.Core.Extensions; namespace Nethermind.Optimism.CL.Decoding; diff --git a/src/Nethermind/Nethermind.Optimism/CL/Derivation/IPayloadAttributesDeriver.cs b/src/Nethermind/Nethermind.Optimism/CL/Derivation/IPayloadAttributesDeriver.cs index ddd668fee9f4..4ba6f2ad1861 100644 --- a/src/Nethermind/Nethermind.Optimism/CL/Derivation/IPayloadAttributesDeriver.cs +++ b/src/Nethermind/Nethermind.Optimism/CL/Derivation/IPayloadAttributesDeriver.cs @@ -3,7 +3,6 @@ using Nethermind.JsonRpc.Data; using Nethermind.Optimism.CL.Decoding; -using Nethermind.Optimism.CL.L1Bridge; using Nethermind.Optimism.Rpc; namespace Nethermind.Optimism.CL.Derivation; diff --git a/src/Nethermind/Nethermind.Optimism/CL/Derivation/ISystemConfigDeriver.cs b/src/Nethermind/Nethermind.Optimism/CL/Derivation/ISystemConfigDeriver.cs index 80d2447d3f88..8509fc8bda97 100644 --- a/src/Nethermind/Nethermind.Optimism/CL/Derivation/ISystemConfigDeriver.cs +++ b/src/Nethermind/Nethermind.Optimism/CL/Derivation/ISystemConfigDeriver.cs @@ -3,7 +3,6 @@ using System; using System.Buffers.Binary; -using System.Linq; using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.JsonRpc.Data; diff --git a/src/Nethermind/Nethermind.Optimism/CL/IL2Api.cs b/src/Nethermind/Nethermind.Optimism/CL/IL2Api.cs index 311b95ec9893..fffbc284dad6 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/L1Bridge/EthereumL1Bridge.cs b/src/Nethermind/Nethermind.Optimism/CL/L1Bridge/EthereumL1Bridge.cs index 879b6044d189..7ca731bd5a3d 100644 --- a/src/Nethermind/Nethermind.Optimism/CL/L1Bridge/EthereumL1Bridge.cs +++ b/src/Nethermind/Nethermind.Optimism/CL/L1Bridge/EthereumL1Bridge.cs @@ -17,9 +17,6 @@ namespace Nethermind.Optimism.CL.L1Bridge; public class EthereumL1Bridge : IL1Bridge { - private const int L1EpochSlotSize = 32; - private const int L1SlotTimeMilliseconds = 12000; - private const int L1EpochTimeMilliseconds = L1EpochSlotSize * L1SlotTimeMilliseconds; private readonly IEthApi _ethL1Api; private readonly IBeaconApi _beaconApi; private readonly ILogger _logger; @@ -224,11 +221,6 @@ private DaDataSource ProcessCalldataBatcherTransaction(L1Transaction transaction return _unfinalizedL1BlocksQueue.Count == 0 ? null : _unfinalizedL1BlocksQueue.Dequeue(); } - private void LogReorg() - { - if (_logger.IsInfo) _logger.Info("L1 reorg detected. Resetting pipeline"); - } - public async Task GetBlock(ulong blockNumber, CancellationToken token) => await RetryGetBlock(async () => await _ethL1Api.GetBlockByNumber(blockNumber, true), token); diff --git a/src/Nethermind/Nethermind.Optimism/CL/L1ConfigValidator.cs b/src/Nethermind/Nethermind.Optimism/CL/L1ConfigValidator.cs index 1ba478bb35aa..217c88fd1cfd 100644 --- a/src/Nethermind/Nethermind.Optimism/CL/L1ConfigValidator.cs +++ b/src/Nethermind/Nethermind.Optimism/CL/L1ConfigValidator.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Threading.Tasks; using Nethermind.Core.Crypto; using Nethermind.Logging; diff --git a/src/Nethermind/Nethermind.Optimism/CL/L2Api.cs b/src/Nethermind/Nethermind.Optimism/CL/L2Api.cs index 41951eeec669..330161eeea44 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,12 +53,16 @@ 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() }; - Transaction[] txs = block.Transactions.Cast().Select(t => t.ToTransaction()).ToArray(); + Transaction[] txs = block.Transactions.Cast().Select(t => + { + Result result = t.ToTransaction(); + return result.IsError ? throw new InvalidOperationException($"Failed to convert transaction: {result.Error}") : result.Data; + }).ToArray(); payloadAttributes.SetTransactions(txs); @@ -133,7 +138,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)); @@ -157,7 +162,7 @@ public async Task ForkChoiceUpdatedV3(Hash256 headHas public async Task GetPayloadV3(string payloadId) { byte[] payloadIdBytes = Bytes.FromHexString(payloadId); - var getPayloadResult = await l2EngineRpc.engine_getPayloadV3(payloadIdBytes); + ResultWrapper getPayloadResult = await l2EngineRpc.engine_getPayloadV3(payloadIdBytes); while (getPayloadResult.Result.ResultType != ResultType.Success) { if (_logger.IsWarn) _logger.Warn($"GetPayload request error: {getPayloadResult.Result.Error}"); diff --git a/src/Nethermind/Nethermind.Optimism/CL/P2P/OptimismCLP2P.cs b/src/Nethermind/Nethermind.Optimism/CL/P2P/OptimismCLP2P.cs index 9790024c45f3..198c101bfdfd 100644 --- a/src/Nethermind/Nethermind.Optimism/CL/P2P/OptimismCLP2P.cs +++ b/src/Nethermind/Nethermind.Optimism/CL/P2P/OptimismCLP2P.cs @@ -265,7 +265,7 @@ private bool TryValidateAndDecodePayload(byte[] msg, [MaybeNullWhen(false)] out return false; } - using ArrayPoolList decompressed = new(length, length); + using ArrayPoolListRef decompressed = new(length, length); Snappy.Decompress(msg, decompressed.AsSpan()); Span signature = decompressed.AsSpan()[..65]; diff --git a/src/Nethermind/Nethermind.Optimism/CL/P2P/P2PBlockValidator.cs b/src/Nethermind/Nethermind.Optimism/CL/P2P/P2PBlockValidator.cs index 4f8c74950499..36b3655e20c7 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/CL/Rpc/OptimismRollupConfig.cs b/src/Nethermind/Nethermind.Optimism/CL/Rpc/OptimismRollupConfig.cs index 474392ee910a..e36e0aeffe84 100644 --- a/src/Nethermind/Nethermind.Optimism/CL/Rpc/OptimismRollupConfig.cs +++ b/src/Nethermind/Nethermind.Optimism/CL/Rpc/OptimismRollupConfig.cs @@ -45,6 +45,8 @@ public sealed record OptimismRollupConfig public required ulong? HoloceneTime { get; init; } [JsonPropertyName("isthmus_time")] public required ulong? IsthmusTime { get; init; } + [JsonPropertyName("jovian_time")] + public required ulong? JovianTime { get; init; } [JsonPropertyName("batch_inbox_address")] public required Address BatchInboxAddress { get; init; } [JsonPropertyName("deposit_contract_address")] @@ -105,6 +107,7 @@ public static OptimismRollupConfig Build( GraniteTime = engineParameters.GraniteTimestamp, HoloceneTime = engineParameters.HoloceneTimestamp, IsthmusTime = engineParameters.IsthmusTimestamp, + JovianTime = engineParameters.JovianTimestamp, BatchInboxAddress = clParameters.BatchSubmitter!, DepositContractAddress = chainSpec.Parameters.DepositContractAddress, diff --git a/src/Nethermind/Nethermind.Optimism/ICostHelper.cs b/src/Nethermind/Nethermind.Optimism/ICostHelper.cs index c125d9dd428b..b863c14a74c5 100644 --- a/src/Nethermind/Nethermind.Optimism/ICostHelper.cs +++ b/src/Nethermind/Nethermind.Optimism/ICostHelper.cs @@ -12,4 +12,6 @@ public interface ICostHelper UInt256 ComputeL1Cost(Transaction tx, BlockHeader header, IWorldState worldState); UInt256 ComputeOperatorCost(long gas, BlockHeader header, IWorldState worldState); + + UInt256 ComputeDaFootprint(Block block); } diff --git a/src/Nethermind/Nethermind.Optimism/IOptimismSpecHelper.cs b/src/Nethermind/Nethermind.Optimism/IOptimismSpecHelper.cs index 1d54b27eca88..068596ff3dd9 100644 --- a/src/Nethermind/Nethermind.Optimism/IOptimismSpecHelper.cs +++ b/src/Nethermind/Nethermind.Optimism/IOptimismSpecHelper.cs @@ -18,6 +18,7 @@ public interface IOptimismSpecHelper bool IsGranite(BlockHeader header); bool IsHolocene(BlockHeader header); bool IsIsthmus(BlockHeader header); + bool IsJovian(BlockHeader header); Address? Create2DeployerAddress { get; } byte[]? Create2DeployerCode { get; } } diff --git a/src/Nethermind/Nethermind.Optimism/OPConfigHelper.cs b/src/Nethermind/Nethermind.Optimism/OPConfigHelper.cs index aa825bccaa4d..8ffe8ba39d6e 100644 --- a/src/Nethermind/Nethermind.Optimism/OPConfigHelper.cs +++ b/src/Nethermind/Nethermind.Optimism/OPConfigHelper.cs @@ -16,6 +16,7 @@ public class OptimismSpecHelper(OptimismChainSpecEngineParameters parameters) : private readonly ulong? _graniteTimestamp = parameters.GraniteTimestamp; private readonly ulong? _holoceneTimestamp = parameters.HoloceneTimestamp; private readonly ulong? _isthmusTimestamp = parameters.IsthmusTimestamp; + private readonly ulong? _jovianTimestamp = parameters.JovianTimestamp; public Address? L1FeeReceiver { get; init; } = parameters.L1FeeRecipient; @@ -64,6 +65,11 @@ public bool IsIsthmus(BlockHeader header) return header.Timestamp >= _isthmusTimestamp; } + public bool IsJovian(BlockHeader header) + { + return header.Timestamp >= _jovianTimestamp; + } + public Address? Create2DeployerAddress { get; } = parameters.Create2DeployerAddress; public byte[]? Create2DeployerCode { get; } = parameters.Create2DeployerCode; } diff --git a/src/Nethermind/Nethermind.Optimism/OptimismBaseFeeCalculator.cs b/src/Nethermind/Nethermind.Optimism/OptimismBaseFeeCalculator.cs index 86410a22303c..244ae58458d4 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismBaseFeeCalculator.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismBaseFeeCalculator.cs @@ -12,18 +12,20 @@ namespace Nethermind.Optimism; /// See /// public sealed class OptimismBaseFeeCalculator( - ulong holoceneTimestamp, + ulong? holoceneTimestamp, + ulong? jovianTimestamp, IBaseFeeCalculator baseFeeCalculator ) : IBaseFeeCalculator { public UInt256 Calculate(BlockHeader parent, IEip1559Spec specFor1559) { var spec = specFor1559; + EIP1559Parameters eip1559Params = default; if (parent.Timestamp >= holoceneTimestamp) { // NOTE: This operation should never fail since headers should be valid at this point. - if (!parent.TryDecodeEIP1559Parameters(out EIP1559Parameters eip1559Params, out var error)) + if (!parent.TryDecodeEIP1559Parameters(out eip1559Params, out var error)) { throw new InvalidOperationException($"{nameof(BlockHeader)} was not properly validated: {error}"); } @@ -35,6 +37,38 @@ public UInt256 Calculate(BlockHeader parent, IEip1559Spec specFor1559) }; } + if (parent.Timestamp >= jovianTimestamp) + { + if (parent.BlobGasUsed is null) + throw new InvalidOperationException($"{nameof(parent.BlobGasUsed)} does not store DA footprint in post-Jovian block."); + + var daFootprint = (long)parent.BlobGasUsed; + + // Override gas used for calculation if the DA footprint is larger + UInt256 baseFee = daFootprint > parent.GasUsed + ? CalculateWithGasUsedOverride(parent, spec, daFootprint) + : baseFeeCalculator.Calculate(parent, spec); + + if (eip1559Params.MinBaseFee > 0) + baseFee = UInt256.Max(baseFee, eip1559Params.MinBaseFee); + + return baseFee; + } + return baseFeeCalculator.Calculate(parent, spec); } + + private UInt256 CalculateWithGasUsedOverride(BlockHeader parent, IEip1559Spec spec, long gasOverride) + { + var prevGasUsed = parent.GasUsed; + try + { + parent.GasUsed = gasOverride; + return baseFeeCalculator.Calculate(parent, spec); + } + finally + { + parent.GasUsed = prevGasUsed; + } + } } diff --git a/src/Nethermind/Nethermind.Optimism/OptimismBlockProcessor.cs b/src/Nethermind/Nethermind.Optimism/OptimismBlockProcessor.cs index bc1a0a1ff121..ed55031d4536 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismBlockProcessor.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismBlockProcessor.cs @@ -13,15 +13,19 @@ using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Specs; +using Nethermind.Crypto; using Nethermind.Evm.State; using Nethermind.Evm.Tracing; +using Nethermind.Int256; using Nethermind.Logging; namespace Nethermind.Optimism; public class OptimismBlockProcessor : BlockProcessor { + private readonly IOptimismSpecHelper _opSpecHelper; private readonly Create2DeployerContractRewriter? _contractRewriter; + private readonly ICostHelper _costHelper; public OptimismBlockProcessor( ISpecProvider specProvider, @@ -36,7 +40,8 @@ public OptimismBlockProcessor( IOptimismSpecHelper opSpecHelper, Create2DeployerContractRewriter contractRewriter, IWithdrawalProcessor withdrawalProcessor, - IExecutionRequestsProcessor executionRequestsProcessor) + IExecutionRequestsProcessor executionRequestsProcessor, + ICostHelper costHelper) : base( specProvider, blockValidator, @@ -51,13 +56,31 @@ public OptimismBlockProcessor( executionRequestsProcessor) { ArgumentNullException.ThrowIfNull(stateProvider); + _opSpecHelper = opSpecHelper; _contractRewriter = contractRewriter; + _costHelper = costHelper; ReceiptsTracer = new OptimismBlockReceiptTracer(opSpecHelper, stateProvider); } protected override TxReceipt[] ProcessBlock(Block block, IBlockTracer blockTracer, ProcessingOptions options, IReleaseSpec spec, CancellationToken token) { _contractRewriter?.RewriteContract(block.Header, _stateProvider); - return base.ProcessBlock(block, blockTracer, options, spec, token); + TxReceipt[] receipts = base.ProcessBlock(block, blockTracer, options, spec, token); + + if (_opSpecHelper.IsJovian(block.Header)) + { + UInt256 daFootprintBig = _costHelper.ComputeDaFootprint(block); + var (daFootprint, hasOverflow) = daFootprintBig.UlongWithOverflow; + if (hasOverflow || daFootprint > long.MaxValue) + throw new InvalidOperationException($"DA Footprint overflow ({daFootprintBig}) at block {block.Header.Number}"); + + if (block.Header.BlobGasUsed != daFootprint) + { + block.Header.BlobGasUsed = daFootprint; + block.Header.Hash = block.Header.CalculateHash(); + } + } + + return receipts; } } diff --git a/src/Nethermind/Nethermind.Optimism/OptimismBlockReceiptTracer.cs b/src/Nethermind/Nethermind.Optimism/OptimismBlockReceiptTracer.cs index 4d93eb4bf0ac..02dcb961ab0c 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismBlockReceiptTracer.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismBlockReceiptTracer.cs @@ -6,7 +6,6 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Evm.State; -using Nethermind.Evm.Tracing; namespace Nethermind.Optimism; diff --git a/src/Nethermind/Nethermind.Optimism/OptimismBlockValidator.cs b/src/Nethermind/Nethermind.Optimism/OptimismBlockValidator.cs index 7574b2f1f7c9..c29adc8c09e1 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismBlockValidator.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismBlockValidator.cs @@ -7,7 +7,6 @@ using Nethermind.Core.Messages; using Nethermind.Core.Specs; using Nethermind.Logging; -using Nethermind.Serialization.Rlp; using Nethermind.TxPool; namespace Nethermind.Optimism; @@ -61,6 +60,12 @@ public override bool ValidateBodyAgainstHeader(BlockHeader header, BlockBody toB return true; } + protected override bool ValidateEip4844Fields(Block block, IReleaseSpec spec, ref string? error) + { + // Base implementation validates BlobGasUsed, but Blob transactions are disabled in Optimism since Ecotone + return specHelper.IsEcotone(block.Header) || base.ValidateEip4844Fields(block, spec, ref error); + } + protected override bool ValidateWithdrawals(Block block, IReleaseSpec spec, bool validateHashes, ref string? error) => ValidateWithdrawals(block.Header, block.Body, out error); diff --git a/src/Nethermind/Nethermind.Optimism/OptimismChainSpecEngineParameters.cs b/src/Nethermind/Nethermind.Optimism/OptimismChainSpecEngineParameters.cs index 6033a52f3125..7ae4fe2f594a 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; @@ -32,6 +33,8 @@ public class OptimismChainSpecEngineParameters : IChainSpecEngineParameters public ulong? IsthmusTimestamp { get; set; } + public ulong? JovianTimestamp { get; set; } + public Address? L1FeeRecipient { get; set; } public Address? L1BlockAddress { get; set; } @@ -50,9 +53,19 @@ public void ApplyToReleaseSpec(ReleaseSpec spec, long startBlock, ulong? startTi spec.BaseFeeMaxChangeDenominator = CanyonBaseFeeChangeDenominator.Value; } - if (HoloceneTimestamp is not null) + 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) { - spec.BaseFeeCalculator = new OptimismBaseFeeCalculator(HoloceneTimestamp.Value, new DefaultBaseFeeCalculator()); + timestamps.Add(timestamp.Value); } } } diff --git a/src/Nethermind/Nethermind.Optimism/OptimismCostHelper.cs b/src/Nethermind/Nethermind.Optimism/OptimismCostHelper.cs index 95db896a1e45..8d2ff0363288 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismCostHelper.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismCostHelper.cs @@ -42,6 +42,10 @@ public OptimismCostHelper(IOptimismSpecHelper opSpecHelper, OptimismChainSpecEng // Isthmus private readonly StorageCell _operatorFeeParamsSlot = new(l1BlockAddr, new UInt256(8)); + // Jovian + private static readonly UInt256 DaFootprintScalarDefault = 400; + private static readonly UInt256 DaFootprintScale = 1_000_000; + [SkipLocalsInit] public UInt256 ComputeL1Cost(Transaction tx, BlockHeader header, IWorldState worldState) { @@ -129,7 +133,9 @@ public UInt256 ComputeOperatorCost(long gas, BlockHeader header, IWorldState wor break; } - return (UInt256)gas * operatorFee.scalar / 1_000_000 + operatorFee.constant; + return opSpecHelper.IsJovian(header) + ? (UInt256)gas * operatorFee.scalar * 100 + operatorFee.constant // TODO: tests + : (UInt256)gas * operatorFee.scalar / 1_000_000 + operatorFee.constant; static (uint scalar, ulong constant) Parse(scoped ReadOnlySpan span) { @@ -141,6 +147,34 @@ public UInt256 ComputeOperatorCost(long gas, BlockHeader header, IWorldState wor } } + // https://specs.optimism.io/protocol/jovian/exec-engine.html#da-footprint-block-limit + public UInt256 ComputeDaFootprint(Block block) + { + if (block.Transactions.Length == 0) + return 0; + + UInt256 daFootprintScalar = GetDaFootprintScalar(block); + + UInt256 footprint = UInt256.Zero; + foreach (Transaction tx in block.Transactions) + { + if (tx.Type == TxType.DepositTx) + continue; + + UInt256 flzLen = L1CostFastlzCoef * ComputeFlzCompressLen(tx); + UInt256 daUsageEstimate = DaFootprintScale.IsZero ? + default : + UInt256.Max( + MinTransactionSizeScaled, + flzLen > L1CostInterceptNeg ? flzLen - L1CostInterceptNeg : 0 // avoid uint underflow + ) / DaFootprintScale; + + footprint += daUsageEstimate * daFootprintScalar; + } + + return footprint; + } + [SkipLocalsInit] public static UInt256 ComputeDataGas(Transaction tx, bool isRegolith) { @@ -173,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: @@ -289,5 +323,20 @@ 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 + private static UInt256 GetDaFootprintScalar(Block block) + { + var firstTx = block.Transactions.FirstOrDefault(); + if (firstTx?.Type is not TxType.DepositTx) + return DaFootprintScalarDefault; + + if (firstTx.Data.Length < 178) + return DaFootprintScalarDefault; + + var scalar = ReadUInt16BigEndian(firstTx.Data.Span[176..178]); + return scalar == 0 ? DaFootprintScalarDefault : scalar; + } } diff --git a/src/Nethermind/Nethermind.Optimism/OptimismEIP1559Parameters.cs b/src/Nethermind/Nethermind.Optimism/OptimismEIP1559Parameters.cs index e6171edeac51..579bd8c7ee7c 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismEIP1559Parameters.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismEIP1559Parameters.cs @@ -12,11 +12,14 @@ namespace Nethermind.Optimism; public readonly struct EIP1559Parameters { - public const int ByteLength = 9; + public static readonly byte[] ByteLengthByVersion = [9, 17]; public byte Version { get; } public UInt32 Denominator { get; } public UInt32 Elasticity { get; } + public UInt64 MinBaseFee { get; } + + public int ByteLength => ByteLengthByVersion[Version]; public EIP1559Parameters(byte version, UInt32 denominator, UInt32 elasticity) { @@ -25,69 +28,131 @@ public EIP1559Parameters(byte version, UInt32 denominator, UInt32 elasticity) Elasticity = elasticity; } - public static bool TryCreate(byte version, UInt32 denominator, UInt32 elasticity, out EIP1559Parameters parameters, [NotNullWhen(false)] out string? error) + public EIP1559Parameters(byte version, UInt32 denominator, UInt32 elasticity, UInt64 minBaseFee) : this(version, denominator, elasticity) + { + MinBaseFee = minBaseFee; + } + + public static bool TryCreateV0(UInt32 denominator, UInt32 elasticity, out EIP1559Parameters parameters, [NotNullWhen(false)] out string? error) { error = null; parameters = default; - if (version != 0) + if (denominator == 0 && elasticity != 0) { - error = $"{nameof(version)} must be 0"; + error = $"{nameof(denominator)} cannot be 0 unless {nameof(elasticity)} is also 0"; return false; } - if (denominator == 0 && elasticity != 0) + parameters = new EIP1559Parameters(0, denominator, elasticity); + + return true; + } + + public static bool TryCreateV1(uint denominator, uint elasticity, UInt64 minBaseFee, out EIP1559Parameters parameters, + [NotNullWhen(false)] out string? error + ) + { + error = null; + parameters = default; + + if (denominator == 0 && (elasticity != 0 || minBaseFee != 0)) { - error = $"{nameof(denominator)} cannot be 0 unless {nameof(elasticity)} is also 0"; + error = $"{nameof(denominator)} cannot be 0 unless {nameof(elasticity)} and {nameof(minBaseFee)} are also 0"; return false; } - parameters = new EIP1559Parameters(version, denominator, elasticity); + parameters = new EIP1559Parameters(1, denominator, elasticity, minBaseFee); + return true; } - public bool IsZero() => Denominator == 0 && Elasticity == 0; + public bool IsZero() => Denominator == 0 && Elasticity == 0 && MinBaseFee == 0; public void WriteTo(Span span) { span[0] = Version; - BinaryPrimitives.WriteUInt32BigEndian(span.Slice(1, 4), Denominator); - BinaryPrimitives.WriteUInt32BigEndian(span.Slice(5, 4), Elasticity); + BinaryPrimitives.WriteUInt32BigEndian(span.Slice(1, sizeof(UInt32)), Denominator); + BinaryPrimitives.WriteUInt32BigEndian(span.Slice(5, sizeof(UInt32)), Elasticity); + + if (Version >= 1) + BinaryPrimitives.WriteUInt64BigEndian(span.Slice(9, sizeof(UInt64)), MinBaseFee); } + + public override string ToString() => Version == 0 + ? $"{nameof(EIP1559Parameters)}(denominator: {Denominator}, elasticity: {Elasticity})" + : $"{nameof(EIP1559Parameters)}(denominator: {Denominator}, elasticity: {Elasticity}, minBaseFee: {MinBaseFee})"; } public static class EIP1559ParametersExtensions { public static bool TryDecodeEIP1559Parameters(this BlockHeader header, out EIP1559Parameters parameters, [NotNullWhen(false)] out string? error) { - if (header.ExtraData.Length != EIP1559Parameters.ByteLength) + parameters = default; + + ReadOnlySpan data = header.ExtraData; + int dataLength = data.Length; + if (dataLength == 0) { - parameters = default; - error = $"{nameof(header.ExtraData)} data must be {EIP1559Parameters.ByteLength} bytes long"; + error = $"{nameof(header.ExtraData)} must not be empty"; return false; } - ReadOnlySpan extraData = header.ExtraData.AsSpan(); - var version = extraData.TakeAndMove(1)[0]; - var denominator = BinaryPrimitives.ReadUInt32BigEndian(extraData.TakeAndMove(4)); - var elasticity = BinaryPrimitives.ReadUInt32BigEndian(extraData.TakeAndMove(4)); + int maxVersion = EIP1559Parameters.ByteLengthByVersion.Length - 1; + byte version = data.TakeAndMove(1)[0]; + if (version > maxVersion) + { + error = $"{nameof(version)} must be between 0 and {maxVersion}"; + return false; + } + + byte expLength = EIP1559Parameters.ByteLengthByVersion[version]; + if (dataLength != expLength) + { + error = $"{nameof(header.ExtraData)} must be {expLength} bytes long on version {version}"; + return false; + } + + UInt32 denominator = BinaryPrimitives.ReadUInt32BigEndian(data.TakeAndMove(sizeof(UInt32))); + UInt32 elasticity = BinaryPrimitives.ReadUInt32BigEndian(data.TakeAndMove(sizeof(UInt32))); + + if (version == 0) + { + return EIP1559Parameters.TryCreateV0(denominator, elasticity, out parameters, out error); + } - return EIP1559Parameters.TryCreate(version, denominator, elasticity, out parameters, out error); + UInt64 minBaseFee = BinaryPrimitives.ReadUInt64BigEndian(data.TakeAndMove(sizeof(UInt64))); + return EIP1559Parameters.TryCreateV1(denominator, elasticity, minBaseFee, out parameters, out error); } public static bool TryDecodeEIP1559Parameters(this OptimismPayloadAttributes attributes, out EIP1559Parameters parameters, [NotNullWhen(false)] out string? error) { - if (attributes.EIP1559Params?.Length != 8) + parameters = default; + + ReadOnlySpan data = attributes.EIP1559Params; + int dataLength = data.Length; + if (dataLength == 0) + { + error = $"{nameof(attributes.EIP1559Params)} must not be empty"; + return false; + } + + int version = Array.IndexOf(EIP1559Parameters.ByteLengthByVersion, (byte)(dataLength + 1)); + if (version < 0) { - parameters = default; - error = $"{nameof(attributes.EIP1559Params)} must be 8 bytes long"; + error = $"{nameof(attributes.EIP1559Params)} has invalid length"; return false; } - ReadOnlySpan span = attributes.EIP1559Params.AsSpan(); - var denominator = BinaryPrimitives.ReadUInt32BigEndian(span.TakeAndMove(4)); - var elasticity = BinaryPrimitives.ReadUInt32BigEndian(span.TakeAndMove(4)); + UInt32 denominator = BinaryPrimitives.ReadUInt32BigEndian(data.TakeAndMove(sizeof(UInt32))); + UInt32 elasticity = BinaryPrimitives.ReadUInt32BigEndian(data.TakeAndMove(sizeof(UInt32))); + + if (version == 0) + { + return EIP1559Parameters.TryCreateV0(denominator, elasticity, out parameters, out error); + } - return EIP1559Parameters.TryCreate(0, denominator, elasticity, out parameters, out error); + UInt64 minBaseFee = BinaryPrimitives.ReadUInt64BigEndian(data.TakeAndMove(sizeof(UInt64))); + return EIP1559Parameters.TryCreateV1(denominator, elasticity, minBaseFee, out parameters, out error); } } diff --git a/src/Nethermind/Nethermind.Optimism/OptimismEthereumEcdsa.cs b/src/Nethermind/Nethermind.Optimism/OptimismEthereumEcdsa.cs index b72cf8e29fbb..9c3becea371f 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismEthereumEcdsa.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismEthereumEcdsa.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Crypto; diff --git a/src/Nethermind/Nethermind.Optimism/OptimismHeaderValidator.cs b/src/Nethermind/Nethermind.Optimism/OptimismHeaderValidator.cs index 18d8845935b8..b3b9dea68f9c 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismHeaderValidator.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismHeaderValidator.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Diagnostics.CodeAnalysis; using Nethermind.Blockchain; using Nethermind.Consensus; using Nethermind.Consensus.Validators; @@ -52,6 +51,19 @@ protected override bool Validate(BlockHeader header, BlockHeader? par error = $"{nameof(EIP1559Parameters)} is zero"; return false; } + + (int version, string reason) versionCheck = header switch + { + // Newer forks should be added on top + _ when specHelper.IsJovian(header) => (1, "since Jovian"), + _ => (0, "before Jovian") + }; + + if (versionCheck.version != parameters.Version) + { + error = $"{nameof(EIP1559Parameters)} version should be {versionCheck.version} {versionCheck.reason}"; + return false; + } } return base.Validate(header, parent, isUncle, out error); @@ -73,8 +85,37 @@ protected override bool ValidateRequestsHash(BlockHeader header, IReleaseSpec sp protected override bool ValidateGasLimitRange(BlockHeader header, BlockHeader parent, IReleaseSpec spec, ref string? error) => true; + protected override bool ValidateBlobGasFields(BlockHeader header, BlockHeader parent, IReleaseSpec spec, ref string? error) + { + if (!base.ValidateBlobGasFields(header, parent, spec, ref error)) + return false; + + if (specHelper.IsJovian(header)) + { + if (header.BlobGasUsed is not { } blobGasUsed) + { + if (_logger.IsWarn) _logger.Warn($"Invalid block header ({header.Hash}) - no DA footprint in {nameof(header.BlobGasUsed)}"); + error = ErrorMessages.DaFootprintMissing; + return false; + } + + if (blobGasUsed > (ulong)header.GasLimit) + { + if (_logger.IsWarn) _logger.Warn($"Invalid block header ({header.Hash}) - gas used above gas limit"); + error = ErrorMessages.DaFootprintExceededGasLimit; + return false; + } + } + + return true; + } + + protected override ulong? CalculateExcessBlobGas(BlockHeader parent, IReleaseSpec spec) => 0; + private static class ErrorMessages { public static readonly string RequestHashShouldBeOfShaOfEmpty = $"{nameof(BlockHeader.RequestsHash)} should be {OptimismPostMergeBlockProducer.PostIsthmusRequestHash} for post-Isthmus blocks"; + public const string DaFootprintMissing = $"InvalidBlobGasUsed: DA footprint is missing from the block header {nameof(BlockHeader.BlobGasUsed)}."; + public const string DaFootprintExceededGasLimit = "ExceededGasLimit: DA footprint exceeds gas limit."; } } diff --git a/src/Nethermind/Nethermind.Optimism/OptimismLegacyTxDecoder.cs b/src/Nethermind/Nethermind.Optimism/OptimismLegacyTxDecoder.cs index 73c69369e5c0..d892fa8795c6 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/OptimismPayloadPreparationService.cs b/src/Nethermind/Nethermind.Optimism/OptimismPayloadPreparationService.cs index 38d86e14944a..1d23e69d630f 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismPayloadPreparationService.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismPayloadPreparationService.cs @@ -3,7 +3,6 @@ using System; using System.Threading; -using Autofac.Features.AttributeFilters; using Nethermind.Config; using Nethermind.Consensus; using Nethermind.Consensus.Producers; @@ -63,7 +62,7 @@ protected override void ImproveBlock(string payloadId, BlockHeader parentHeader, eip1559Parameters = new EIP1559Parameters(eip1559Parameters.Version, (UInt32)spec.BaseFeeMaxChangeDenominator, (UInt32)spec.ElasticityMultiplier); } - currentBestBlock.Header.ExtraData = new byte[EIP1559Parameters.ByteLength]; + currentBestBlock.Header.ExtraData = new byte[eip1559Parameters.ByteLength]; eip1559Parameters.WriteTo(currentBestBlock.Header.ExtraData); // NOTE: Since we updated the `Header` we need to recalculate the hash. diff --git a/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs b/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs index 0e0615b52e48..1f6794a6b6de 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs @@ -12,20 +12,14 @@ using Nethermind.Consensus.Producers; using Nethermind.Merge.Plugin; using Nethermind.Merge.Plugin.BlockProduction; -using Nethermind.Merge.Plugin.GC; -using Nethermind.Merge.Plugin.Handlers; using Nethermind.JsonRpc.Modules; using Nethermind.Config; using Nethermind.Logging; -using Nethermind.Blockchain.Synchronization; -using Nethermind.Merge.Plugin.InvalidChainTracker; using Nethermind.Blockchain; -using Nethermind.Blockchain.Receipts; using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.Merge.Plugin.Synchronization; -using Nethermind.HealthChecks; using Nethermind.Init.Steps; using Nethermind.Optimism.CL; using Nethermind.Specs.ChainSpecStyle; @@ -40,7 +34,6 @@ using Nethermind.Core.Specs; using Nethermind.Crypto; using Nethermind.Evm.TransactionProcessing; -using Nethermind.Facade.Simulate; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Optimism.CL.Decoding; using Nethermind.Optimism.CL.Derivation; @@ -55,8 +48,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 +99,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 +118,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/OptimismPostMergeBlockProducer.cs b/src/Nethermind/Nethermind.Optimism/OptimismPostMergeBlockProducer.cs index 5824e2a05544..0855e48f3033 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismPostMergeBlockProducer.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismPostMergeBlockProducer.cs @@ -35,7 +35,7 @@ public OptimismPostMergeBlockProducer( ISpecProvider specProvider, IOptimismSpecHelper specHelper, ILogManager logManager, - IBlocksConfig? miningConfig) : base( + IBlocksConfig? blocksConfig) : base( payloadAttrsTxSource.Then(txPoolTxSource), processor, blockTree, @@ -45,7 +45,7 @@ public OptimismPostMergeBlockProducer( timestamper, specProvider, logManager, - miningConfig) + blocksConfig) { _payloadAttrsTxSource = payloadAttrsTxSource; _specHelper = specHelper; diff --git a/src/Nethermind/Nethermind.Optimism/OptimismReceiptMessageDecoder.cs b/src/Nethermind/Nethermind.Optimism/OptimismReceiptMessageDecoder.cs index 75c9265a6a84..dfb4dbb60df7 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismReceiptMessageDecoder.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismReceiptMessageDecoder.cs @@ -11,13 +11,13 @@ namespace Nethermind.Optimism; [Rlp.Decoder(RlpDecoderKey.Trie)] -public class OptimismReceiptTrieDecoder() : OptimismReceiptMessageDecoder(true); +public sealed class OptimismReceiptTrieDecoder() : OptimismReceiptMessageDecoder(true); [Rlp.Decoder] -public class OptimismReceiptMessageDecoder(bool isEncodedForTrie = false, bool skipStateAndStatus = false) : IRlpStreamDecoder +public class OptimismReceiptMessageDecoder(bool isEncodedForTrie = false, bool skipStateAndStatus = false) : RlpStreamDecoder { private readonly bool _skipStateAndStatus = skipStateAndStatus; - public OptimismTxReceipt Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override OptimismTxReceipt DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { OptimismTxReceipt txReceipt = new(); if (!rlpStream.IsSequenceNext()) @@ -49,6 +49,7 @@ public OptimismTxReceipt Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = int logEntriesCheck = rlpStream.ReadSequenceLength() + rlpStream.Position; int numberOfReceipts = rlpStream.PeekNumberOfItemsRemaining(logEntriesCheck); + rlpStream.GuardLimit(numberOfReceipts); LogEntry[] entries = new LogEntry[numberOfReceipts]; for (int i = 0; i < numberOfReceipts; i++) { @@ -125,10 +126,10 @@ public static int GetLogsLength(TxReceipt item) /// /// https://eips.ethereum.org/EIPS/eip-2718 /// - public int GetLength(TxReceipt item, RlpBehaviors rlpBehaviors) + public override int GetLength(TxReceipt item, RlpBehaviors rlpBehaviors) { - (int Total, _) = GetContentLength(item, rlpBehaviors); - int receiptPayloadLength = Rlp.LengthOfSequence(Total); + (int total, _) = GetContentLength(item, rlpBehaviors); + int receiptPayloadLength = Rlp.LengthOfSequence(total); bool isForTxRoot = (rlpBehaviors & RlpBehaviors.SkipTypedWrapping) == RlpBehaviors.SkipTypedWrapping; int result = item.TxType != TxType.Legacy @@ -139,7 +140,7 @@ public int GetLength(TxReceipt item, RlpBehaviors rlpBehaviors) return result; } - public void Encode(RlpStream rlpStream, TxReceipt item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream rlpStream, TxReceipt item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) { @@ -188,11 +189,6 @@ public void Encode(RlpStream rlpStream, TxReceipt item, RlpBehaviors rlpBehavior } } } - - TxReceipt IRlpStreamDecoder.Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors) - { - return Decode(rlpStream, rlpBehaviors); - } } internal static class TxReceiptExt diff --git a/src/Nethermind/Nethermind.Optimism/OptimismReceiptStorageDecoder.cs b/src/Nethermind/Nethermind.Optimism/OptimismReceiptStorageDecoder.cs index 034ec8150584..59f4d41627b9 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismReceiptStorageDecoder.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismReceiptStorageDecoder.cs @@ -41,7 +41,7 @@ public OptimismTxReceipt Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = int sequenceLength = rlpStream.ReadSequenceLength(); int logEntriesCheck = sequenceLength + rlpStream.Position; - using ArrayPoolList logEntries = new(sequenceLength * 2 / LengthOfAddressRlp); + using ArrayPoolListRef logEntries = new(sequenceLength * 2 / LengthOfAddressRlp); while (rlpStream.Position < logEntriesCheck) { @@ -104,7 +104,7 @@ public OptimismTxReceipt Decode(ref ValueDecoderContext decoderContext, int logEntriesCheck = sequenceLength + decoderContext.Position; // Don't know the size exactly, I'll just assume its just an address and add some margin - using ArrayPoolList logEntries = new(sequenceLength * 2 / LengthOfAddressRlp); + using ArrayPoolListRef logEntries = new(sequenceLength * 2 / LengthOfAddressRlp); while (decoderContext.Position < logEntriesCheck) { logEntries.Add(CompactLogEntryDecoder.Decode(ref decoderContext, RlpBehaviors.AllowExtraBytes)!); @@ -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,9 +164,8 @@ 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 logsBytes = ContentLength + PrefixLength; + (int prefixLength, int contentLength) = decoderContext.PeekPrefixAndContentLength(); + int logsBytes = contentLength + prefixLength; item.LogsRlp = decoderContext.Data.Slice(decoderContext.Position, logsBytes); if (lastCheck > decoderContext.Position) diff --git a/src/Nethermind/Nethermind.Optimism/OptimismSynchronizerModule.cs b/src/Nethermind/Nethermind.Optimism/OptimismSynchronizerModule.cs index d331fbbaaaee..29ecf1e047d2 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 5108f8492620..2538b74c1ef3 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs @@ -1,9 +1,10 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only 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; @@ -45,7 +46,7 @@ protected override TransactionResult Execute(Transaction tx, ITxTracer tracer, E TransactionResult result = base.Execute(tx, tracer, opts); - if (!result && tx.IsDeposit() && result.Error != "block gas limit exceeded") + if (!result && tx.IsDeposit() && result.Error != TransactionResult.ErrorType.BlockGasLimitExceeded) { // deposit tx should be included WorldState.Restore(snapshot); @@ -58,7 +59,7 @@ protected override TransactionResult Execute(Transaction tx, ITxTracer tracer, E WorldState.IncrementNonce(tx.SenderAddress!); } header.GasUsed += tx.GasLimit; - tracer.MarkAsFailed(tx.To!, tx.GasLimit, [], $"failed deposit: {result.Error}"); + tracer.MarkAsFailed(tx.To!, tx.GasLimit, [], $"failed deposit: {result.ErrorDescription}"); result = TransactionResult.Ok; } @@ -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.Optimism/ProtocolVersion/OptimismProtocolVersion.cs b/src/Nethermind/Nethermind.Optimism/ProtocolVersion/OptimismProtocolVersion.cs index ee68603d41d2..274c7a16359f 100644 --- a/src/Nethermind/Nethermind.Optimism/ProtocolVersion/OptimismProtocolVersion.cs +++ b/src/Nethermind/Nethermind.Optimism/ProtocolVersion/OptimismProtocolVersion.cs @@ -3,7 +3,6 @@ using System; using System.Buffers.Binary; -using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; using Nethermind.Core.Extensions; diff --git a/src/Nethermind/Nethermind.Optimism/ProtocolVersion/OptimismSuperchainSignal.cs b/src/Nethermind/Nethermind.Optimism/ProtocolVersion/OptimismSuperchainSignal.cs index aa970d6ebfb9..f625bdaae0ee 100644 --- a/src/Nethermind/Nethermind.Optimism/ProtocolVersion/OptimismSuperchainSignal.cs +++ b/src/Nethermind/Nethermind.Optimism/ProtocolVersion/OptimismSuperchainSignal.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Threading.Tasks; using Nethermind.Logging; namespace Nethermind.Optimism.ProtocolVersion; diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/DepositTransactionForRpc.cs b/src/Nethermind/Nethermind.Optimism/Rpc/DepositTransactionForRpc.cs index 157604b7bd36..f68810b14d01 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/DepositTransactionForRpc.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/DepositTransactionForRpc.cs @@ -47,9 +47,10 @@ public class DepositTransactionForRpc : TransactionForRpc, IFromTransaction ToTransaction(bool validateUserInput = false) { - var tx = base.ToTransaction(); + Result baseResult = base.ToTransaction(validateUserInput); + if (baseResult.IsError) return baseResult; + Transaction tx = baseResult.Data; tx.SourceHash = SourceHash ?? throw new ArgumentNullException(nameof(SourceHash)); tx.SenderAddress = From ?? throw new ArgumentNullException(nameof(From)); tx.To = To; @@ -91,6 +94,6 @@ public override void EnsureDefaults(long? gasCap) public override bool ShouldSetBaseFee() => false; - public static DepositTransactionForRpc FromTransaction(Transaction tx, TransactionConverterExtraData extraData) - => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber, receipt: extraData.Receipt as OptimismTxReceipt); + public static DepositTransactionForRpc FromTransaction(Transaction tx, in TransactionForRpcContext extraData) + => new(tx, extraData); } diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/IOptimismEngineRpcModule.cs b/src/Nethermind/Nethermind.Optimism/Rpc/IOptimismEngineRpcModule.cs index b49f50f5dd86..5e5f36012db7 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/IOptimismEngineRpcModule.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/IOptimismEngineRpcModule.cs @@ -81,7 +81,7 @@ Task> engine_newPayloadV3(ExecutionPayloadV3 exec Description = "Returns the most recent version of an execution payload and fees with respect to the transaction set contained by the mempool.", IsSharable = true, IsImplemented = true)] - public Task> engine_getPayloadV4(byte[] payloadId); + public Task> engine_getPayloadV4(byte[] payloadId); [JsonRpcMethod( Description = "Signals which protocol version is recommended and required.", diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEngineRpcModule.cs b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEngineRpcModule.cs index 3a24313ded59..2af6ad620189 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEngineRpcModule.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEngineRpcModule.cs @@ -70,9 +70,10 @@ public Task> engine_newPayloadV4(OptimismExecutio return _engineRpcModule.engine_newPayloadV4(executionPayload, blobVersionedHashes, parentBeaconBlockRoot, executionRequests); } - public Task> engine_getPayloadV4(byte[] payloadId) + public async Task> engine_getPayloadV4(byte[] payloadId) { - return _engineRpcModule.engine_getPayloadV4(payloadId); + ResultWrapper result = await _engineRpcModule.engine_getPayloadV4(payloadId); + return ResultWrapper.From(result, result.Data is null ? null : new OptimismGetPayloadV4Result(result.Data)); } public ResultWrapper engine_signalSuperchainV1(OptimismSuperchainSignal signal) diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs index aa6db5df906f..ad5e85acdcdc 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs @@ -17,6 +17,7 @@ using Nethermind.Config; using Nethermind.Core; using Nethermind.Crypto; +using Nethermind.Db.LogIndex; using Nethermind.JsonRpc.Client; using Nethermind.Network; using Nethermind.Serialization.Json; @@ -44,6 +45,7 @@ public class OptimismEthModuleFactory : ModuleFactoryBase private readonly IOptimismSpecHelper _opSpecHelper; private readonly IProtocolsManager _protocolsManager; private readonly IForkInfo _forkInfo; + private readonly ILogIndexConfig _logIndexConfig; private readonly ulong? _secondsPerSlot; private readonly IJsonRpcClient? _sequencerRpcClient; @@ -67,7 +69,8 @@ public OptimismEthModuleFactory(IJsonRpcConfig rpcConfig, IOptimismSpecHelper opSpecHelper, IOptimismConfig config, IJsonSerializer jsonSerializer, - ITimestamper timestamper + ITimestamper timestamper, + ILogIndexConfig logIndexConfig ) { _secondsPerSlot = blocksConfig.SecondsPerSlot; @@ -88,6 +91,7 @@ ITimestamper timestamper _opSpecHelper = opSpecHelper; _protocolsManager = protocolsManager; _forkInfo = forkInfo; + _logIndexConfig = logIndexConfig; ILogger logger = logManager.GetClassLogger(); if (config.SequencerUrl is null && logger.IsWarn) { @@ -123,10 +127,10 @@ public override IOptimismEthRpcModule Create() _protocolsManager, _forkInfo, _secondsPerSlot, - _sequencerRpcClient, _ecdsa, _sealer, + _logIndexConfig, _opSpecHelper ); } diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs index 4bb577f5003c..e8befe91bc7a 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs @@ -10,6 +10,7 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Crypto; +using Nethermind.Db.LogIndex; using Nethermind.Evm; using Nethermind.Facade; using Nethermind.Facade.Eth; @@ -32,58 +33,46 @@ namespace Nethermind.Optimism.Rpc; -public class OptimismEthRpcModule : EthRpcModule, IOptimismEthRpcModule +public class OptimismEthRpcModule( + IJsonRpcConfig rpcConfig, + IBlockchainBridge blockchainBridge, + IBlockFinder blockFinder, + IReceiptFinder receiptFinder, + IStateReader stateReader, + ITxPool txPool, + ITxSender txSender, + IWallet wallet, + ILogManager logManager, + ISpecProvider specProvider, + IGasPriceOracle gasPriceOracle, + IEthSyncingInfo ethSyncingInfo, + IFeeHistoryOracle feeHistoryOracle, + IProtocolsManager protocolsManager, + IForkInfo forkInfo, + ulong? secondsPerSlot, + IJsonRpcClient? sequencerRpcClient, + IEthereumEcdsa ecdsa, + ITxSealer sealer, + ILogIndexConfig? logIndexConfig, + IOptimismSpecHelper opSpecHelper) + : EthRpcModule(rpcConfig, + blockchainBridge, + blockFinder, + receiptFinder, + stateReader, + txPool, + txSender, + wallet, + logManager, + specProvider, + gasPriceOracle, + ethSyncingInfo, + feeHistoryOracle, + protocolsManager, + forkInfo, + logIndexConfig, + secondsPerSlot), IOptimismEthRpcModule { - private readonly IJsonRpcClient? _sequencerRpcClient; - private readonly IEthereumEcdsa _ecdsa; - private readonly ITxSealer _sealer; - private readonly IOptimismSpecHelper _opSpecHelper; - - public OptimismEthRpcModule( - IJsonRpcConfig rpcConfig, - IBlockchainBridge blockchainBridge, - IBlockFinder blockFinder, - IReceiptFinder receiptFinder, - IStateReader stateReader, - ITxPool txPool, - ITxSender txSender, - IWallet wallet, - ILogManager logManager, - ISpecProvider specProvider, - IGasPriceOracle gasPriceOracle, - IEthSyncingInfo ethSyncingInfo, - IFeeHistoryOracle feeHistoryOracle, - IProtocolsManager protocolsManager, - IForkInfo forkInfo, - ulong? secondsPerSlot, - - IJsonRpcClient? sequencerRpcClient, - IEthereumEcdsa ecdsa, - ITxSealer sealer, - IOptimismSpecHelper opSpecHelper) : base( - rpcConfig, - blockchainBridge, - blockFinder, - receiptFinder, - stateReader, - txPool, - txSender, - wallet, - logManager, - specProvider, - gasPriceOracle, - ethSyncingInfo, - feeHistoryOracle, - protocolsManager, - forkInfo, - secondsPerSlot) - { - _sequencerRpcClient = sequencerRpcClient; - _ecdsa = ecdsa; - _sealer = sealer; - _opSpecHelper = opSpecHelper; - } - public override ResultWrapper eth_getBlockReceipts(BlockParameter blockParameter) { SearchResult searchResult = _blockFinder.SearchForBlock(blockParameter); @@ -96,7 +85,7 @@ public OptimismEthRpcModule( TxReceipt[] receipts = _receiptFinder.Get(block) ?? new TxReceipt[block.Transactions.Length]; IReleaseSpec spec = _specProvider.GetSpec(block.Header); - L1BlockGasInfo l1BlockGasInfo = new(block, _opSpecHelper); + L1BlockGasInfo l1BlockGasInfo = new(block, opSpecHelper); OptimismReceiptForRpc[]? result = [.. receipts .Zip(block.Transactions, (receipt, tx) => @@ -119,34 +108,34 @@ receipt is OptimismTxReceipt optimismTxReceipt public override async Task> eth_sendTransaction(TransactionForRpc rpcTx) { - Transaction tx = rpcTx.ToTransaction(); + Result txResult = rpcTx.ToTransaction(validateUserInput: true); + if (!txResult.Success(out Transaction? tx, out string? error)) + { + return ResultWrapper.Fail(error, ErrorCodes.InvalidInput); + } + tx.ChainId = _blockchainBridge.GetChainId(); - tx.SenderAddress ??= _ecdsa.RecoverAddress(tx); + tx.SenderAddress ??= ecdsa.RecoverAddress(tx); if (tx.SenderAddress is null) { return ResultWrapper.Fail("Failed to recover sender"); } - await _sealer.Seal(tx, TxHandlingOptions.None); + await sealer.Seal(tx, TxHandlingOptions.None); return await eth_sendRawTransaction(Rlp.Encode(tx, RlpBehaviors.SkipTypedWrapping).Bytes); } public override async Task> eth_sendRawTransaction(byte[] transaction) { - if (_sequencerRpcClient is null) + if (sequencerRpcClient is null) { return await base.eth_sendRawTransaction(transaction); } - Hash256? result = await _sequencerRpcClient.Post(nameof(eth_sendRawTransaction), transaction); - if (result is null) - { - return ResultWrapper.Fail("Failed to forward transaction"); - } - - return ResultWrapper.Success(result); + Hash256? result = await sequencerRpcClient.Post(nameof(eth_sendRawTransaction), transaction); + return result is null ? ResultWrapper.Fail("Failed to forward transaction") : ResultWrapper.Success(result); } public override ResultWrapper eth_getTransactionReceipt(Hash256 txHash) @@ -164,7 +153,7 @@ public override async Task> eth_sendRawTransaction(byte[] } Block block = foundBlock.Object; - L1BlockGasInfo l1GasInfo = new L1BlockGasInfo(block, _opSpecHelper); + L1BlockGasInfo l1GasInfo = new L1BlockGasInfo(block, opSpecHelper); OptimismReceiptForRpc result = receipt is OptimismTxReceipt optimismTxReceipt ? new OptimismReceiptForRpc( @@ -185,17 +174,22 @@ receipt is OptimismTxReceipt optimismTxReceipt public override ResultWrapper eth_getTransactionByHash(Hash256 transactionHash) { - (TxReceipt? receipt, Transaction? transaction, UInt256? baseFee) = _blockchainBridge.GetTransaction(transactionHash, checkTxnPool: true); - if (transaction is null) + if (!_blockchainBridge.TryGetTransaction(transactionHash, out TransactionLookupResult? transactionResult, checkTxnPool: true)) { return ResultWrapper.Success(null); } + TransactionLookupResult result = transactionResult!.Value; + Transaction transaction = result.Transaction!; + RecoverTxSenderIfNeeded(transaction); - TransactionForRpc transactionModel = TransactionForRpc.FromTransaction(transaction: transaction, blockHash: receipt?.BlockHash, blockNumber: receipt?.BlockNumber, txIndex: receipt?.Index, baseFee: baseFee, chainId: _specProvider.ChainId); + TransactionForRpcContext extraData = result.ExtraData; + TransactionForRpc transactionModel = TransactionForRpc.FromTransaction( + transaction: transaction, + extraData: extraData); if (transactionModel is DepositTransactionForRpc depositTx) { - depositTx.DepositReceiptVersion = (receipt as OptimismTxReceipt)?.DepositReceiptVersion; + depositTx.DepositReceiptVersion = (extraData.Receipt as OptimismTxReceipt)?.DepositReceiptVersion; } if (_logger.IsTrace) _logger.Trace($"eth_getTransactionByHash request {transactionHash}, result: {transactionModel.Hash}"); return ResultWrapper.Success(transactionModel); @@ -222,7 +216,16 @@ receipt is OptimismTxReceipt optimismTxReceipt .Get(block) .FirstOrDefault(r => r.TxHash == transaction.Hash); - TransactionForRpc transactionModel = TransactionForRpc.FromTransaction(transaction: transaction, blockHash: receipt?.BlockHash, blockNumber: receipt?.BlockNumber, txIndex: receipt?.Index, baseFee: block.BaseFeePerGas, chainId: _specProvider.ChainId); + TransactionForRpc transactionModel = TransactionForRpc.FromTransaction( + transaction, + new( + chainId: _specProvider.ChainId, + blockHash: block.Hash!, + blockNumber: block.Number, + txIndex: (int)positionIndex, + blockTimestamp: block.Timestamp, + baseFee: block.BaseFeePerGas, + receipt: receipt)); if (transactionModel is DepositTransactionForRpc depositTx) { depositTx.DepositReceiptVersion = (receipt as OptimismTxReceipt)?.DepositReceiptVersion; @@ -264,10 +267,15 @@ receipt is OptimismTxReceipt optimismTxReceipt { Transaction tx = transactions[i]; TransactionForRpc rpcTx = TransactionForRpc.FromTransaction( - transaction: tx, - blockHash: block.Hash, - blockNumber: block.Number, - txIndex: i); + tx, + new( + chainId: _specProvider.ChainId, + blockHash: block.Hash!, + blockNumber: block.Number, + txIndex: i, + blockTimestamp: block.Timestamp, + baseFee: block.BaseFeePerGas, + receipt: receipts.FirstOrDefault(r => r.TxHash?.Equals(tx.Hash) ?? false))); if (rpcTx is DepositTransactionForRpc depositTx) { diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismGetPayloadV3Result.cs b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismGetPayloadV3Result.cs index 17190524b3a8..3c22677bc95d 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismGetPayloadV3Result.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismGetPayloadV3Result.cs @@ -9,7 +9,7 @@ namespace Nethermind.Optimism.Rpc; public class OptimismGetPayloadV3Result { - public OptimismGetPayloadV3Result(GetPayloadV3Result result) + public OptimismGetPayloadV3Result(GetPayloadV3Result result) { ExecutionPayload = result.ExecutionPayload; BlockValue = result.BlockValue; diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismGetPayloadV4Result.cs b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismGetPayloadV4Result.cs new file mode 100644 index 000000000000..06180ed5e28a --- /dev/null +++ b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismGetPayloadV4Result.cs @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Merge.Plugin.Data; + +namespace Nethermind.Optimism.Rpc; + +public class OptimismGetPayloadV4Result(GetPayloadV4Result result) : OptimismGetPayloadV3Result(result) +{ + public byte[][]? ExecutionRequests { get; } = result.ExecutionRequests; + + public override string ToString() => + $"{{ExecutionPayload: {ExecutionPayload}, Fees: {BlockValue}, BlobsBundle blobs count: {BlobsBundle.Blobs.Length}, ShouldOverrideBuilder {ShouldOverrideBuilder}, ExecutionRequests count : {ExecutionRequests?.Length}}}"; +} diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismPayloadAttributes.cs b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismPayloadAttributes.cs index aa5b6925aa10..9d504265c987 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismPayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismPayloadAttributes.cs @@ -120,10 +120,25 @@ public override PayloadAttributesValidationResult Validate(ISpecProvider specPro error = $"{nameof(EIP1559Params)} should be null before Holocene"; return PayloadAttributesValidationResult.InvalidPayloadAttributes; } - if (releaseSpec.IsOpHoloceneEnabled && !this.TryDecodeEIP1559Parameters(out _, out var decodeError)) + if (releaseSpec.IsOpHoloceneEnabled) { - error = decodeError; - return PayloadAttributesValidationResult.InvalidPayloadAttributes; + if (!this.TryDecodeEIP1559Parameters(out EIP1559Parameters parameters, out var decodeError)) + { + error = decodeError; + return PayloadAttributesValidationResult.InvalidPayloadAttributes; + } + + (int version, string reason) versionCheck = parameters switch + { + // Newer forks should be added on top + _ when releaseSpec.IsOpJovianEnabled => (1, "since Jovian"), + _ => (0, "before Jovian") + }; + if (versionCheck.version != parameters.Version) + { + error = $"{nameof(EIP1559Params)} version should be {versionCheck.version} {versionCheck.reason}"; + return PayloadAttributesValidationResult.InvalidPayloadAttributes; + } } try diff --git a/src/Nethermind/Nethermind.Overseer.Test/AuRaTest.cs b/src/Nethermind/Nethermind.Overseer.Test/AuRaTest.cs deleted file mode 100644 index e075e10530ad..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/AuRaTest.cs +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Linq; -using System.Threading.Tasks; -using FluentAssertions; -using Nethermind.Overseer.Test.Framework; - -using NUnit.Framework; - -namespace Nethermind.Overseer.Test -{ - [Explicit] - public class AuRaTests : TestBuilder - { - [SetUp] - public void Setup() - { - } - - [Test] - public async Task One_validator() - { - StartAuRaMiner("auraval1", "0xcff9b5a51f50cfddbbd227a273c769164dfe6b6185b56f63e4eb2c545bf5ca38") - .Wait(5000) - .Kill("auraval1"); - - await ScenarioCompletion; - } - - [Test] - public async Task Multiple_validators() - { - (string Name, string Address, string PrivateKey)[] validators = new (string Name, string Address, string PrivateKey)[] - { - ("auraval11", "0x557abc72a6594d1bd9a655a1cb58a595526416c8", "0xcff9b5a51f50cfddbbd227a273c769164dfe6b6185b56f63e4eb2c545bf5ca38"), - ("auraval22", "0x69399093be61566a1c86b09bd02612c6bf31214f", "0xcb807c162517bfb179adfeee0d440b81e0bba770e377be4f887e0a4e6c27575d"), - ("auraval33", "0x4cb87ff61e0e3f9f4043f69fe391a62b5a018b97", "0x2429abae64ce7db0f75941082dc6fa1de10c48a7907f29f54c1c1e9f5bd2baf3"), - }; - - var auRaState = new AuRaState(); - - var context = - StartAuRaMiner(validators[0].Name, validators[0].PrivateKey) - .StartAuRaMiner(validators[1].Name, validators[1].PrivateKey) - .StartAuRaMiner(validators[2].Name, validators[2].PrivateKey) - .SetContext(new AuRaContext(auRaState)) - .Wait(40000) - .SwitchNode(validators[1].Name) - .ReadBlockNumber(); - - await ScenarioCompletion; - - context.ReadBlockAuthors() - .LeaveContext() - .KillAll(); - - await ScenarioCompletion; - - var expectedCount = 14; - - auRaState.BlocksCount.Should().BeGreaterThanOrEqualTo(expectedCount, $"at least {expectedCount} steps."); - - var blockNumbers = auRaState.Blocks.Take(expectedCount).Select(v => v.Key); - blockNumbers.Should().BeEquivalentTo(Enumerable.Range(1, expectedCount), "block numbers sequential from 1."); - - var steps = auRaState.Blocks.Take(expectedCount).Select(v => v.Value.Step); - var startStep = auRaState.Blocks.First().Value.Step; - steps.Should().BeEquivalentTo(Enumerable.Range(0, expectedCount).Select(i => i + startStep), $"steps sequential from {startStep}."); - - var authors = auRaState.Blocks.Take(expectedCount).Select(v => v.Value.Author).Distinct(); - authors.Should().Contain(validators.Select(v => v.Address), "each validator produced a block."); - } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/BasicTests.cs b/src/Nethermind/Nethermind.Overseer.Test/BasicTests.cs deleted file mode 100644 index 97b89eae4c90..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/BasicTests.cs +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Threading.Tasks; -using Nethermind.Overseer.Test.Framework; -using NUnit.Framework; - -namespace Nethermind.Overseer.Test -{ - [Explicit] - public class BasicTests : TestBuilder - { - [SetUp] - public void Setup() - { - } - - [Test] - public async Task Test1() - { - StartCliqueNode("basicnode1") - .Wait(3000) - .Kill(); - - await ScenarioCompletion; - } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/CliqueTests.cs b/src/Nethermind/Nethermind.Overseer.Test/CliqueTests.cs deleted file mode 100644 index f1583b6339b4..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/CliqueTests.cs +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Threading.Tasks; -using Nethermind.Core.Extensions; -using Nethermind.Crypto; -using Nethermind.Facade.Eth.RpcTransaction; -using Nethermind.Overseer.Test.Framework; -using NUnit.Framework; - -namespace Nethermind.Overseer.Test -{ - [Explicit] - public class CliqueTests : TestBuilder - { - [SetUp] - public void Setup() - { - } - - [Test] - public async Task One_validator() - { - StartCliqueMiner("cliqueval1a") - .Wait(5000) - .Kill("cliqueval1a"); - - await ScenarioCompletion; - } - - [Test] - public async Task Two_validators() - { - StartCliqueMiner("cliqueval1b") - .StartCliqueMiner("cliqueval2b") - .Wait(10000) - .Kill("cliqueval1b") - .Kill("cliqueval2b"); - - await ScenarioCompletion; - } - - [Test] - public async Task Clique_vote() - { - StartCliqueMiner("cliqueval1c") - .StartCliqueMiner("cliqueval2c") - .StartCliqueMiner("cliqueval3c") - .StartCliqueMiner("cliqueval4c") - .StartCliqueMiner("cliqueval5c") - .StartCliqueMiner("cliqueval6c") - .StartCliqueMiner("cliqueval7c") - .StartCliqueNode("cliquenode1c") - .SetContext(new CliqueContext(new CliqueState())) - .Wait(20000) - .SwitchNode("cliqueval1c") - .Propose(Nodes["cliquenode1c"].Address, true) - .SwitchNode("cliqueval2c") - .Propose(Nodes["cliquenode1c"].Address, true) - .SwitchNode("cliqueval5c") - .Propose(Nodes["cliquenode1c"].Address, true) - .SwitchNode("cliqueval7c") - .Propose(Nodes["cliquenode1c"].Address, true) - .Wait(10000) - .LeaveContext() - .KillAll(); - - await ScenarioCompletion; - } - - [Test] - public async Task Clique_transaction_broadcast() - { - var tx = new LegacyTransactionForRpc - { - Value = 2.Ether(), - GasPrice = 20.GWei(), - Gas = 21000, - From = new PrivateKey(new byte[32] { - 0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,3 - }).Address, - To = new PrivateKey(new byte[32] { - 0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,1 - }).Address - }; - - StartCliqueMiner("cliqueval1d") - .StartCliqueMiner("cliqueval2d") - .StartCliqueNode("cliquenode3d") - .SetContext(new CliqueContext(new CliqueState())) - .Wait(5000) - .SendTransaction(tx) - .Wait(10000) - .LeaveContext() - .KillAll(); - - await ScenarioCompletion; - } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/Framework/AuRaContext.cs b/src/Nethermind/Nethermind.Overseer.Test/Framework/AuRaContext.cs deleted file mode 100644 index 89eb5ed9d429..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/Framework/AuRaContext.cs +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Overseer.Test.JsonRpc; -using Newtonsoft.Json.Linq; - -namespace Nethermind.Overseer.Test.Framework -{ - public class AuRaContext : TestContextBase - { - public AuRaContext(AuRaState state) : base(state) - { - } - - public AuRaContext ReadBlockAuthors() - { - for (int i = 1; i <= State.BlocksCount; i++) - { - ReadBlockAuthor(i); - } - - return this; - } - - public AuRaContext ReadBlockNumber() - { - IJsonRpcClient client = TestBuilder.CurrentNode.JsonRpcClient; - return AddJsonRpc("Read block number", "eth_blockNumber", - () => client.PostAsync("eth_blockNumber"), stateUpdater: (s, r) => s.BlocksCount = r.Result - ); - } - - private AuRaContext ReadBlockAuthor(long blockNumber) - { - IJsonRpcClient client = TestBuilder.CurrentNode.JsonRpcClient; - return AddJsonRpc("Read block", "eth_getBlockByNumber", - () => client.PostAsync("eth_getBlockByNumber", new object[] { blockNumber, false }), - stateUpdater: (s, r) => s.Blocks.Add( - Convert.ToInt64(r.Result["number"].Value(), 16), - (r.Result["miner"].Value(), Convert.ToInt64(r.Result["step"].Value(), 16)))); - } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/Framework/AuRaState.cs b/src/Nethermind/Nethermind.Overseer.Test/Framework/AuRaState.cs deleted file mode 100644 index a4739a58f161..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/Framework/AuRaState.cs +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; - -namespace Nethermind.Overseer.Test.Framework -{ - public class AuRaState : ITestState - { - public IDictionary Blocks { get; set; } = new SortedDictionary(); - public long BlocksCount { get; set; } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/Framework/CliqueContext.cs b/src/Nethermind/Nethermind.Overseer.Test/Framework/CliqueContext.cs deleted file mode 100644 index 6661b28c2fc1..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/Framework/CliqueContext.cs +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Core; -using Nethermind.Facade.Eth.RpcTransaction; -using Nethermind.Overseer.Test.JsonRpc; - -namespace Nethermind.Overseer.Test.Framework; - -public class CliqueContext(CliqueState state) : TestContextBase(state) -{ - public CliqueContext Propose(Address address, bool vote) - { - IJsonRpcClient client = TestBuilder.CurrentNode.JsonRpcClient; - return AddJsonRpc($"vote {vote} for {address}", "clique_propose", - () => client.PostAsync("clique_propose", [address, vote])); - } - - public CliqueContext Discard(Address address) - { - IJsonRpcClient client = TestBuilder.CurrentNode.JsonRpcClient; - return AddJsonRpc($"discard vote for {address}", "clique_discard", - () => client.PostAsync("clique_discard", [address])); - } - - public CliqueContext SendTransaction(TransactionForRpc tx) - { - IJsonRpcClient client = TestBuilder.CurrentNode.JsonRpcClient; - return AddJsonRpc($"send tx to {TestBuilder.CurrentNode.HttpPort}", "eth_sendTransaction", - () => client.PostAsync("eth_SendTransaction", [tx])); - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/Framework/CliqueState.cs b/src/Nethermind/Nethermind.Overseer.Test/Framework/CliqueState.cs deleted file mode 100644 index ff8bdd45858c..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/Framework/CliqueState.cs +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Overseer.Test.Framework -{ - public class CliqueState : ITestState; -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/Framework/ITestContext.cs b/src/Nethermind/Nethermind.Overseer.Test/Framework/ITestContext.cs deleted file mode 100644 index fbfe7de359e8..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/Framework/ITestContext.cs +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Overseer.Test.Framework -{ - // Marker - public interface ITestContext - { - void SetBuilder(TestBuilder builder); - } - - public interface ITestState; -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/Framework/NethermindProcessWrapper.cs b/src/Nethermind/Nethermind.Overseer.Test/Framework/NethermindProcessWrapper.cs deleted file mode 100644 index 108d10849329..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/Framework/NethermindProcessWrapper.cs +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Diagnostics; -using Nethermind.Core; -using Nethermind.Overseer.Test.JsonRpc; - -namespace Nethermind.Overseer.Test.Framework -{ - public class NethermindProcessWrapper - { - public string Enode { get; } - public IJsonRpcClient JsonRpcClient { get; } - public string Name { get; } - public Process Process { get; } - public bool IsRunning { get; private set; } - - public Address Address { get; private set; } - - public int HttpPort { get; private set; } - - public NethermindProcessWrapper(string name, Process process, int httpPort, Address address, string enode, IJsonRpcClient jsonRpcClient) - { - HttpPort = httpPort; - Address = address; - Enode = enode; - JsonRpcClient = jsonRpcClient; - Name = name; - Process = process; - } - - public void Start() - { - if (IsRunning) - { - throw new InvalidOperationException(); - } - - Console.WriteLine($"Starting in {Process.StartInfo.WorkingDirectory}"); - Process.Start(); - IsRunning = true; - } - - public void Kill() - { - if (!IsRunning) - { - throw new InvalidOperationException(); - } - - Process.Kill(); - Process.WaitForExit(); - IsRunning = false; - } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/Framework/ProcessBuilder.cs b/src/Nethermind/Nethermind.Overseer.Test/Framework/ProcessBuilder.cs deleted file mode 100644 index c39a33a0044e..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/Framework/ProcessBuilder.cs +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; -using Nethermind.Crypto; -using Nethermind.Overseer.Test.JsonRpc; -using NUnit.Framework; - -namespace Nethermind.Overseer.Test.Framework -{ - public class ProcessBuilder - { - public NethermindProcessWrapper Create(string name, string workingDirectory, string config, string dbPath, int httpPort, int p2pPort, string nodeKey, string bootnode) - { - var process = new Process { EnableRaisingEvents = true }; - process.ErrorDataReceived += ProcessOnErrorDataReceived; - process.OutputDataReceived += ProcessOnOutputDataReceived; - process.Exited += ProcessOnExited; - process.StartInfo.WorkingDirectory = workingDirectory; - process.StartInfo.FileName = "dotnet"; - var arguments = $"nethermind.dll -c {config} --JsonRpc.Port {httpPort} --Network.P2PPort {p2pPort} --Network.DiscoveryPort {p2pPort} --KeyStore.TestNodeKey {nodeKey}"; - if (!string.IsNullOrEmpty(dbPath)) - { - arguments = $"{arguments} -d {dbPath}"; - } - - if (!string.IsNullOrEmpty(bootnode)) - { - arguments = $"{arguments} --Discovery.Bootnodes {bootnode}"; - } - - process.StartInfo.Arguments = arguments; - process.StartInfo.UseShellExecute = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - process.StartInfo.CreateNoWindow = false; - process.StartInfo.WindowStyle = ProcessWindowStyle.Normal; - - return new NethermindProcessWrapper(name, process, httpPort, new PrivateKey(nodeKey).Address, $"enode://{new PrivateKey(nodeKey).PublicKey.ToString(false)}@127.0.0.1:{p2pPort}", new JsonRpcClient($"http://localhost:{httpPort}")); - } - - private static void ProcessOnExited(object sender, EventArgs eventArgs) - { - TestContext.Out.WriteLine($"Process exited: {((Process)sender).StartInfo.Arguments}"); - } - - private static void ProcessOnOutputDataReceived(object sender, DataReceivedEventArgs dataReceivedEventArgs) - { - } - - private static void ProcessOnErrorDataReceived(object sender, DataReceivedEventArgs dataReceivedEventArgs) - { - } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/Framework/Steps/JsonRpcTestStep.cs b/src/Nethermind/Nethermind.Overseer.Test/Framework/Steps/JsonRpcTestStep.cs deleted file mode 100644 index 70d335284994..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/Framework/Steps/JsonRpcTestStep.cs +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Threading.Tasks; -using Nethermind.Overseer.Test.JsonRpc; - -namespace Nethermind.Overseer.Test.Framework.Steps -{ - public class JsonRpcTestStep : TestStepBase - { - private readonly Func _validator; - private readonly Func>> _request; - private JsonRpcResponse _response; - - public JsonRpcTestStep(string name, - Func>> request, - Func validator) : base(name) - { - _validator = validator; - _request = request; - } - - public override async Task ExecuteAsync() - { - _response = await _request(); - - return _response.IsValid - ? GetResult(_validator?.Invoke(_response.Result) ?? true) - : GetResult(false); - } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/Framework/Steps/KillProcessTestStep.cs b/src/Nethermind/Nethermind.Overseer.Test/Framework/Steps/KillProcessTestStep.cs deleted file mode 100644 index 58ff87a5c486..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/Framework/Steps/KillProcessTestStep.cs +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Threading.Tasks; - -namespace Nethermind.Overseer.Test.Framework.Steps -{ - public class KillProcessTestStep : TestStepBase - { - private readonly NethermindProcessWrapper _process; - private readonly int _delay; - - public KillProcessTestStep(string name, NethermindProcessWrapper process, - int delay = 0) : base(name) - { - _process = process; - _delay = delay; - } - - public override async Task ExecuteAsync() - { - _process.Kill(); - if (_delay > 0) - { - await Task.Delay(_delay); - } - - return GetResult(!_process.IsRunning); - } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/Framework/Steps/StartProcessTestStep.cs b/src/Nethermind/Nethermind.Overseer.Test/Framework/Steps/StartProcessTestStep.cs deleted file mode 100644 index aff01f0158d2..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/Framework/Steps/StartProcessTestStep.cs +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Threading.Tasks; - -namespace Nethermind.Overseer.Test.Framework.Steps -{ - public class StartProcessTestStep : TestStepBase - { - private readonly NethermindProcessWrapper _process; - private readonly int _delay; - - public StartProcessTestStep(string name, NethermindProcessWrapper process, - int delay = 0) : base(name) - { - _process = process; - _delay = delay; - } - - public override async Task ExecuteAsync() - { - _process.Start(); - if (_delay > 0) - { - await Task.Delay(_delay); - } - - return GetResult(true); - } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/Framework/Steps/TestStepBase.cs b/src/Nethermind/Nethermind.Overseer.Test/Framework/Steps/TestStepBase.cs deleted file mode 100644 index 10a1802dd1d9..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/Framework/Steps/TestStepBase.cs +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Threading.Tasks; - -namespace Nethermind.Overseer.Test.Framework.Steps -{ - public abstract class TestStepBase - { - public string Name { get; } - - private static int _order = 1; - - protected TestStepBase(string name) - { - Name = name; - } - - public abstract Task ExecuteAsync(); - - protected TestResult GetResult(bool passed) => new TestResult(_order++, Name, passed); - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/Framework/Steps/WaitTestStep.cs b/src/Nethermind/Nethermind.Overseer.Test/Framework/Steps/WaitTestStep.cs deleted file mode 100644 index 4c0a9e8f6d11..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/Framework/Steps/WaitTestStep.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Threading.Tasks; - -namespace Nethermind.Overseer.Test.Framework.Steps -{ - public class WaitTestStep : TestStepBase - { - private readonly int _delay; - - public WaitTestStep(string name, int delay = 5000) : base(name) - { - _delay = delay; - } - - public override async Task ExecuteAsync() - { - if (_delay > 0) - { - await Task.Delay(_delay); - } - - return GetResult(true); - } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/Framework/TestBuilder.cs b/src/Nethermind/Nethermind.Overseer.Test/Framework/TestBuilder.cs deleted file mode 100644 index 42cbe499665e..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/Framework/TestBuilder.cs +++ /dev/null @@ -1,300 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Nethermind.Core.Extensions; -using Nethermind.Overseer.Test.Framework.Steps; -using NUnit.Framework; - -namespace Nethermind.Overseer.Test.Framework; - -/// -/// https://stackoverflow.com/questions/32112418/how-to-make-a-fluent-async-inferface-in-c-sharp -/// -public abstract class TestBuilder -{ - [TearDown] - public void TearDown() - { - var passedCount = _results.Count(static r => r.Passed); - var failedCount = _results.Count - passedCount; - - TestContext.Out.WriteLine("=========================== TESTS RESULTS ==========================="); - TestContext.Out.WriteLine($"TESTS PASSED: {passedCount}, FAILED: {failedCount}"); - foreach (var testResult in _results) - { - string message = $"{testResult.Order}. {testResult.Name} has " + - $"{(testResult.Passed ? "passed [+]" : "failed [-]")}"; - TestContext.Out.WriteLine(message); - } - } - -#pragma warning disable NUnit1032 - /// - /// Gets the task representing the fluent work. - /// - /// - /// The task. - /// - public Task ScenarioCompletion { get; private set; } -#pragma warning restore NUnit1032 - - /// - /// Queues up asynchronous work. - /// - /// The work to be queued. - public void QueueWork(Action work) - { - // queue up the work - ScenarioCompletion = ScenarioCompletion.ContinueWith(task => - { - try - { - work(); - } - catch (Exception e) - { - TestContext.Out.WriteLine(e.ToString()); - throw; - } - - return this; - }, TaskContinuationOptions.OnlyOnRanToCompletion); - } - - /// - /// Queues up asynchronous work. - /// - /// The work to be queued. - public void QueueWork(Func work) - { - // queue up the work - ScenarioCompletion = ScenarioCompletion.ContinueWith(async task => - { - try - { - await work(); - } - catch (Exception e) - { - TestContext.Out.WriteLine(e.ToString()); - throw; - } - - return this; - }, TaskContinuationOptions.OnlyOnRanToCompletion); - } - - public void QueueWork(TestStepBase step) - { - // queue up the work - ScenarioCompletion = ScenarioCompletion.ContinueWith(async task => - { - TestContext.Out.WriteLine($"Awaiting step {step.Name}"); - try - { - _results.Add(await step.ExecuteAsync()); - } - catch (Exception e) - { - TestContext.Out.WriteLine($"Step {step.Name} failed with error: {e}"); - throw; - } - - TestContext.Out.WriteLine($"Step {step.Name} complete"); - }, TaskContinuationOptions.OnlyOnRanToCompletion).Unwrap(); - } - - private readonly ProcessBuilder _processBuilder; - - private static readonly string _runnerDir; - private static readonly string _dbsDir; - private static readonly string _configsDir; - - static TestBuilder() - { - string testContextDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "context"); - _runnerDir = Path.Combine(testContextDir, "runner"); - _configsDir = Path.Combine(testContextDir, "configs"); - _dbsDir = Path.Combine(testContextDir, "dbs"); - - if (Directory.Exists(testContextDir)) - { - Directory.Delete(testContextDir, true); - } - - Directory.CreateDirectory(_dbsDir); - Directory.CreateDirectory(_configsDir); - } - - public TestBuilder() - { - _processBuilder = new ProcessBuilder(); - - if (!Directory.Exists(_runnerDir)) - { - Directory.CreateDirectory(_runnerDir); - CopyRunnerFiles(_runnerDir); - } - - // The entry point for the async work. - // Spin up a completed task to start with - // so that we dont have to do null checks - this.ScenarioCompletion = Task.FromResult(0); - } - - public T SetContext(T newContext) where T : ITestContext - { - newContext.SetBuilder(this); - return newContext; - } - - private const int _startHttpPort = 8600; - private const int _startPort = 30200; - - private byte _nodeCounter; - - public NethermindProcessWrapper CurrentNode { get; private set; } - - public List _results = new List(); - - public TestBuilder SwitchNode(string node) - { - CurrentNode = Nodes[node]; - return this; - } - - public TestBuilder Wait(int delay = 5000, string name = "Wait") - { - QueueWork(async () => await Task.Delay(delay)); - return this; - } - - public TestBuilder StartCliqueNode(string name) - { - return StartNode(name, "configs/cliqueNode.json"); - } - - public TestBuilder StartCliqueMiner(string name) - { - return StartNode(name, "configs/cliqueMiner.json"); - } - - public TestBuilder StartAuRaMiner(string name, string key) - { - return StartNode(name, "configs/auRaMiner.json", key); - } - - public TestBuilder StartNode(string name, string baseConfigFile, string key = null) - { - CurrentNode = GetOrCreateNode(name, baseConfigFile, key); - var step = new StartProcessTestStep($"Start {name}", CurrentNode); - QueueWork(step); - return this; - } - - private NethermindProcessWrapper GetOrCreateNode(string name, string baseConfigFile, string key) - { - if (!Nodes.TryGetValue(name, out NethermindProcessWrapper value)) - { - string bootnodes = string.Empty; - foreach ((_, NethermindProcessWrapper process) in Nodes) - { - bootnodes += $",{process.Enode}"; - } - - bootnodes = bootnodes.TrimStart(','); - - var nodeKey = GetNodeKey(key); - - string dbDir = Path.Combine(_dbsDir, name); - string configPath = Path.Combine(_configsDir, $"{name}.json"); - File.Copy(baseConfigFile, configPath); - int p2pPort = _startPort + _nodeCounter; - int httpPort = _startHttpPort + _nodeCounter; - TestContext.Out.WriteLine($"Creating {name} at {p2pPort}, http://localhost:{httpPort}"); - value = _processBuilder.Create(name, _runnerDir, configPath, dbDir, httpPort, p2pPort, nodeKey, bootnodes); - Nodes[name] = value; - _nodeCounter++; - } - - return value; - } - - private string GetNodeKey(string key) - { - if (key is null) - { - byte[] keyArray = new byte[32]; - keyArray[0] = 1; - keyArray[31] = _nodeCounter; - key = keyArray.ToHexString(); - } - - return key; - } - - public Dictionary Nodes { get; } = new Dictionary(); - - public TestBuilder Kill() - { - return Kill(CurrentNode.Name); - } - - public TestBuilder Kill(string name) - { - var step = new KillProcessTestStep($"Kill {name}", Nodes[name]); - QueueWork(step); - return this; - } - - public TestBuilder KillAll() - { - foreach (KeyValuePair keyValuePair in Nodes) - { - var step = new KillProcessTestStep($"Kill {keyValuePair.Key}", Nodes[keyValuePair.Key]); - QueueWork(step); - } - - return this; - } - -#if DEBUG - const string buildConfiguration = "Debug"; -#else - const string buildConfiguration = "Release"; -#endif - - private void CopyRunnerFiles(string targetDirectory) - { - string sourceDirectory = Path.Combine(Directory.GetCurrentDirectory(), $"../../../../artifacts/bin/Nethermind.Runner/{buildConfiguration}/"); - if (!Directory.Exists(sourceDirectory)) - { - throw new IOException($"Runner not found at {sourceDirectory}"); - } - - TestContext.Out.WriteLine($"Copying runner files from {sourceDirectory} to {targetDirectory}"); - CopyDir(sourceDirectory, targetDirectory); - string chainsDir = Path.Combine(Directory.GetCurrentDirectory(), "chainspec"); - CopyDir(chainsDir, Path.Combine(targetDirectory, "chainspec")); - } - - private void CopyDir(string sourceDirectory, string targetDirectory) - { - foreach (string file in Directory.GetFiles(sourceDirectory)) - { - File.Copy(file, Path.Combine(targetDirectory, Path.GetFileName(file)), true); - } - - foreach (string directory in Directory.GetDirectories(sourceDirectory)) - { - string targetSubDir = Path.Combine(targetDirectory, Path.GetFileName(directory)); - Directory.CreateDirectory(targetSubDir); - CopyDir(directory, targetSubDir); - } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/Framework/TestContextBase.cs b/src/Nethermind/Nethermind.Overseer.Test/Framework/TestContextBase.cs deleted file mode 100644 index 40dfc1263d96..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/Framework/TestContextBase.cs +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Text.Json; -using System.Threading.Tasks; -using Nethermind.Overseer.Test.Framework.Steps; -using Nethermind.Overseer.Test.JsonRpc; - -using NUnit.Framework; - -namespace Nethermind.Overseer.Test.Framework -{ - public abstract class TestContextBase : ITestContext where TState : ITestState where TContext : TestContextBase - { - protected TState State { get; } - protected TestBuilder TestBuilder; - - protected TestContextBase(TState state) - { - State = state; - } - - public TContext SwitchNode(string node) - { - TestBuilder.SwitchNode(node); - return (TContext)this; - } - - public TestBuilder LeaveContext() - { - return TestBuilder; - } - - public TContext Wait(int delay = 5000, string name = "Wait") - => Add(new WaitTestStep($"name {delay}", delay)); - - protected TContext AddJsonRpc(string name, string methodName, - Func>> func, Func validator = null, - Action> stateUpdater = null) - => Add(new JsonRpcTestStep(name, - async () => - { - - var result = await ExecuteJsonRpcAsync(methodName, func); - if (result.IsValid) - { - stateUpdater?.Invoke(State, result); - } - - return result; - }, validator)); - - protected TContext Add(TestStepBase step) - { - TestBuilder.QueueWork(step); - return (TContext)this; - } - - private async Task> ExecuteJsonRpcAsync( - string methodName, Func>> func) - { - TestContext.Out.WriteLine($"Sending JSON RPC call: '{methodName}'."); - var delay = Task.Delay(20000); - var funcTask = func(); - var first = await Task.WhenAny(delay, funcTask); - if (first == delay) - { - string message = $"JSON RPC call '{methodName}' timed out"; - TestContext.Out.WriteLine(message); - throw new TimeoutException(message); - } - - var result = await funcTask; - - TestContext.Out.WriteLine($"Received a response for JSON RPC call '{methodName}'." + - $"{Environment.NewLine}{JsonSerializer.Serialize(result)}"); - - return await funcTask; - } - - public void SetBuilder(TestBuilder builder) - { - TestBuilder = builder; - } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/Framework/TestResult.cs b/src/Nethermind/Nethermind.Overseer.Test/Framework/TestResult.cs deleted file mode 100644 index a8cfeea3c60e..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/Framework/TestResult.cs +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Overseer.Test.Framework -{ - public class TestResult - { - public int Order { get; } - public string Name { get; } - public bool Passed { get; } - - public TestResult(int order, string name, bool passed) - { - Order = order; - Name = name; - Passed = passed; - } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/Framework/TestResults.cs b/src/Nethermind/Nethermind.Overseer.Test/Framework/TestResults.cs deleted file mode 100644 index b75a1a04c8c8..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/Framework/TestResults.cs +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; - -namespace Nethermind.Overseer.Test.Framework -{ - public class TestResults - { - public List Results { get; set; } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DataAssetDataDto.cs b/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DataAssetDataDto.cs deleted file mode 100644 index ce6da9734759..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DataAssetDataDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Overseer.Test.JsonRpc.Dto -{ - public class DataAssetDataDto - { - public string DataAssetId { get; set; } - public string Subscription { get; set; } - public string Data { get; set; } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DataAssetDto.cs b/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DataAssetDto.cs deleted file mode 100644 index 047ba7212766..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DataAssetDto.cs +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Overseer.Test.JsonRpc.Dto -{ - public class DataAssetDto - { - // Default ID when adding a new data asset (otherwise, will fail for null). - public string Id { get; set; } = "0xd45c6b02474e7c60aeaf60df4ee451a53a09bb5df0a7e9231a0def145785f086"; - public string Name { get; set; } - public string Description { get; set; } - public string UnitPrice { get; set; } - public string UnitType { get; set; } - public uint MinUnits { get; set; } - public uint MaxUnits { get; set; } - public DataAssetRulesDto Rules { get; set; } - public DataAssetProviderDto Provider { get; set; } - public string File { get; set; } - public byte[] Data { get; set; } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DataAssetProviderDto.cs b/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DataAssetProviderDto.cs deleted file mode 100644 index 0e1f3be48518..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DataAssetProviderDto.cs +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Overseer.Test.JsonRpc.Dto -{ - public class DataAssetProviderDto - { - public string Address { get; set; } - public string Name { get; set; } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DataAssetRuleDto.cs b/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DataAssetRuleDto.cs deleted file mode 100644 index 00ee5ebc902a..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DataAssetRuleDto.cs +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Overseer.Test.JsonRpc.Dto -{ - public class DataAssetRuleDto - { - public string Value { get; set; } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DataAssetRulesDto.cs b/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DataAssetRulesDto.cs deleted file mode 100644 index 3539a70365a0..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DataAssetRulesDto.cs +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Overseer.Test.JsonRpc.Dto -{ - public class DataAssetRulesDto - { - public DataAssetRuleDto Expiry { get; set; } - public DataAssetRuleDto UpfrontPayment { get; set; } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DataRequestDto.cs b/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DataRequestDto.cs deleted file mode 100644 index e1a81c98c684..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DataRequestDto.cs +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Overseer.Test.JsonRpc.Dto -{ - public class DataRequestDto - { - public string DataAssetId { get; set; } - public uint Units { get; set; } - public string Value { get; set; } - public uint ExpiryTime { get; set; } - public string Provider { get; set; } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DepositDetailsDto.cs b/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DepositDetailsDto.cs deleted file mode 100644 index e557d96c2648..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DepositDetailsDto.cs +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Overseer.Test.JsonRpc.Dto -{ - public class DepositDetailsDto - { - public DepositDto Deposit { get; set; } - public bool Confirmed { get; set; } - public uint StartTimestamp { get; set; } - public uint SessionTimestamp { get; set; } - public string TransactionHash { get; set; } - public DataAssetDto DataAsset { get; set; } - public DataRequestDto DataRequest { get; set; } - public string[] Args { get; set; } - public bool StreamEnabled { get; set; } - public long ProviderTotalUnits { get; set; } - public long ConsumerTotalUnits { get; set; } - public long StartUnits { get; set; } - public long CurrentUnits { get; set; } - public long UnpaidUnits { get; set; } - public long PaidUnits { get; set; } - public string DataAvailability { get; set; } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DepositDto.cs b/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DepositDto.cs deleted file mode 100644 index c3f4203e9716..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/DepositDto.cs +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Overseer.Test.JsonRpc.Dto -{ - public class DepositDto - { - public string Id { get; set; } - public uint Units { get; set; } - public string Value { get; set; } - public uint ExpiryTime { get; set; } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/MakeDepositDto.cs b/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/MakeDepositDto.cs deleted file mode 100644 index 80cc15022905..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/MakeDepositDto.cs +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Overseer.Test.JsonRpc.Dto -{ - public class MakeDepositDto - { - public string DataAssetId { get; set; } - public uint Units { get; set; } - public string Value { get; set; } - public uint ExpiryTime { get; set; } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/UnitsRangeDto.cs b/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/UnitsRangeDto.cs deleted file mode 100644 index 3928626d6b5a..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/Dto/UnitsRangeDto.cs +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Overseer.Test.JsonRpc.Dto -{ - public class UnitsRangeDto - { - public uint From { get; set; } - public uint To { get; set; } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/IJsonRpcClient.cs b/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/IJsonRpcClient.cs deleted file mode 100644 index 5b7dd08e2107..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/IJsonRpcClient.cs +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Threading.Tasks; - -namespace Nethermind.Overseer.Test.JsonRpc -{ - public interface IJsonRpcClient - { - Task> PostAsync(string method); - Task> PostAsync(string method, object[] @params); - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/JsonRpcClient.cs b/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/JsonRpcClient.cs deleted file mode 100644 index 3d314baadd5e..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/JsonRpcClient.cs +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using DotNetty.Common.Utilities; -using Nethermind.JsonRpc; -using Nethermind.Serialization.Json; - -namespace Nethermind.Overseer.Test.JsonRpc -{ - public class JsonRpcClient : IJsonRpcClient - { - private readonly string _host; - private readonly string _methodPrefix = "ndm_"; - private readonly HttpClient _client; - - public JsonRpcClient(string host) - { - _host = host; - _client = new HttpClient - { - BaseAddress = new Uri(host) - }; - } - - public Task> PostAsync(string method) - => PostAsync(method, []); - - public async Task> PostAsync(string method, object[] @params) - { - string methodToCall = method.Contains('_') ? method : $"{_methodPrefix}{method}"; - Console.WriteLine($"Sending {methodToCall} to {_host}"); - var request = new JsonRpcRequest(methodToCall, @params); - var payload = GetPayload(request); - string errorMessage = null; - HttpResponseMessage response = await _client.PostAsync("/", payload).ContinueWith((t) => - { - if (t.IsFaulted) - { - errorMessage = t.Exception.Unwrap().Message; - return null; - } - else if (t.IsCanceled) - { - return null; - } - - return t.Result; - }); - - if (!(response?.IsSuccessStatusCode ?? false)) - { - var result = new JsonRpcResponse(); - result.Error = new JsonRpcResponse.ErrorResponse(ErrorCodes.InternalError, errorMessage); - return result; - } - - return await response.Content.ReadAsStringAsync() - .ContinueWith(t => new EthereumJsonSerializer().Deserialize>(t.Result)); - } - - private StringContent GetPayload(JsonRpcRequest request) - => new StringContent(new EthereumJsonSerializer().Serialize(request), Encoding.UTF8, "application/json"); - - private class JsonRpcRequest - { - public string JsonRpc { get; set; } - public int Id { get; set; } - public string Method { get; set; } - public object[] Params { get; set; } - - public JsonRpcRequest(string method, object[] @params) : this("2.0", 1, method, @params) - { - } - - public JsonRpcRequest(string jsonRpc, int id, string method, object[] @params) - { - JsonRpc = jsonRpc; - Id = id; - Method = method; - Params = @params; - } - } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/JsonRpcResponse.cs b/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/JsonRpcResponse.cs deleted file mode 100644 index 54e2b5cb0a03..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/JsonRpc/JsonRpcResponse.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Overseer.Test.JsonRpc -{ - public class JsonRpcResponse - { - public int Id { get; set; } - public string JsonRpc { get; set; } - public T Result { get; set; } - public ErrorResponse Error { get; set; } - public bool IsValid => Error is null; - - public class ErrorResponse - { - public ErrorResponse(int code, string message, object data = null) - { - Code = code; - Message = message; - } - - public int Code { get; set; } - public string Message { get; set; } - public object Data { get; set; } - } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/Nethermind.Overseer.Test.csproj b/src/Nethermind/Nethermind.Overseer.Test/Nethermind.Overseer.Test.csproj deleted file mode 100644 index d450cc905964..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/Nethermind.Overseer.Test.csproj +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - PreserveNewest - - - - - - - - - - diff --git a/src/Nethermind/Nethermind.Overseer.Test/chainspec/auRa.json b/src/Nethermind/Nethermind.Overseer.Test/chainspec/auRa.json deleted file mode 100644 index cd92dd481744..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/chainspec/auRa.json +++ /dev/null @@ -1,140 +0,0 @@ -{ - "name": "AuRaTest", - "dataDir": "aura_test", - "engine": { - "authorityRound": { - "params": { - "stepDuration": 3, - "blockReward": "0xDE0B6B3A7640000", - "maximumUncleCountTransition": 0, - "maximumUncleCount": 0, - "validators": { - "multi": { - "0" : { - "list": ["0x557abc72a6594d1bd9a655a1cb58a595526416c8"] - }, - "4" : { - "list": ["0x557abc72a6594d1bd9a655a1cb58a595526416c8", "0x69399093be61566a1c86b09bd02612c6bf31214f"] - }, - "8" : { - "list": ["0x557abc72a6594d1bd9a655a1cb58a595526416c8", "0x4cb87ff61e0e3f9f4043f69fe391a62b5a018b97"] - }, - "12" : { - "safeContract": "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" - } - } - }, - "blockRewardContractAddress": "0x3145197AD50D7083D0222DE4fCCf67d9BD05C30D", - "blockRewardContractTransition": 4639000 - } - } - }, - "params": { - "gasLimitBoundDivisor": "0x400", - "maximumExtraDataSize": "0x20", - "minGasLimit": "0x1388", - "networkID": "0x4D00", - - "eip140Transition": "0x0", - "eip211Transition": "0x0", - "eip214Transition": "0x0", - "eip658Transition": "0x0", - "eip145Transition": 6464300, - "eip1014Transition": 6464300, - "eip1052Transition": 6464300, - "eip1283Transition": 6464300, - "eip1283DisableTransition": 7026400, - - "eip152Transition": "0xFFFFFFFF", - "eip1108Transition": "0xFFFFFFFF", - "eip1344Transition": "0xFFFFFFFF", - "eip1884Transition": "0xFFFFFFFF", - "eip2028Transition": "0xFFFFFFFF", - "eip2200Transition": "0xFFFFFFFF" - }, - "genesis": { - "seal": { - "authorityRound": { - "step": "0x0", - "signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - } - }, - "difficulty": "0x20000", - "gasLimit": "0x863BE0" - }, - "accounts": { - "0000000000000000000000000000000000000005": { "builtin": { "name": "modexp", "activate_at": "0x0", "pricing": { "modexp": { "divisor": 20 } } } }, - "0000000000000000000000000000000000000006": { "builtin": { "name": "alt_bn128_add", "activate_at": "0x0", "pricing": { "linear": { "base": 500, "word": 0 } } } }, - "0000000000000000000000000000000000000007": { "builtin": { "name": "alt_bn128_mul", "activate_at": "0x0", "pricing": { "linear": { "base": 40000, "word": 0 } } } }, - "0000000000000000000000000000000000000008": { "builtin": { "name": "alt_bn128_pairing", "activate_at": "0x0", "pricing": { "alt_bn128_pairing": { "base": 100000, "pair": 80000, "eip1108_transition_base": 0, "eip1108_transition_pair": 0 } } } }, - - "0x0000000000000000000000000000000000000001": { - "balance": "1", - "builtin": { - "name": "ecrecover", - "pricing": { - "linear": { - "base": 3000, - "word": 0 - } - } - } - }, - "0x0000000000000000000000000000000000000002": { - "balance": "1", - "builtin": { - "name": "sha256", - "pricing": { - "linear": { - "base": 60, - "word": 12 - } - } - } - }, - "0x0000000000000000000000000000000000000003": { - "balance": "1", - "builtin": { - "name": "ripemd160", - "pricing": { - "linear": { - "base": 600, - "word": 120 - } - } - } - }, - "0x0000000000000000000000000000000000000004": { - "balance": "1", - "builtin": { - "name": "identity", - "pricing": { - "linear": { - "base": 15, - "word": 3 - } - } - } - }, - "0x557abc72a6594d1bd9a655a1cb58a595526416c8": { - "balance": "252460800000000000000000000" - }, - "0x8bf38d4764929064f2d4d3a56520a76ab3df415b": { - "balance":"1", - "constructor": "0x608060405234801561001057600080fd5b506040516109c43803806109c4833981018060405281019080805182019291905050508060009080519060200190610049929190610082565b508060019080519060200190610060929190610082565b506001600260006101000a81548160ff0219169083151502179055505061014f565b8280548282559060005260206000209081019282156100fb579160200282015b828111156100fa5782518260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550916020019190600101906100a2565b5b509050610108919061010c565b5090565b61014c91905b8082111561014857600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905550600101610112565b5090565b90565b6108668061015e6000396000f300608060405260043610610078576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806340a141ff1461007d5780634d238c8e146100c05780637528621114610103578063b3f05b971461011a578063b7ab4db514610149578063eebc7a39146101b5575b600080fd5b34801561008957600080fd5b506100be600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610221565b005b3480156100cc57600080fd5b50610101600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610453565b005b34801561010f57600080fd5b506101186105a7565b005b34801561012657600080fd5b5061012f610625565b604051808215151515815260200191505060405180910390f35b34801561015557600080fd5b5061015e610638565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b838110156101a1578082015181840152602081019050610186565b505050509050019250505060405180910390f35b3480156101c157600080fd5b506101ca6106c6565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b8381101561020d5780820151818401526020810190506101f2565b505050509050019250505060405180910390f35b6000600260009054906101000a900460ff16151561023e57600080fd5b600090505b60008054905081101561044e5760008181548110151561025f57fe5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156104415760006001600080549050038154811015156102d457fe5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1660008281548110151561030e57fe5b9060005260206000200160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600080548091906001900361036b9190610754565b506001430340600019167f55252fa6eee4741b4e24a74a70e9c11fd2c2281df8d6ea13126ff845f7825c8960006040518080602001828103825283818154815260200191508054801561041357602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190600101908083116103c9575b50509250505060405180910390a26000600260006101000a81548160ff02191690831515021790555061044f565b8080600101915050610243565b5b5050565b600260009054906101000a900460ff16151561046e57600080fd5b60008190806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550506001430340600019167f55252fa6eee4741b4e24a74a70e9c11fd2c2281df8d6ea13126ff845f7825c8960006040518080602001828103825283818154815260200191508054801561057b57602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311610531575b50509250505060405180910390a26000600260006101000a81548160ff02191690831515021790555050565b73fffffffffffffffffffffffffffffffffffffffe73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156105f557600080fd5b60006001908054610607929190610780565b506001600260006101000a81548160ff021916908315150217905550565b600260009054906101000a900460ff1681565b606060018054806020026020016040519081016040528092919081815260200182805480156106bc57602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311610672575b5050505050905090565b6060600080548060200260200160405190810160405280929190818152602001828054801561074a57602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311610700575b5050505050905090565b81548183558181111561077b5781836000526020600020918201910161077a91906107d2565b5b505050565b8280548282559060005260206000209081019282156107c15760005260206000209182015b828111156107c05782548255916001019190600101906107a5565b5b5090506107ce91906107f7565b5090565b6107f491905b808211156107f05760008160009055506001016107d8565b5090565b90565b61083791905b8082111561083357600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055506001016107fd565b5090565b905600a165627a7a7230582077d7be9f11ab8944b6f6dc9e5086fe426593e38f30a5999ae41c11052ec424e4002900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000557abc72a6594d1bd9a655a1cb58a595526416c8" - }, - "0xe85db1afba971890bd66ba08dfeec018ab80726e": { - "balance":"1", - "constructor": "0x608060405234801561001057600080fd5b506040516109c43803806109c4833981018060405281019080805182019291905050508060009080519060200190610049929190610082565b508060019080519060200190610060929190610082565b506001600260006101000a81548160ff0219169083151502179055505061014f565b8280548282559060005260206000209081019282156100fb579160200282015b828111156100fa5782518260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550916020019190600101906100a2565b5b509050610108919061010c565b5090565b61014c91905b8082111561014857600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905550600101610112565b5090565b90565b6108668061015e6000396000f300608060405260043610610078576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806340a141ff1461007d5780634d238c8e146100c05780637528621114610103578063b3f05b971461011a578063b7ab4db514610149578063eebc7a39146101b5575b600080fd5b34801561008957600080fd5b506100be600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610221565b005b3480156100cc57600080fd5b50610101600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610453565b005b34801561010f57600080fd5b506101186105a7565b005b34801561012657600080fd5b5061012f610625565b604051808215151515815260200191505060405180910390f35b34801561015557600080fd5b5061015e610638565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b838110156101a1578082015181840152602081019050610186565b505050509050019250505060405180910390f35b3480156101c157600080fd5b506101ca6106c6565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b8381101561020d5780820151818401526020810190506101f2565b505050509050019250505060405180910390f35b6000600260009054906101000a900460ff16151561023e57600080fd5b600090505b60008054905081101561044e5760008181548110151561025f57fe5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156104415760006001600080549050038154811015156102d457fe5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1660008281548110151561030e57fe5b9060005260206000200160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600080548091906001900361036b9190610754565b506001430340600019167f55252fa6eee4741b4e24a74a70e9c11fd2c2281df8d6ea13126ff845f7825c8960006040518080602001828103825283818154815260200191508054801561041357602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190600101908083116103c9575b50509250505060405180910390a26000600260006101000a81548160ff02191690831515021790555061044f565b8080600101915050610243565b5b5050565b600260009054906101000a900460ff16151561046e57600080fd5b60008190806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550506001430340600019167f55252fa6eee4741b4e24a74a70e9c11fd2c2281df8d6ea13126ff845f7825c8960006040518080602001828103825283818154815260200191508054801561057b57602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311610531575b50509250505060405180910390a26000600260006101000a81548160ff02191690831515021790555050565b73fffffffffffffffffffffffffffffffffffffffe73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156105f557600080fd5b60006001908054610607929190610780565b506001600260006101000a81548160ff021916908315150217905550565b600260009054906101000a900460ff1681565b606060018054806020026020016040519081016040528092919081815260200182805480156106bc57602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311610672575b5050505050905090565b6060600080548060200260200160405190810160405280929190818152602001828054801561074a57602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311610700575b5050505050905090565b81548183558181111561077b5781836000526020600020918201910161077a91906107d2565b5b505050565b8280548282559060005260206000209081019282156107c15760005260206000209182015b828111156107c05782548255916001019190600101906107a5565b5b5090506107ce91906107f7565b5090565b6107f491905b808211156107f05760008160009055506001016107d8565b5090565b90565b61083791905b8082111561083357600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055506001016107fd565b5090565b905600a165627a7a7230582077d7be9f11ab8944b6f6dc9e5086fe426593e38f30a5999ae41c11052ec424e4002900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000557abc72a6594d1bd9a655a1cb58a595526416c8" - }, - "0x69399093be61566a1c86b09bd02612c6bf31214f": { - "balance":"1000000000000" - }, - "0x7e99aceb5d3dba8c79732c6cf49f184b00cff69b": { - "balance":"1000000000000" - }, - "0x2da63c1f7ede7305cdaa931e3feb2e4a234780d0": { - "balance":"10000000000000000000000000000000000" - } - } -} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Overseer.Test/chainspec/clique.json b/src/Nethermind/Nethermind.Overseer.Test/chainspec/clique.json deleted file mode 100644 index 5f2e17948042..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/chainspec/clique.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "name": "CliqueTest", - "dataDir": "clique_test", - "engine": { - "clique": { - "params": { - "period": 2, - "epoch": 30000, - "blockReward": "0x0" - } - } - }, - "params": { - "accountStartNonce": "0x0", - "eip98Transition": "0xffffffffffffffff", - "eip140Transition": "0x0", - "eip145Transition": "0x0", - "eip150Transition": "0x0", - "eip155Transition": "0x0", - "eip158Transition": "0x0", - "eip160Transition": "0x0", - "eip161abcTransition": "0x0", - "eip161dTransition": "0x0", - "eip211Transition": "0x0", - "eip214Transition": "0x0", - "eip658Transition": "0x0", - "eip1014Transition": "0x0", - "eip1052Transition": "0x0", - "eip1283Transition": "0x0", - "gasLimitBoundDivisor": "0x400", - "homesteadTransition": "0x0", - "kip4Transition": "0xffffffffffffffff", - "kip6Transition": "0xffffffffffffffff", - "maxCodeSize": "0x6000", - "maxCodeSizeTransition": "0x0", - "maximumExtraDataSize": "0x64", - "minGasLimit": "0x1388", - "networkID": "0x188c", - "validateReceipts": false, - "validateReceiptsTransition": "0xffffffffffffffff", - "wasmActivationTransition": "0xffffffffffffffff" - }, - "genesis": { - "seal": { - "ethereum": { - "nonce": "0x0000000000000000", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" - } - }, - "author": "0x0000000000000000000000000000000000000000", - "difficulty": "0x1", - "extraData": "0x2249276d20646f6e652077616974696e672e2e2e20666f7220626c6f636b20667691ee0343b9a529675e1a8a70197b3b704f90b7dc5b20847f43d67928f49cd4f85d696b5a7617b50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0xa00000", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "timestamp": "0x5bdda800" - }, - "nodes": [ - ], - "accounts": { - "0x0000000000000000000000000000000000000000": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000001": { - "balance": "0x1", - "builtin": { - "name": "ecrecover", - "pricing": { - "linear": { - "base": 3000, - "word": 0 - } - } - } - }, - "0x0000000000000000000000000000000000000002": { - "balance": "0x1", - "builtin": { - "name": "sha256", - "pricing": { - "linear": { - "base": 60, - "word": 12 - } - } - } - }, - "0x0000000000000000000000000000000000000003": { - "balance": "0x1", - "builtin": { - "name": "ripemd160", - "pricing": { - "linear": { - "base": 600, - "word": 120 - } - } - } - }, - "0x0000000000000000000000000000000000000004": { - "balance": "0x1", - "builtin": { - "name": "identity", - "pricing": { - "linear": { - "base": 15, - "word": 3 - } - } - } - }, - "0x0000000000000000000000000000000000000005": { - "balance": "0x1", - "builtin": { - "name": "modexp", - "activate_at": "0x0", - "pricing": { - "modexp": { - "divisor": 20 - } - } - } - }, - "0x0000000000000000000000000000000000000006": { - "balance": "0x1", - "builtin": { - "name": "alt_bn128_add", - "activate_at": "0x0", - "pricing": { - "linear": { - "base": 500, - "word": 0 - } - } - } - }, - "0x0000000000000000000000000000000000000007": { - "balance": "0x1", - "builtin": { - "name": "alt_bn128_mul", - "activate_at": "0x0", - "pricing": { - "linear": { - "base": 40000, - "word": 0 - } - } - } - }, - "0x0000000000000000000000000000000000000008": { - "balance": "0x1", - "builtin": { - "name": "alt_bn128_pairing", - "activate_at": "0x0", - "pricing": { - "alt_bn128_pairing": { - "base": 100000, - "pair": 80000 - } - } - } - }, - "7e5f4552091a69125d5dfcb7b8c2659029395bdf": { "balance": "100000000000000000000" }, - "2b5ad5c4795c026514f8317c7a215e218dccd6cf": { "balance": "100000000000000000000" }, - "6813eb9362372eef6200f3b1dbc3f819671cba69": { "balance": "100000000000000000000" }, - "1eff47bc3a10a45d4b230b5d10e37751fe6aa718": { "balance": "100000000000000000000" }, - "e1ab8145f7e55dc933d51a18c793f901a3a0b276": { "balance": "100000000000000000000" }, - "e57bfe9f44b819898f47bf37e5af72a0783e1141": { "balance": "100000000000000000000" }, - "d41c057fd1c78805aac12b0a94a405c0461a6fbb": { "balance": "100000000000000000000" }, - "f1f6619b38a98d6de0800f1defc0a6399eb6d30c": { "balance": "100000000000000000000" }, - "f7edc8fa1ecc32967f827c9043fcae6ba73afa5c": { "balance": "100000000000000000000" }, - "4cceba2d7d2b4fdce4304d3e09a1fea9fbeb1528": { "balance": "100000000000000000000" } - } -} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Overseer.Test/chainspec/nethdev.json b/src/Nethermind/Nethermind.Overseer.Test/chainspec/nethdev.json deleted file mode 100644 index 2046a0836a48..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/chainspec/nethdev.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "name": "nethdev", - "dataDir": "test", - "engine": { - "NethDev": { - "params": { - } - } - }, - "params": { - "gasLimitBoundDivisor": "0x0400", - "registrar": "0x81a4b044831c4f12ba601adb9274516939e9b8a2", - "accountStartNonce": "0x0", - "maximumExtraDataSize": "0x20", - "minGasLimit": "0x1388", - "networkID" : "0x63", - "forkBlock": 641350, - "forkCanonHash": "0x8033403e9fe5811a7b6d6b469905915de1c59207ce2172cbcf5d6ff14fa6a2eb", - "maxCodeSize": 24576, - "maxCodeSizeTransition": 10, - "eip155Transition": 10, - "eip98Transition": "0x7fffffffffffff", - "eip86Transition": "0x7fffffffffffff", - "eip140Transition": 1700000, - "eip211Transition": 1700000, - "eip214Transition": 1700000, - "eip658Transition": 1700000 - }, - "genesis": { - "seal": { - "ethereum": { - "nonce": "0x0000000000000042", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" - } - }, - "difficulty": "0x100000", - "author": "0x0000000000000000000000000000000000000000", - "timestamp": "0x00", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "extraData": "0x3535353535353535353535353535353535353535353535353535353535353535", - "gasLimit": "0x1000000" - }, - "nodes": [ - ], - "accounts": { - "0000000000000000000000000000000000000000": { "balance": "1" }, - "0000000000000000000000000000000000000001": { "balance": "1", "nonce": "0", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, - "0000000000000000000000000000000000000002": { "balance": "1", "nonce": "0", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, - "0000000000000000000000000000000000000003": { "balance": "1", "nonce": "0", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, - "0000000000000000000000000000000000000004": { "balance": "1", "nonce": "0", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, - "0000000000000000000000000000000000000005": { "balance": "1", "nonce": "0", "builtin": { "name": "modexp", "activate_at": 1700000, "pricing": { "modexp": { "divisor": 20 } } } }, - "0000000000000000000000000000000000000006": { "balance": "1", "nonce": "0", "builtin": { "name": "alt_bn128_add", "activate_at": 1700000, "pricing": { "linear": { "base": 500, "word": 0 } } } }, - "0000000000000000000000000000000000000007": { "balance": "1", "nonce": "0", "builtin": { "name": "alt_bn128_mul", "activate_at": 1700000, "pricing": { "linear": { "base": 40000, "word": 0 } } } }, - "0000000000000000000000000000000000000008": { "balance": "1", "nonce": "0", "builtin": { "name": "alt_bn128_pairing", "activate_at": 1700000, "pricing": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 } } } }, - "7e5f4552091a69125d5dfcb7b8c2659029395bdf": { "balance": "100000000000000000000" }, - "2b5ad5c4795c026514f8317c7a215e218dccd6cf": { "balance": "100000000000000000000" }, - "6813eb9362372eef6200f3b1dbc3f819671cba69": { "balance": "100000000000000000000" }, - "1eff47bc3a10a45d4b230b5d10e37751fe6aa718": { "balance": "100000000000000000000" }, - "e1ab8145f7e55dc933d51a18c793f901a3a0b276": { "balance": "100000000000000000000" }, - "e57bfe9f44b819898f47bf37e5af72a0783e1141": { "balance": "100000000000000000000" }, - "d41c057fd1c78805aac12b0a94a405c0461a6fbb": { "balance": "100000000000000000000" }, - "f1f6619b38a98d6de0800f1defc0a6399eb6d30c": { "balance": "100000000000000000000" }, - "f7edc8fa1ecc32967f827c9043fcae6ba73afa5c": { "balance": "100000000000000000000" }, - "4cceba2d7d2b4fdce4304d3e09a1fea9fbeb1528": { "balance": "100000000000000000000" } - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/configs/auRaMiner.json b/src/Nethermind/Nethermind.Overseer.Test/configs/auRaMiner.json deleted file mode 100644 index 32c8f911a1e4..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/configs/auRaMiner.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "Init": { - "EnableUnsecuredDevWallet": true, - "KeepDevWalletInMemory": true, - "IsMining": true, - "ChainSpecPath": "chainspec/auRa.json", - "GenesisHash": "", - "BaseDbPath": "aura", - "LogFileName": "auRaMiner.log" - }, - "Network": { - "DiscoveryPort": 30303, - "P2PPort": 30303, - }, - "JsonRpc": { - "Host": "127.0.0.1", - "Port": 8545, - "Enabled": true, - }, - "Db": { - "WriteBufferSize": 67108864, - "WriteBufferNumber": 6, - "BlockCacheSize": 67108864, - "CacheIndexAndFilterBlocks": false - }, - "Aura": - { - "ForceSealing": true - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/configs/cliqueMiner.json b/src/Nethermind/Nethermind.Overseer.Test/configs/cliqueMiner.json deleted file mode 100644 index ea9fdd44105f..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/configs/cliqueMiner.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "Init": { - "EnableUnsecuredDevWallet": true, - "KeepDevWalletInMemory": true, - "IsMining": true, - "ChainSpecPath": "chainspec/clique.json", - "GenesisHash": "", - "BaseDbPath": "clique", - "LogFileName": "cliqueMiner.log" - }, - "Network": { - "DiscoveryPort": 30303, - "P2PPort": 30303, - }, - "JsonRpc": { - "Host": "127.0.0.1", - "Port": 8545, - "Enabled": true, - }, - "Db": { - "WriteBufferSize": 67108864, - "WriteBufferNumber": 6, - "BlockCacheSize": 67108864, - "CacheIndexAndFilterBlocks": false - } -} diff --git a/src/Nethermind/Nethermind.Overseer.Test/configs/cliqueNode.json b/src/Nethermind/Nethermind.Overseer.Test/configs/cliqueNode.json deleted file mode 100644 index 2694dddb357a..000000000000 --- a/src/Nethermind/Nethermind.Overseer.Test/configs/cliqueNode.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "Init": { - "EnableUnsecuredDevWallet": true, - "KeepDevWalletInMemory": true, - "ChainSpecPath": "chainspec/clique.json", - "GenesisHash": "", - "BaseDbPath": "clique", - "LogFileName": "cliqueNode.log" - }, - "Network": { - "DiscoveryPort": 30303, - "P2PPort": 30303, - }, - "JsonRpc": { - "Host": "127.0.0.1", - "Port": 8545, - "Enabled": true, - }, - "Db": { - "WriteBufferSize": 67108864, - "WriteBufferNumber": 6, - "BlockCacheSize": 67108864, - "CacheIndexAndFilterBlocks": false - } -} diff --git a/src/Nethermind/Nethermind.PerfTest/Program.cs b/src/Nethermind/Nethermind.PerfTest/Program.cs index 6944fd422b0f..0931f947cd22 100644 --- a/src/Nethermind/Nethermind.PerfTest/Program.cs +++ b/src/Nethermind/Nethermind.PerfTest/Program.cs @@ -253,7 +253,7 @@ private static async Task RunBenchmarkBlocks() if (_logger.IsInfo) _logger.Info("State DBs deleted"); /* load spec */ - ChainSpecLoader loader = new ChainSpecLoader(new EthereumJsonSerializer()); + ChainSpecLoader loader = new ChainSpecLoader(new EthereumJsonSerializer(), _logManager); string path = Path.Combine(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"chainspec", "goerli.json")); _logger.Info($"Loading ChainSpec from {path}"); ChainSpec chainSpec = loader.Load(File.ReadAllText(path)); diff --git a/src/Nethermind/Nethermind.Precompiles.Benchmark/ModExpBenchmark.cs b/src/Nethermind/Nethermind.Precompiles.Benchmark/ModExpBenchmark.cs index 1a7f8a3ad004..b018a642af3c 100644 --- a/src/Nethermind/Nethermind.Precompiles.Benchmark/ModExpBenchmark.cs +++ b/src/Nethermind/Nethermind.Precompiles.Benchmark/ModExpBenchmark.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using Nethermind.Evm.Precompiles; diff --git a/src/Nethermind/Nethermind.Precompiles.Benchmark/PrecompileBenchmarkBase.cs b/src/Nethermind/Nethermind.Precompiles.Benchmark/PrecompileBenchmarkBase.cs index 132d06e8228c..22abc10e97a8 100644 --- a/src/Nethermind/Nethermind.Precompiles.Benchmark/PrecompileBenchmarkBase.cs +++ b/src/Nethermind/Nethermind.Precompiles.Benchmark/PrecompileBenchmarkBase.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using BenchmarkDotNet.Attributes; +using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Evm.Precompiles; @@ -16,6 +17,8 @@ namespace Nethermind.Precompiles.Benchmark { public abstract class PrecompileBenchmarkBase { + private const int Operations = 25; + protected abstract IEnumerable Precompiles { get; } protected abstract string InputsDirectory { get; } @@ -50,14 +53,14 @@ public IEnumerable Inputs // take only first line from each file inputs.AddRange(File.ReadAllLines(file) .Select(LineToTestInput).Take(1).ToArray() - .Select(i => new Param(precompile, file, i, null))); + .Select(i => new Param(precompile, Path.GetFileName(file), i, null))); } foreach (string file in Directory.GetFiles(inputsDir, "*.json", SearchOption.TopDirectoryOnly)) { EthereumJsonSerializer jsonSerializer = new(); JsonInput[] jsonInputs = jsonSerializer.Deserialize(File.ReadAllText(file)); - IEnumerable parameters = jsonInputs.Select(i => new Param(precompile, i.Name!, i.Input!, i.Expected)); + IEnumerable parameters = jsonInputs.Select(i => new Param(precompile, Path.GetFileName(i.Name!), i.Input!, i.Expected)); inputs.AddRange(parameters); } @@ -75,8 +78,16 @@ public IEnumerable Inputs private static byte[] LineToTestInput(string line) => Bytes.FromHexString(line.Split(',')[0]); - [Benchmark(Baseline = true)] - public (ReadOnlyMemory, bool) Baseline() - => Input.Precompile.Run(Input.Bytes, Cancun.Instance); + [Benchmark(Baseline = true, OperationsPerInvoke = Operations)] + public bool Baseline() + { + bool overallResult = true; + for (var i = 0; i < Operations; i++) + { + Result result = Input.Precompile.Run(Input.Bytes, Cancun.Instance); + overallResult &= result; + } + return overallResult; + } } } diff --git a/src/Nethermind/Nethermind.Runner.Test/ChainSpecFilesTests.cs b/src/Nethermind/Nethermind.Runner.Test/ChainSpecFilesTests.cs index fb84e934f28d..7f3e3871473e 100644 --- a/src/Nethermind/Nethermind.Runner.Test/ChainSpecFilesTests.cs +++ b/src/Nethermind/Nethermind.Runner.Test/ChainSpecFilesTests.cs @@ -18,7 +18,7 @@ public class ChainSpecFilesTests public ChainSpecFilesTests() { - _loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + _loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance); } [TestCase("foundation", 1UL)] @@ -37,15 +37,6 @@ public void ChainSpec_from_file(string chainSpecPath, ulong chainId) .Match(cs => cs.ChainId == chainId); } - // This holesky.json contains invalid config values. This test ensues that those config values are - // ignored for the correct ones contained in another holesky.json file embedded in the config directory - [TestCase("holesky.json", 0x4268UL)] - public void ignoring_custom_chainSpec_when_embedded_exists(string chainSpecPath, ulong chainId) - { - _loader.LoadEmbeddedOrFromFile(chainSpecPath).Should() - .Match(cs => cs.ChainId == chainId); - } - [TestCase("chainspec/custom_chainspec_that_does_not_exist.json")] public void ChainSpecNotFound(string chainSpecPath) { diff --git a/src/Nethermind/Nethermind.Runner.Test/ConfigFilesTests.cs b/src/Nethermind/Nethermind.Runner.Test/ConfigFilesTests.cs index 5f535c9fbe4c..c166eb5cfdf5 100644 --- a/src/Nethermind/Nethermind.Runner.Test/ConfigFilesTests.cs +++ b/src/Nethermind/Nethermind.Runner.Test/ConfigFilesTests.cs @@ -6,12 +6,12 @@ using System.IO; using System.Linq; using FluentAssertions; -using Nethermind.Analytics; using Nethermind.Api; using Nethermind.Blockchain.Receipts; using Nethermind.Blockchain.Synchronization; using Nethermind.Config; using Nethermind.Config.Test; +using Nethermind.Consensus; using Nethermind.Db; using Nethermind.EthStats; using Nethermind.JsonRpc; @@ -107,7 +107,7 @@ public void Genesis_hash_is_correct(string configWildcard, string genesisHash) [TestCase("^validators ^spaceneth", false)] public void Mining_defaults_are_correct(string configWildcard, bool defaultValue = false) { - Test(configWildcard, static c => c.IsMining, defaultValue); + Test(configWildcard, static c => c.Enabled, defaultValue); } [TestCase("*")] @@ -116,17 +116,8 @@ public void Eth_stats_disabled_by_default(string configWildcard) Test(configWildcard, static c => c.Enabled, false); } - [TestCase("*")] - public void Analytics_defaults(string configWildcard) - { - Test(configWildcard, static c => c.PluginsEnabled, false); - Test(configWildcard, static c => c.StreamBlocks, false); - Test(configWildcard, static c => c.StreamTransactions, false); - Test(configWildcard, static c => c.LogPublishedData, false); - } - [TestCase("mainnet archive", 4096000000)] - [TestCase("mainnet ^archive", 2048000000)] + [TestCase("mainnet ^archive", 1024000000)] [TestCase("volta archive", 768000000)] [TestCase("volta ^archive", 768000000)] [TestCase("gnosis archive", 1024000000)] @@ -168,7 +159,7 @@ public void Network_diag_tracer_disabled_by_default(string configWildcard) } [TestCase("mainnet", 2048)] - [TestCase("holesky", 1024)] + [TestCase("hoodi", 1024)] [TestCase("sepolia", 1024)] [TestCase("gnosis", 2048)] [TestCase("poacore", 2048)] @@ -185,9 +176,9 @@ public void Tx_pool_defaults_are_correct(string configWildcard, int poolSize) [TestCase("gnosis", true)] [TestCase("mainnet", true)] [TestCase("sepolia", true)] - [TestCase("holesky", true)] + [TestCase("hoodi", true)] [TestCase("chiado", true)] - [TestCase("^spaceneth ^mainnet ^gnosis ^sepolia ^holesky ^chiado", false)] + [TestCase("^spaceneth ^mainnet ^gnosis ^sepolia ^hoodi ^chiado", false)] public void Json_defaults_are_correct(string configWildcard, bool jsonEnabled) { Test(configWildcard, static c => c.Enabled, jsonEnabled); @@ -224,11 +215,11 @@ public void Snap_sync_settings_as_expected(string configWildcard, bool enabled) Test(configWildcard, static c => c.SnapSync, enabled); } - [TestCase("^aura ^sepolia ^holesky ^mainnet", false)] + [TestCase("^aura ^sepolia ^hoodi ^mainnet", false)] [TestCase("aura ^archive", true)] [TestCase("^archive ^spaceneth", true)] [TestCase("sepolia ^archive", true)] - [TestCase("holesky ^archive", true)] + [TestCase("hoodi ^archive", true)] [TestCase("mainnet ^archive", true)] public void Stays_on_full_sync(string configWildcard, bool stickToFullSyncAfterFastSync) { @@ -244,7 +235,7 @@ public void Diagnostics_mode_is_not_enabled_by_default(string configWildcard) [TestCase("*")] public void Migrations_are_not_enabled_by_default(string configWildcard) { - Test(configWildcard, static c => c.ReceiptsMigration, false); + Test(configWildcard, static c => c.ReceiptsMigration, false); Test(configWildcard, static c => c.Migration, false); Test(configWildcard, static c => c.MigrationStatistics, false); } @@ -267,8 +258,7 @@ public void Base_db_path_is_set(string configWildcard, string startWith) Test(configWildcard, c => c.BaseDbPath, (cf, p) => p.Should().StartWith(startWith)); } - [TestCase("^sepolia", "Data/static-nodes.json")] - [TestCase("sepolia", "Data/static-nodes-sepolia.json")] + [TestCase("*", "static-nodes.json")] public void Static_nodes_path_is_default(string configWildcard, string staticNodesPath) { Test(configWildcard, static c => c.StaticNodesPath, staticNodesPath); @@ -309,11 +299,11 @@ public void Simulating_block_production_on_every_slot_is_always_disabled(string } [TestCase("sepolia", BlobsSupportMode.StorageWithReorgs)] - [TestCase("holesky", BlobsSupportMode.StorageWithReorgs)] + [TestCase("hoodi", BlobsSupportMode.StorageWithReorgs)] [TestCase("chiado", BlobsSupportMode.StorageWithReorgs)] [TestCase("mainnet", BlobsSupportMode.StorageWithReorgs)] [TestCase("gnosis", BlobsSupportMode.StorageWithReorgs)] - [TestCase("^sepolia ^holesky ^chiado ^mainnet ^gnosis", BlobsSupportMode.Disabled)] + [TestCase("^sepolia ^hoodi ^chiado ^mainnet ^gnosis", BlobsSupportMode.Disabled)] public void Blob_txs_support_is_correct(string configWildcard, BlobsSupportMode blobsSupportMode) { Test(configWildcard, static c => c.BlobsSupport, blobsSupportMode); @@ -349,10 +339,10 @@ public void Arena_order_is_default(string configWildcard) [TestCase("chiado", 17_000_000L, 5UL, 3000)] [TestCase("gnosis", 17_000_000L, 5UL, 3000)] - [TestCase("mainnet", 45_000_000L)] + [TestCase("mainnet", 60_000_000L)] [TestCase("sepolia", 60_000_000L)] - [TestCase("holesky", 60_000_000L)] - [TestCase("^chiado ^gnosis ^mainnet ^sepolia ^holesky")] + [TestCase("hoodi", 60_000_000L)] + [TestCase("^chiado ^gnosis ^mainnet ^sepolia ^hoodi")] public void Blocks_defaults_are_correct(string configWildcard, long? targetBlockGasLimit = null, ulong secondsPerSlot = 12, int blockProductionTimeout = 4000) { Test(configWildcard, static c => c.TargetBlockGasLimit, targetBlockGasLimit); @@ -403,8 +393,8 @@ public void Memory_hint_is_enough(string configWildcard) protected override IEnumerable Configs { get; } = new HashSet { - "holesky.json", - "holesky_archive.json", + "hoodi.json", + "hoodi_archive.json", "mainnet_archive.json", "mainnet.json", "poacore.json", diff --git a/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs b/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs index e0013891a0c1..174526aaf142 100644 --- a/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs +++ b/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO.Abstractions; using System.Linq; using Autofac; using Autofac.Core; @@ -11,38 +10,22 @@ using Autofac.Core.Lifetime; using Autofac.Core.Registration; using Nethermind.Api; -using Nethermind.Blockchain; -using Nethermind.Blockchain.Filters; -using Nethermind.Blockchain.Receipts; -using Nethermind.Blockchain.Services; using Nethermind.Config; using Nethermind.Consensus; using Nethermind.Consensus.Comparers; -using Nethermind.Consensus.Processing; using Nethermind.Consensus.Producers; -using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Validators; using Nethermind.Core.Specs; -using Nethermind.Crypto; -using Nethermind.Db; using Nethermind.Logging; using Nethermind.Network; -using Nethermind.Db.Blooms; -using Nethermind.Grpc; -using Nethermind.JsonRpc.Modules.Eth.GasPrice; using Nethermind.KeyStore; using Nethermind.Serialization.Json; using Nethermind.Specs.ChainSpecStyle; -using Nethermind.State.Repositories; using Nethermind.TxPool; using Nethermind.Wallet; -using Nethermind.Sockets; using Nethermind.Specs; -using Nethermind.Trie; using NSubstitute; -using Nethermind.Blockchain.Blocks; using Nethermind.Core; -using Nethermind.Facade.Find; namespace Nethermind.Runner.Test.Ethereum { @@ -52,7 +35,7 @@ public static NethermindApi ContextWithMocks() { NethermindApi.Dependencies apiDependencies = new NethermindApi.Dependencies( Substitute.For(), - Substitute.For(), + new EthereumJsonSerializer(), LimboLogs.Instance, new ChainSpec { Parameters = new ChainParameters(), }, Substitute.For(), @@ -86,7 +69,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 953abe1a301a..761107c92260 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)), @@ -176,7 +176,8 @@ private static ContainerBuilder CreateCommonBuilder(params IEnumerable .AddSingleton() .AddSingleton() .AddSingleton(new ConfigProvider()) - .AddSingleton(new EthereumJsonSerializer()) + .AddSingleton(new EthereumJsonSerializer()) + .Bind() .AddSingleton(LimboLogs.Instance) .AddSingleton(new ChainSpec()) .AddSingleton(Substitute.For()) diff --git a/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/Migrations/ReceiptMigrationTests.cs b/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/Migrations/ReceiptMigrationTests.cs index 02a695382dfd..6fb33121c458 100644 --- a/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/Migrations/ReceiptMigrationTests.cs +++ b/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/Migrations/ReceiptMigrationTests.cs @@ -160,7 +160,8 @@ public void RemoveReceipts(Block block) } #pragma warning disable CS0067 - public event EventHandler ReceiptsInserted; + public event EventHandler NewCanonicalReceipts; + public event EventHandler ReceiptsInserted; #pragma warning restore CS0067 } } diff --git a/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs b/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs index 06645e641210..9fdd2f92139e 100644 --- a/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs +++ b/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs @@ -19,6 +19,7 @@ using FluentAssertions; using Nethermind.Api; using Nethermind.Api.Extensions; +using Nethermind.Api.Steps; using Nethermind.Blockchain.Synchronization; using Nethermind.Config; using Nethermind.Consensus; @@ -29,6 +30,7 @@ using Nethermind.Consensus.Processing; using Nethermind.Consensus.Producers; using Nethermind.Consensus.Rewards; +using Nethermind.Consensus.Scheduler; using Nethermind.Consensus.Tracing; using Nethermind.Consensus.Validators; using Nethermind.Core; @@ -42,10 +44,10 @@ using Nethermind.Db.Rocks.Config; using Nethermind.Era1; using Nethermind.Evm; +using Nethermind.Evm.State; using Nethermind.Evm.TransactionProcessing; using Nethermind.Flashbots; using Nethermind.HealthChecks; -using Nethermind.Hive; using Nethermind.Init.Steps; using Nethermind.JsonRpc; using Nethermind.JsonRpc.Modules; @@ -55,20 +57,17 @@ using Nethermind.Merge.Plugin.Synchronization; using Nethermind.Network; using Nethermind.Network.Config; -using Nethermind.Runner.Ethereum; using Nethermind.Optimism; +using Nethermind.Runner.Ethereum; using Nethermind.Runner.Ethereum.Api; using Nethermind.Serialization.Rlp; -using Nethermind.Evm.State; using Nethermind.Synchronization; using Nethermind.Taiko.TaikoSpec; using Nethermind.TxPool; +using Nethermind.Xdc.Spec; using NSubstitute; using NUnit.Framework; using Build = Nethermind.Runner.Test.Ethereum.Build; -using Nethermind.Api.Steps; -using Nethermind.Consensus.Scheduler; -using Nethermind.Xdc.Spec; namespace Nethermind.Runner.Test; @@ -78,15 +77,15 @@ public class EthereumRunnerTests static EthereumRunnerTests() { // Trigger plugins loading early to ensure TypeDiscovery caches plugin's types - PluginLoader pluginLoader = new("plugins", new FileSystem(), NullLogger.Instance); + PluginLoader pluginLoader = new("plugins", new FileSystem(), NullLogger.Instance, NethermindPlugins.EmbeddedPlugins); pluginLoader.Load(); AssemblyLoadContext.Default.Resolving += static (_, _) => null; } - private static readonly Lazy? _cachedProviders = new(InitOnce); + private static readonly Lazy>? _cachedProviders = new(InitOnce); - private static ICollection InitOnce() + private static ICollection<(string file, ConfigProvider configProvider)> InitOnce() { // we need this to discover ChainSpecEngineParameters _ = new[] { typeof(CliqueChainSpecEngineParameters), typeof(OptimismChainSpecEngineParameters), typeof(TaikoChainSpecEngineParameters), typeof(XdcChainSpecEngineParameters) }; @@ -142,9 +141,9 @@ public static IEnumerable ChainSpecRunnerTests get { int index = 0; - foreach (var cachedProvider in _cachedProviders!.Value) + foreach ((string file, ConfigProvider configProvider) in _cachedProviders!.Value) { - yield return new TestCaseData(cachedProvider, index); + yield return new TestCaseData((Path.GetFileName(file), configProvider), index); index++; } } @@ -176,6 +175,8 @@ public async Task Smoke_cancel((string file, ConfigProvider configProvider) test return; } + if (testCase.file.Contains("none.json")) Assert.Ignore("engine port missing"); + await SmokeTest(testCase.configProvider, testIndex, 30430, true); } @@ -203,12 +204,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(); @@ -336,9 +337,6 @@ public async Task Smoke_CanResolveAllSteps((string file, ConfigProvider configPr private static async Task SmokeTest(ConfigProvider configProvider, int testIndex, int basePort, bool cancel = false) { - // An ugly hack to keep unused types - Console.WriteLine(typeof(IHiveConfig)); - Rlp.ResetDecoders(); // One day this will be fix. But that day is not today, because it is seriously difficult. configProvider.GetConfig().DiagnosticMode = DiagnosticMode.MemDb; var tempPath = TempPath.GetTempDirectory(); diff --git a/src/Nethermind/Nethermind.Runner.Test/Init/ExitOnInvalidBlock.cs b/src/Nethermind/Nethermind.Runner.Test/Init/ExitOnInvalidBlock.cs index f82f6647a230..cadcaf42fb6f 100644 --- a/src/Nethermind/Nethermind.Runner.Test/Init/ExitOnInvalidBlock.cs +++ b/src/Nethermind/Nethermind.Runner.Test/Init/ExitOnInvalidBlock.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Threading.Tasks; using Autofac; using Nethermind.Api; using Nethermind.Config; diff --git a/src/Nethermind/Nethermind.Runner.Test/JsonRpc/CountingWriterTests.cs b/src/Nethermind/Nethermind.Runner.Test/JsonRpc/CountingWriterTests.cs new file mode 100644 index 000000000000..d24da0a7ffe7 --- /dev/null +++ b/src/Nethermind/Nethermind.Runner.Test/JsonRpc/CountingWriterTests.cs @@ -0,0 +1,587 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO; +using System.IO.Pipelines; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Nethermind.JsonRpc; +using Nethermind.Serialization.Json; +using NUnit.Framework; + +#nullable enable + +namespace Nethermind.Runner.Test.JsonRpc; + +/// +/// Tests demonstrating the buffering problem with CountingPipeWriter (old path) +/// and how CountingStreamPipeWriter (new path from PR #9411) fixes it. +/// +/// Problem: Kestrel's PipeWriter accumulates the ENTIRE serialized response in internal +/// pipe buffers before the socket layer drains them. For multi-MB responses (eth_getLogs, +/// debug_traceTransaction, large batches), this causes LOH allocations and GC pressure. +/// +/// Fix: CountingStreamPipeWriter writes to ctx.Response.Body (Stream) directly, auto-flushing +/// in small chunks (~4KB) as data is produced. Peak memory stays bounded regardless of +/// response size. +/// +[TestFixture] +public class CountingWriterTests +{ + private static readonly EthereumJsonSerializer Serializer = new(); + + // ────────────────────────────────────────────────────────── + // PROBLEM DEMONSTRATION: PipeWriter holds entire response in memory + // ────────────────────────────────────────────────────────── + + [TestCase(1)] // 1MB + [TestCase(5)] // 5MB + [TestCase(10)] // 10MB + public async Task Problem_PipeWriter_BuffersEntireResponse_BeforeConsumerSeesAnything(int sizeMB) + { + // This simulates the OLD behavior: CountingPipeWriter wraps Kestrel's PipeWriter. + // The pipe holds ALL data until the reader (socket layer) drains it. + int totalBytes = sizeMB * 1024 * 1024; + Pipe pipe = new(new PipeOptions(pauseWriterThreshold: long.MaxValue)); + CountingPipeWriter writer = new(pipe.Writer); + + // Write data in 4KB chunks (simulating Utf8JsonWriter behavior) + byte[] chunk = new byte[4096]; + Array.Fill(chunk, (byte)'x'); + int written = 0; + while (written < totalBytes) + { + int toWrite = Math.Min(chunk.Length, totalBytes - written); + writer.Write(chunk.AsSpan(0, toWrite)); + written += toWrite; + } + + await writer.CompleteAsync(); + + // PROBLEM: The pipe reader now sees the ENTIRE response as one contiguous buffer. + // In Kestrel, this means the response body is fully buffered in memory before + // a single byte reaches the network socket. + ReadResult result = await pipe.Reader.ReadAsync(); + long bufferedSize = result.Buffer.Length; + + bufferedSize.Should().Be(totalBytes, + $"PipeWriter accumulated the entire {sizeMB}MB response in pipe buffers — " + + "this is the LOH allocation problem PR #9411 fixes"); + + pipe.Reader.AdvanceTo(result.Buffer.End); + await pipe.Reader.CompleteAsync(); + } + + [TestCase(1)] // 1MB + [TestCase(5)] // 5MB + [TestCase(10)] // 10MB + public async Task Fix_StreamWriter_FlushesIncrementally_PeakBufferStaysSmall(int sizeMB) + { + // This simulates the NEW behavior: CountingStreamPipeWriter writes to + // ctx.Response.Body (Stream). It auto-flushes when internal buffer exceeds 4KB. + int totalBytes = sizeMB * 1024 * 1024; + TrackingStream tracker = new(); + CountingStreamPipeWriter writer = new(tracker); + + byte[] chunk = new byte[4096]; + Array.Fill(chunk, (byte)'x'); + int written = 0; + while (written < totalBytes) + { + int toWrite = Math.Min(chunk.Length, totalBytes - written); + writer.Write(chunk.AsSpan(0, toWrite)); + written += toWrite; + } + + await writer.CompleteAsync(); + + // FIX: The stream received many small writes instead of one huge buffer + tracker.WriteCallCount.Should().BeGreaterThan(1, + "StreamWriter should flush incrementally to the underlying stream"); + + // The largest single write to the stream should be bounded (not the full response) + tracker.LargestSingleWrite.Should().BeLessThan(totalBytes, + $"No single write to the stream should be {sizeMB}MB — data should flow in chunks"); + + // Peak internal buffer is bounded by the auto-flush threshold (~4KB + segment overhead) + // not by total response size + tracker.LargestSingleWrite.Should().BeLessOrEqualTo(64 * 1024, + "Individual writes to the stream should be small (auto-flush at ~4KB boundary)"); + + writer.WrittenCount.Should().Be(totalBytes); + } + + // ────────────────────────────────────────────────────────── + // ALLOCATION COMPARISON: GC pressure difference + // ────────────────────────────────────────────────────────── + + [Test] + public async Task Problem_PipeWriter_AllocatesMoreForLargeResponses() + { + // Serialize a 2MB response through both paths and compare allocations + string largeData = new('B', 2 * 1024 * 1024); + JsonRpcSuccessResponse response = new() { Id = 1, Result = largeData }; + + // Warm up both paths + await SerializeViaPipeWriter(response); + await SerializeViaStreamWriter(response); + + // Measure PipeWriter allocations + GC.Collect(2, GCCollectionMode.Forced, true, true); + GC.WaitForPendingFinalizers(); + long beforePipe = GC.GetAllocatedBytesForCurrentThread(); + await SerializeViaPipeWriter(response); + long pipeAllocations = GC.GetAllocatedBytesForCurrentThread() - beforePipe; + + // Measure StreamWriter allocations + GC.Collect(2, GCCollectionMode.Forced, true, true); + GC.WaitForPendingFinalizers(); + long beforeStream = GC.GetAllocatedBytesForCurrentThread(); + await SerializeViaStreamWriter(response); + long streamAllocations = GC.GetAllocatedBytesForCurrentThread() - beforeStream; + + // StreamWriter should allocate less because it doesn't need the Pipe's internal + // buffer segments to hold the entire response. It flushes to a MemoryStream in chunks. + // Note: In production with Kestrel, the difference is even larger because the Pipe's + // internal buffers come from ArrayPool and may cause LOH fragmentation. + Console.WriteLine($"PipeWriter allocations: {pipeAllocations:N0} bytes"); + Console.WriteLine($"StreamWriter allocations: {streamAllocations:N0} bytes"); + Console.WriteLine($"Difference: {pipeAllocations - streamAllocations:N0} bytes ({(double)pipeAllocations / streamAllocations:F2}x)"); + + // We don't assert a hard ratio since GC allocation tracking is approximate, + // but log the comparison for review + streamAllocations.Should().BeGreaterThan(0, "sanity check"); + } + + // ────────────────────────────────────────────────────────── + // BATCH SCENARIO: the most impactful real-world case + // ────────────────────────────────────────────────────────── + + [Test] + public async Task Problem_PipeWriter_BatchResponse_EntirelyBuffered() + { + // Simulate a 50-entry batch response where each entry is ~10KB + // Total response: ~500KB — enough to cause LOH allocation + TrackingPipe tracker = new(); + CountingPipeWriter writer = new(tracker.Writer); + + await WriteBatchResponse(writer, entryCount: 50, entryDataSize: 10_000); + await writer.CompleteAsync(); + + // Read what the pipe accumulated + ReadResult result = await tracker.Reader.ReadAsync(); + long totalBuffered = result.Buffer.Length; + tracker.Reader.AdvanceTo(result.Buffer.End); + await tracker.Reader.CompleteAsync(); + + totalBuffered.Should().BeGreaterThan(400_000, + "PipeWriter buffered the entire ~500KB batch response before any byte reached the consumer"); + + writer.WrittenCount.Should().Be(totalBuffered); + } + + [Test] + public async Task Fix_StreamWriter_BatchResponse_StreamedIncrementally() + { + // Same 50-entry batch, but through CountingStreamPipeWriter + TrackingStream tracker = new(); + CountingStreamPipeWriter writer = new(tracker); + + await WriteBatchResponse(writer, entryCount: 50, entryDataSize: 10_000); + await writer.CompleteAsync(); + + // The stream received the data in many small writes + tracker.WriteCallCount.Should().BeGreaterThan(10, + "batch response should stream incrementally, not in one shot"); + + tracker.LargestSingleWrite.Should().BeLessThan(100_000, + "no single write to the stream should be a significant fraction of the total response"); + + writer.WrittenCount.Should().BeGreaterThan(400_000); + } + + // ────────────────────────────────────────────────────────── + // CORRECTNESS: ensure both paths produce identical output + // ────────────────────────────────────────────────────────── + + [Test] + public async Task BothWriters_ProduceIdenticalOutput_SmallResponse() + { + JsonRpcSuccessResponse response = new() { Id = 1, Result = "0x1234" }; + + (byte[] pipeBytes, long pipeCount) = await SerializeViaPipeWriter(response); + (byte[] streamBytes, long streamCount) = await SerializeViaStreamWriter(response); + + streamBytes.Should().Equal(pipeBytes, "both writers must produce identical JSON bytes"); + streamCount.Should().Be(pipeCount, "WrittenCount must match"); + } + + [Test] + public async Task BothWriters_ProduceIdenticalOutput_ErrorResponse() + { + JsonRpcErrorResponse response = new() + { + Id = 42, + Error = new Error { Code = -32600, Message = "Invalid request" } + }; + + (byte[] pipeBytes, long pipeCount) = await SerializeViaPipeWriter(response); + (byte[] streamBytes, long streamCount) = await SerializeViaStreamWriter(response); + + streamBytes.Should().Equal(pipeBytes); + streamCount.Should().Be(pipeCount); + } + + [TestCase(100)] + [TestCase(1_000)] + [TestCase(10_000)] + public async Task BothWriters_ProduceIdenticalOutput_LargeArray(int elementCount) + { + List> largeResult = new(elementCount); + for (int i = 0; i < elementCount; i++) + { + largeResult.Add(new Dictionary + { + ["address"] = "0x" + i.ToString("x40"), + ["data"] = "0x" + new string('a', 256), + ["blockNumber"] = "0x" + i.ToString("x"), + ["transactionHash"] = "0x" + new string((char)('0' + (i % 10)), 64), + }); + } + + JsonRpcSuccessResponse response = new() { Id = 1, Result = largeResult }; + + (byte[] pipeBytes, long pipeCount) = await SerializeViaPipeWriter(response); + (byte[] streamBytes, long streamCount) = await SerializeViaStreamWriter(response); + + streamBytes.Should().Equal(pipeBytes, "large responses must be byte-identical"); + streamCount.Should().Be(pipeCount); + } + + [TestCase(1)] + [TestCase(10)] + [TestCase(100)] + public async Task BothWriters_ProduceIdenticalOutput_BatchResponse(int batchSize) + { + byte[] openBracket = "["u8.ToArray(); + byte[] comma = ","u8.ToArray(); + byte[] closeBracket = "]"u8.ToArray(); + + // PipeWriter path + Pipe pipe = new(); + CountingPipeWriter pipeWriter = new(pipe.Writer); + pipeWriter.Write(openBracket); + for (int i = 0; i < batchSize; i++) + { + if (i > 0) pipeWriter.Write(comma); + JsonRpcSuccessResponse entry = new() { Id = i, Result = $"result_{i}" }; + await Serializer.SerializeAsync(pipeWriter, entry); + } + pipeWriter.Write(closeBracket); + await pipeWriter.CompleteAsync(); + + ReadResult pipeRead = await pipe.Reader.ReadAsync(); + byte[] pipeBytes = pipeRead.Buffer.ToArray(); + pipe.Reader.AdvanceTo(pipeRead.Buffer.End); + await pipe.Reader.CompleteAsync(); + + // StreamWriter path + MemoryStream ms = new(); + CountingStreamPipeWriter streamWriter = new(ms); + streamWriter.Write(openBracket); + for (int i = 0; i < batchSize; i++) + { + if (i > 0) streamWriter.Write(comma); + JsonRpcSuccessResponse entry = new() { Id = i, Result = $"result_{i}" }; + await Serializer.SerializeAsync(streamWriter, entry); + } + streamWriter.Write(closeBracket); + await streamWriter.CompleteAsync(); + + byte[] streamBytes = ms.ToArray(); + + streamBytes.Should().Equal(pipeBytes, "batch output must be byte-identical"); + pipeWriter.WrittenCount.Should().Be(streamWriter.WrittenCount); + + // Verify valid JSON + string json = Encoding.UTF8.GetString(streamBytes); + JsonDocument doc = JsonDocument.Parse(json); + doc.RootElement.GetArrayLength().Should().Be(batchSize); + } + + [Test] + public async Task BothWriters_ProduceIdenticalOutput_5MB() + { + string largeData = new('A', 5 * 1024 * 1024); + JsonRpcSuccessResponse response = new() { Id = 1, Result = largeData }; + + (byte[] pipeBytes, long pipeCount) = await SerializeViaPipeWriter(response); + (byte[] streamBytes, long streamCount) = await SerializeViaStreamWriter(response); + + streamCount.Should().Be(pipeCount); + streamBytes.Should().Equal(pipeBytes); + streamCount.Should().BeGreaterThan(5 * 1024 * 1024); + } + + // ────────────────────────────────────────────────────────── + // Helpers + // ────────────────────────────────────────────────────────── + + private static async Task<(byte[] Bytes, long WrittenCount)> SerializeViaPipeWriter(T value) + { + Pipe pipe = new(); + CountingPipeWriter writer = new(pipe.Writer); + + await Serializer.SerializeAsync(writer, value); + await writer.CompleteAsync(); + + ReadResult readResult = await pipe.Reader.ReadAsync(); + byte[] bytes = readResult.Buffer.ToArray(); + pipe.Reader.AdvanceTo(readResult.Buffer.End); + await pipe.Reader.CompleteAsync(); + + return (bytes, writer.WrittenCount); + } + + private static async Task<(byte[] Bytes, long WrittenCount)> SerializeViaStreamWriter(T value) + { + MemoryStream ms = new(); + CountingStreamPipeWriter writer = new(ms); + + await Serializer.SerializeAsync(writer, value); + await writer.CompleteAsync(); + + return (ms.ToArray(), writer.WrittenCount); + } + + private static async Task WriteBatchResponse(PipeWriter writer, int entryCount, int entryDataSize) + { + writer.Write("["u8); + for (int i = 0; i < entryCount; i++) + { + if (i > 0) writer.Write(","u8); + string data = new('x', entryDataSize); + JsonRpcSuccessResponse entry = new() { Id = i, Result = data }; + await Serializer.SerializeAsync(writer, entry); + } + writer.Write("]"u8); + } + + /// + /// A Stream that tracks write patterns: count, sizes, and peak single write. + /// Used to prove CountingStreamPipeWriter flushes incrementally. + /// + private sealed class TrackingStream : MemoryStream + { + public int WriteCallCount { get; private set; } + public long TotalBytesWritten { get; private set; } + public int LargestSingleWrite { get; private set; } + + public override void Write(ReadOnlySpan buffer) + { + WriteCallCount++; + TotalBytesWritten += buffer.Length; + if (buffer.Length > LargestSingleWrite) LargestSingleWrite = buffer.Length; + base.Write(buffer); + } + + public override void Write(byte[] buffer, int offset, int count) + { + WriteCallCount++; + TotalBytesWritten += count; + if (count > LargestSingleWrite) LargestSingleWrite = count; + base.Write(buffer, offset, count); + } + + public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + WriteCallCount++; + TotalBytesWritten += buffer.Length; + if (buffer.Length > LargestSingleWrite) LargestSingleWrite = buffer.Length; + return base.WriteAsync(buffer, cancellationToken); + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + WriteCallCount++; + TotalBytesWritten += count; + if (count > LargestSingleWrite) LargestSingleWrite = count; + return base.WriteAsync(buffer, offset, count, cancellationToken); + } + } + + /// + /// Thin wrapper around Pipe to expose Reader/Writer for test use with CountingPipeWriter. + /// + private sealed class TrackingPipe + { + private readonly Pipe _pipe = new(new PipeOptions(pauseWriterThreshold: long.MaxValue)); + public PipeWriter Writer => _pipe.Writer; + public PipeReader Reader => _pipe.Reader; + } + + // ────────────────────────────────────────────────────────── + // HEAD-TO-HEAD: Peak buffered memory during serialization + // + // The core problem: during synchronous JSON serialization, + // Utf8JsonWriter calls GetSpan/Advance repeatedly. PipeWriter + // accumulates ALL these bytes in its internal buffer (UnflushedBytes + // grows to total response size). CountingStreamPipeWriter auto-flushes + // to the stream every ~4KB, keeping UnflushedBytes bounded. + // + // These tests measure UnflushedBytes (peak internal buffer) during + // serialization. The PipeWriter path shows unbounded growth. + // The StreamWriter path stays bounded regardless of response size. + // ────────────────────────────────────────────────────────── + + [TestCase(1)] // 1MB + [TestCase(5)] // 5MB + public async Task PeakBuffer_PipeWriter_GrowsToFullResponseSize(int sizeMB) + { + int totalBytes = sizeMB * 1024 * 1024; + string largeData = new('Z', totalBytes); + JsonRpcSuccessResponse response = new() { Id = 1, Result = largeData }; + + // ─── Master path: CountingPipeWriter wrapping a Pipe ─── + // In production this is ctx.Response.BodyWriter (Kestrel's pipe) + Pipe pipe = new(new PipeOptions(pauseWriterThreshold: long.MaxValue)); + PeakTrackingWriter masterWriter = new(new CountingPipeWriter(pipe.Writer)); + await Serializer.SerializeAsync(masterWriter, response); + long masterPeakBuffer = masterWriter.PeakUnflushedBytes; + await masterWriter.CompleteAsync(); + // Drain pipe to complete the test + ReadResult result = await pipe.Reader.ReadAsync(); + pipe.Reader.AdvanceTo(result.Buffer.End); + await pipe.Reader.CompleteAsync(); + + // ─── PR path: CountingStreamPipeWriter wrapping a Stream ─── + // In production this is ctx.Response.Body (Kestrel's response stream) + MemoryStream ms = new(); + PeakTrackingWriter prWriter = new(new CountingStreamPipeWriter(ms)); + await Serializer.SerializeAsync(prWriter, response); + long prPeakBuffer = prWriter.PeakUnflushedBytes; + await prWriter.CompleteAsync(); + + Console.WriteLine($"=== {sizeMB}MB response: peak internal buffer ==="); + Console.WriteLine($"Master (PipeWriter): {masterPeakBuffer:N0} bytes peak"); + Console.WriteLine($"PR (StreamPipeWriter): {prPeakBuffer:N0} bytes peak"); + Console.WriteLine($"Reduction: {(double)masterPeakBuffer / prPeakBuffer:F0}x less memory held during serialization"); + + // PROOF: PipeWriter accumulated the entire response in its buffer + masterPeakBuffer.Should().BeGreaterThan(totalBytes, + $"PipeWriter should buffer the entire {sizeMB}MB response (this is the problem)"); + + // PROOF: StreamWriter kept its buffer bounded (auto-flush at ~4KB) + prPeakBuffer.Should().BeLessThan(64 * 1024, + "StreamPipeWriter should auto-flush, keeping peak buffer under 64KB"); + + // The actual ratio should be dramatic + masterPeakBuffer.Should().BeGreaterThan(prPeakBuffer * 10, + "PipeWriter's peak buffer should be >10x larger than StreamPipeWriter's"); + } + + [Test] + public async Task PeakBuffer_BatchResponse_PipeWriterUnbounded_StreamWriterBounded() + { + // 100-entry batch with 5KB per entry = ~500KB total + int entryCount = 100; + int entryDataSize = 5_000; + + // ─── Master path ─── + Pipe pipe = new(new PipeOptions(pauseWriterThreshold: long.MaxValue)); + PeakTrackingWriter masterWriter = new(new CountingPipeWriter(pipe.Writer)); + await WriteBatchResponse(masterWriter, entryCount, entryDataSize); + long masterPeak = masterWriter.PeakUnflushedBytes; + await masterWriter.CompleteAsync(); + ReadResult result = await pipe.Reader.ReadAsync(); + long totalResponseSize = result.Buffer.Length; + pipe.Reader.AdvanceTo(result.Buffer.End); + await pipe.Reader.CompleteAsync(); + + // ─── PR path ─── + MemoryStream ms = new(); + PeakTrackingWriter prWriter = new(new CountingStreamPipeWriter(ms)); + await WriteBatchResponse(prWriter, entryCount, entryDataSize); + long prPeak = prWriter.PeakUnflushedBytes; + await prWriter.CompleteAsync(); + + Console.WriteLine($"=== 100-entry batch ({totalResponseSize:N0} bytes total) ==="); + Console.WriteLine($"Master peak buffer: {masterPeak:N0} bytes"); + Console.WriteLine($"PR peak buffer: {prPeak:N0} bytes"); + Console.WriteLine($"Reduction: {(double)masterPeak / prPeak:F0}x"); + + masterPeak.Should().BeGreaterThan(totalResponseSize / 2, + "PipeWriter should buffer most of the batch response"); + + prPeak.Should().BeLessThan(64 * 1024, + "StreamPipeWriter should auto-flush, keeping peak buffer bounded"); + } + + [Test] + public async Task StreamWriter_ConsumerReceivesData_DuringSerialization() + { + // This proves data flows to the consumer DURING serialization with StreamWriter, + // rather than only AFTER serialization completes (as with PipeWriter). + int totalBytes = 2 * 1024 * 1024; + string largeData = new('W', totalBytes); + JsonRpcSuccessResponse response = new() { Id = 1, Result = largeData }; + + // Track how many bytes the stream received after each Advance call + MidpointTrackingStream tracker = new(); + CountingStreamPipeWriter writer = new(tracker); + + await Serializer.SerializeAsync(writer, response); + + // Before CompleteAsync, the stream should ALREADY have most of the data + // because CountingStreamPipeWriter auto-flushed during serialization + long bytesReceivedDuringSerialization = tracker.Position; + await writer.CompleteAsync(); + long bytesReceivedAfterComplete = tracker.Position; + + Console.WriteLine($"=== 2MB response: consumer data availability ==="); + Console.WriteLine($"Bytes received DURING serialization: {bytesReceivedDuringSerialization:N0}"); + Console.WriteLine($"Bytes received AFTER CompleteAsync: {bytesReceivedAfterComplete:N0}"); + Console.WriteLine($"Data available during serialization: {100.0 * bytesReceivedDuringSerialization / bytesReceivedAfterComplete:F1}%"); + + bytesReceivedDuringSerialization.Should().BeGreaterThan(totalBytes / 2, + "Most data should reach the consumer DURING serialization, not after — " + + "this means Kestrel can start sending bytes to the network immediately"); + } + + /// + /// Wraps any PipeWriter and tracks peak UnflushedBytes during writes. + /// This measures the maximum internal buffer size held at any point during serialization. + /// + private sealed class PeakTrackingWriter(PipeWriter inner) : PipeWriter + { + public long PeakUnflushedBytes { get; private set; } + + public override void Advance(int bytes) + { + inner.Advance(bytes); + long unflushed = inner.UnflushedBytes; + if (unflushed > PeakUnflushedBytes) PeakUnflushedBytes = unflushed; + } + + public override Memory GetMemory(int sizeHint = 0) => inner.GetMemory(sizeHint); + public override Span GetSpan(int sizeHint = 0) => inner.GetSpan(sizeHint); + public override ValueTask CompleteAsync(Exception? exception = null) => inner.CompleteAsync(exception); + public override void CancelPendingFlush() => inner.CancelPendingFlush(); + public override void Complete(Exception? exception = null) => inner.Complete(exception); + public override ValueTask FlushAsync(CancellationToken cancellationToken = default) => inner.FlushAsync(cancellationToken); + public override bool CanGetUnflushedBytes => inner.CanGetUnflushedBytes; + public override long UnflushedBytes => inner.UnflushedBytes; + } + + /// + /// A MemoryStream that can be inspected for Position during serialization + /// to prove data was flushed before serialization completed. + /// + private sealed class MidpointTrackingStream : MemoryStream { } +} diff --git a/src/Nethermind/Nethermind.Runner.Test/Module/MainProcessingContextTests.cs b/src/Nethermind/Nethermind.Runner.Test/Module/MainProcessingContextTests.cs index 516f65a0bc8e..bc8956646654 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/Nethermind.Runner.Test.csproj b/src/Nethermind/Nethermind.Runner.Test/Nethermind.Runner.Test.csproj index ab8e137ccea5..36a5f8a3267c 100644 --- a/src/Nethermind/Nethermind.Runner.Test/Nethermind.Runner.Test.csproj +++ b/src/Nethermind/Nethermind.Runner.Test/Nethermind.Runner.Test.csproj @@ -7,7 +7,6 @@ - diff --git a/src/Nethermind/Nethermind.Runner.Test/PluginDisposalTests.cs b/src/Nethermind/Nethermind.Runner.Test/PluginDisposalTests.cs new file mode 100644 index 000000000000..efc6b452145f --- /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.Test/holesky.json b/src/Nethermind/Nethermind.Runner.Test/holesky.json deleted file mode 100644 index 5766bce3a56d..000000000000 --- a/src/Nethermind/Nethermind.Runner.Test/holesky.json +++ /dev/null @@ -1,933 +0,0 @@ -{ - "_comment": "IMPORTANT! This holesky chainspec file is incorrect. it exists only for testing purposes.", - "name": "Holesky Incorrect Testnet", - "dataDir": "holesky", - "engine": { - "clique": { - "params": { - "period": 15, - "epoch": 30000 - } - } - }, - "params": { - "accountStartNonce": "0x0", - "chainID": "0x55555", - "eip140Transition": "0x0", - "eip145Transition": "0x0", - "eip150Transition": "0x0", - "eip155Transition": "0x0", - "eip160Transition": "0x0", - "eip161abcTransition": "0x0", - "eip161dTransition": "0x0", - "eip211Transition": "0x0", - "eip214Transition": "0x0", - "eip658Transition": "0x0", - "eip1014Transition": "0x0", - "eip1052Transition": "0x0", - "eip1283Transition": "0x0", - "eip1283DisableTransition": "0x0", - "eip152Transition": "0x17D433", - "eip1108Transition": "0x17D433", - "eip1344Transition": "0x17D433", - "eip1884Transition": "0x17D433", - "eip2028Transition": "0x17D433", - "eip2200Transition": "0x17D433", - "eip2565Transition": "0x441064", - "eip2929Transition": "0x441064", - "eip2930Transition": "0x441064", - "eip1559Transition": "0x4D3FCD", - "eip3198Transition": "0x4D3FCD", - "eip3529Transition": "0x4D3FCD", - "eip3541Transition": "0x4D3FCD", - "terminalTotalDifficulty": "A4A470", - "gasLimitBoundDivisor": "0x400", - "maxCodeSize": "0x6000", - "maxCodeSizeTransition": "0x0", - "maximumExtraDataSize": "0xffff", - "minGasLimit": "0x1388", - "networkID": "0x5" - }, - "genesis": { - "author": "0x0000000000000000000000000000000000000000", - "difficulty": "0x1", - "extraData": "0x22466c6578692069732061207468696e6722202d204166726900000000000000e0a2bd4258d2768837baa26a28fe71dc079f84c70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0xa00000", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "seal": { - "ethereum": { - "nonce": "0x0000000000000000", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" - } - }, - "timestamp": "0x5c51a607" - }, - "nodes": [ - "enode://011f758e6552d105183b1761c5e2dea0111bc20fd5f6422bc7f91e0fabbec9a6595caf6239b37feb773dddd3f87240d99d859431891e4a642cf2a0a9e6cbb98a@51.141.78.53:30303", - "enode://176b9417f511d05b6b2cf3e34b756cf0a7096b3094572a8f6ef4cdcb9d1f9d00683bf0f83347eebdf3b81c3521c2332086d9592802230bf528eaf606a1d9677b@13.93.54.137:30303", - "enode://46add44b9f13965f7b9875ac6b85f016f341012d84f975377573800a863526f4da19ae2c620ec73d11591fa9510e992ecc03ad0751f53cc02f7c7ed6d55c7291@94.237.54.114:30313", - "enode://c1f8b7c2ac4453271fa07d8e9ecf9a2e8285aa0bd0c07df0131f47153306b0736fd3db8924e7a9bf0bed6b1d8d4f87362a71b033dc7c64547728d953e43e59b2@52.64.155.147:30303", - "enode://f4a9c6ee28586009fb5a96c8af13a58ed6d8315a9eee4772212c1d4d9cebe5a8b8a78ea4434f318726317d04a3f531a1ef0420cf9752605a562cfe858c46e263@213.186.16.82:30303", - "enode://a61215641fb8714a373c80edbfa0ea8878243193f57c96eeb44d0bc019ef295abd4e044fd619bfc4c59731a73fb79afe84e9ab6da0c743ceb479cbb6d263fa91@3.11.147.67:30303", - "enode://b5948a2d3e9d486c4d75bf32713221c2bd6cf86463302339299bd227dc2e276cd5a1c7ca4f43a0e9122fe9af884efed563bd2a1fd28661f3b5f5ad7bf1de5949@18.218.250.66:30303", - "enode://d2b720352e8216c9efc470091aa91ddafc53e222b32780f505c817ceef69e01d5b0b0797b69db254c586f493872352f5a022b4d8479a00fc92ec55f9ad46a27e@88.99.70.182:30303", - "enode://d4f764a48ec2a8ecf883735776fdefe0a3949eb0ca476bd7bc8d0954a9defe8fea15ae5da7d40b5d2d59ce9524a99daedadf6da6283fca492cc80b53689fb3b3@46.4.99.122:32109" - ], - "accounts": { - "0x0000000000000000000000000000000000000000": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000001": { - "balance": "0x1", - "builtin": { - "name": "ecrecover", - "pricing": { - "linear": { - "base": 3000, - "word": 0 - } - } - } - }, - "0x0000000000000000000000000000000000000002": { - "balance": "0x1", - "builtin": { - "name": "sha256", - "pricing": { - "linear": { - "base": 60, - "word": 12 - } - } - } - }, - "0x0000000000000000000000000000000000000003": { - "balance": "0x1", - "builtin": { - "name": "ripemd160", - "pricing": { - "linear": { - "base": 600, - "word": 120 - } - } - } - }, - "0x0000000000000000000000000000000000000004": { - "balance": "0x1", - "builtin": { - "name": "identity", - "pricing": { - "linear": { - "base": 15, - "word": 3 - } - } - } - }, - "0x0000000000000000000000000000000000000005": { - "balance": "0x1", - "builtin": { - "name": "modexp", - "activate_at": "0x0", - "pricing": { - "modexp": { - "divisor": 20 - } - } - } - }, - "0x0000000000000000000000000000000000000006": { - "balance": "0x1", - "builtin": { - "name": "alt_bn128_add", - "activate_at": "0x0", - "pricing": { - "linear": { - "base": 500, - "word": 0 - } - } - } - }, - "0x0000000000000000000000000000000000000007": { - "balance": "0x1", - "builtin": { - "name": "alt_bn128_mul", - "activate_at": "0x0", - "pricing": { - "linear": { - "base": 40000, - "word": 0 - } - } - } - }, - "0x0000000000000000000000000000000000000008": { - "balance": "0x1", - "builtin": { - "name": "alt_bn128_pairing", - "activate_at": "0x0", - "pricing": { - "alt_bn128_pairing": { - "base": 100000, - "pair": 80000 - } - } - } - }, - "0x0000000000000000000000000000000000000009": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000000a": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000000c": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000000d": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000000e": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000000f": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000010": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000011": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000012": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000013": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000014": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000015": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000016": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000017": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000018": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000019": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000001a": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000001b": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000001c": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000001d": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000001e": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000001f": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000020": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000021": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000022": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000023": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000024": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000025": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000026": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000027": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000028": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000029": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000002a": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000002b": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000002c": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000002d": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000002e": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000002f": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000030": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000031": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000032": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000033": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000034": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000035": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000036": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000037": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000038": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000039": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000003a": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000003b": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000003c": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000003d": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000003e": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000003f": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000040": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000041": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000042": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000043": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000044": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000045": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000046": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000047": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000048": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000049": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000004a": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000004b": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000004c": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000004d": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000004e": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000004f": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000050": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000051": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000052": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000053": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000054": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000055": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000056": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000057": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000058": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000059": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000005a": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000005b": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000005c": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000005d": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000005e": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000005f": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000060": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000061": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000062": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000063": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000064": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000065": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000066": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000067": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000068": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000069": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000006a": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000006b": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000006c": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000006d": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000006e": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000006f": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000070": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000071": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000072": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000073": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000074": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000075": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000076": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000077": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000078": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000079": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000007a": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000007b": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000007c": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000007d": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000007e": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000007f": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000080": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000081": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000082": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000083": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000084": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000085": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000086": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000087": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000088": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000089": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000008a": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000008b": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000008c": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000008d": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000008e": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000008f": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000090": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000091": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000092": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000093": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000094": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000095": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000096": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000097": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000098": { - "balance": "0x1" - }, - "0x0000000000000000000000000000000000000099": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000009a": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000009b": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000009c": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000009d": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000009e": { - "balance": "0x1" - }, - "0x000000000000000000000000000000000000009f": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000a0": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000a1": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000a2": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000a3": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000a4": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000a5": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000a6": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000a7": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000a8": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000a9": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000aa": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000ab": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000ac": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000ad": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000ae": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000af": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000b0": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000b1": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000b2": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000b3": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000b4": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000b5": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000b6": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000b7": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000b8": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000b9": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000ba": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000bb": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000bc": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000bd": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000be": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000bf": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000c0": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000c1": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000c2": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000c3": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000c4": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000c5": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000c6": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000c7": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000c8": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000c9": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000ca": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000cb": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000cc": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000cd": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000ce": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000cf": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000d0": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000d1": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000d2": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000d3": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000d4": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000d5": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000d6": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000d7": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000d8": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000d9": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000da": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000db": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000dc": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000dd": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000de": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000df": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000e0": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000e1": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000e2": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000e3": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000e4": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000e5": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000e6": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000e7": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000e8": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000e9": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000ea": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000eb": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000ec": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000ed": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000ee": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000ef": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000f0": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000f1": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000f2": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000f3": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000f4": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000f5": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000f6": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000f7": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000f8": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000f9": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000fa": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000fb": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000fc": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000fd": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000fe": { - "balance": "0x1" - }, - "0x00000000000000000000000000000000000000ff": { - "balance": "0x1" - }, - "0x4c2ae482593505f0163cdefc073e81c63cda4107": { - "balance": "0x152d02c7e14af6800000" - }, - "0xa8e8f14732658e4b51e8711931053a8a69baf2b1": { - "balance": "0x152d02c7e14af6800000" - }, - "0xd9a5179f091d85051d3c982785efd1455cec8699": { - "balance": "0x84595161401484a000000" - }, - "0xe0a2bd4258d2768837baa26a28fe71dc079f84c7": { - "balance": "0x4a47e3c12448f4ad000000" - } - } -} diff --git a/src/Nethermind/Nethermind.Runner/Data/static-nodes.json b/src/Nethermind/Nethermind.Runner/Data/static-nodes.json deleted file mode 100644 index dcc120e00104..000000000000 --- a/src/Nethermind/Nethermind.Runner/Data/static-nodes.json +++ /dev/null @@ -1,4 +0,0 @@ -[ -] - - diff --git a/src/Nethermind/Nethermind.Runner/Dockerfile b/src/Nethermind/Nethermind.Runner/Dockerfile index ab6d539da849..dee7a77b1d90 100644 --- a/src/Nethermind/Nethermind.Runner/Dockerfile +++ b/src/Nethermind/Nethermind.Runner/Dockerfile @@ -1,7 +1,7 @@ # See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. # This stage is used when running from VS in fast mode (Default for Debug configuration) -FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base #USER app diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs b/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs index c9c4312a67b9..2bf6eefbe92e 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs @@ -20,7 +20,7 @@ namespace Nethermind.Runner.Ethereum.Api; public class ApiBuilder { private readonly IConfigProvider _configProvider; - private readonly IJsonSerializer _jsonSerializer; + private readonly EthereumJsonSerializer _jsonSerializer; private readonly ILogManager _logManager; private readonly ILogger _logger; private readonly IInitConfig _initConfig; @@ -36,7 +36,9 @@ public ApiBuilder(IProcessExitSource processExitSource, IConfigProvider configPr _processExitSource = processExitSource; _configProvider = configProvider ?? throw new ArgumentNullException(nameof(configProvider)); _initConfig = configProvider.GetConfig(); - _jsonSerializer = new EthereumJsonSerializer(configProvider.GetConfig().JsonSerializationMaxDepth); + IJsonRpcConfig? jsonRpcConfig = configProvider.GetConfig(); + EthereumJsonSerializer.StrictHexFormat = jsonRpcConfig.StrictHexFormat; + _jsonSerializer = new EthereumJsonSerializer(jsonRpcConfig.JsonSerializationMaxDepth); ChainSpec = LoadChainSpec(_jsonSerializer); } @@ -62,14 +64,21 @@ public EthereumRunner CreateEthereumRunner(IEnumerable plugin return container.Resolve(); } - private ChainSpec LoadChainSpec(IJsonSerializer ethereumJsonSerializer) + private ChainSpec LoadChainSpec(EthereumJsonSerializer ethereumJsonSerializer) { if (_logger.IsDebug) _logger.Debug($"Loading chain spec from {_initConfig.ChainSpecPath}"); ThisNodeInfo.AddInfo("Chainspec :", _initConfig.ChainSpecPath); - var loader = new ChainSpecFileLoader(ethereumJsonSerializer, _logger); + var loader = new ChainSpecFileLoader(ethereumJsonSerializer, _logManager); ChainSpec chainSpec = loader.LoadEmbeddedOrFromFile(_initConfig.ChainSpecPath); + + //overwriting NetworkId which is useful for some devnets (like bloatnet) + if (_initConfig.NetworkId is not null) + { + chainSpec.NetworkId = (ulong)_initConfig.NetworkId; + } + return chainSpec; } diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/EthereumRunner.cs b/src/Nethermind/Nethermind.Runner/Ethereum/EthereumRunner.cs index 01c503629655..75ec67da4646 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/EthereumRunner.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/EthereumRunner.cs @@ -1,12 +1,10 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Threading; using System.Threading.Tasks; using Autofac; using Nethermind.Api; -using Nethermind.Api.Extensions; using Nethermind.Core; using Nethermind.Core.ServiceStopper; using Nethermind.Init.Steps; @@ -42,18 +40,4 @@ public async Task StopAsync() _logger.Info("Ethereum runner stopped"); } } - - private Task Stop(Func stopAction, string description) - { - try - { - if (_logger.IsInfo) _logger.Info(description); - return stopAction() ?? Task.CompletedTask; - } - catch (Exception e) - { - if (_logger.IsError) _logger.Error($"{description} shutdown error.", e); - return Task.CompletedTask; - } - } } diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/JsonRpcRunner.cs b/src/Nethermind/Nethermind.Runner/Ethereum/JsonRpcRunner.cs index 534a11c4bdbc..45c8f41a760e 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/JsonRpcRunner.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/JsonRpcRunner.cs @@ -38,7 +38,7 @@ public class JsonRpcRunner private readonly IConfigProvider _configurationProvider; private readonly IRpcAuthentication _rpcAuthentication; private readonly ILogManager _logManager; - private readonly IJsonRpcProcessor _jsonRpcProcessor; + private readonly JsonRpcProcessor _jsonRpcProcessor; private readonly IJsonRpcUrlCollection _jsonRpcUrlCollection; private readonly IWebSocketsManager _webSocketsManager; private WebHost? _webApp; @@ -51,7 +51,7 @@ public class JsonRpcRunner private readonly IMainProcessingContext _mainProcessingContext; public JsonRpcRunner( - IJsonRpcProcessor jsonRpcProcessor, + JsonRpcProcessor jsonRpcProcessor, IJsonRpcUrlCollection jsonRpcUrlCollection, IWebSocketsManager webSocketsManager, IConfigProvider configurationProvider, diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs b/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs index a196e786b099..fbdae0a5805d 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs @@ -31,7 +31,7 @@ namespace Nethermind.Runner.Ethereum.Modules; /// /// public class NethermindRunnerModule( - IJsonSerializer jsonSerializer, + EthereumJsonSerializer jsonSerializer, ChainSpec chainSpec, IConfigProvider configProvider, IProcessExitSource processExitSource, @@ -75,16 +75,18 @@ protected override void Load(ContainerBuilder builder) .AddSingleton((api) => api.BlockPreprocessor) .AddSingleton(jsonSerializer) + .AddSingleton(jsonSerializer) .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/Bootstrap.cs b/src/Nethermind/Nethermind.Runner/JsonRpc/Bootstrap.cs index 7471c2700762..3e8d96b58138 100644 --- a/src/Nethermind/Nethermind.Runner/JsonRpc/Bootstrap.cs +++ b/src/Nethermind/Nethermind.Runner/JsonRpc/Bootstrap.cs @@ -17,9 +17,9 @@ private Bootstrap() { } public static Bootstrap Instance => _instance ??= new Bootstrap(); - public IJsonRpcService? JsonRpcService { private get; set; } + public JsonRpcService? JsonRpcService { private get; set; } public ILogManager? LogManager { private get; set; } - public IJsonSerializer? JsonSerializer { private get; set; } + public EthereumJsonSerializer? JsonSerializer { private get; set; } public IJsonRpcLocalStats? JsonRpcLocalStats { private get; set; } public IRpcAuthentication? JsonRpcAuthentication { private get; set; } diff --git a/src/Nethermind/Nethermind.Runner/JsonRpc/HostingApplication.cs b/src/Nethermind/Nethermind.Runner/JsonRpc/HostingApplication.cs index 4214dc44be04..790a05c25389 100644 --- a/src/Nethermind/Nethermind.Runner/JsonRpc/HostingApplication.cs +++ b/src/Nethermind/Nethermind.Runner/JsonRpc/HostingApplication.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Diagnostics; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Abstractions; diff --git a/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs b/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs index 767092bc9999..09c83b29fcf2 100644 --- a/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs +++ b/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs @@ -7,12 +7,14 @@ using System.IO; using System.IO.Pipelines; using System.Net; +using System.Runtime.CompilerServices; using System.Security.Authentication; +using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using HealthChecks.UI.Client; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -27,8 +29,10 @@ using Nethermind.Config; using Nethermind.Core.Authentication; using Nethermind.Core.Resettables; +using Nethermind.Facade.Eth; using Nethermind.HealthChecks; using Nethermind.JsonRpc; +using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules; using Nethermind.Logging; using Nethermind.Serialization.Json; @@ -38,6 +42,14 @@ namespace Nethermind.Runner.JsonRpc; public class Startup : IStartup { + private JsonRpcProcessor _jsonRpcProcessor = null!; + private JsonRpcService _jsonRpcService = null!; + private IJsonRpcLocalStats _jsonRpcLocalStats = null!; + private EthereumJsonSerializer _jsonSerializer = null!; + private IJsonRpcConfig _jsonRpcConfig = null!; + private IRpcAuthentication? _rpcAuthentication; + private ILogger _logger = default; + private static ReadOnlySpan _jsonOpeningBracket => [(byte)'[']; private static ReadOnlySpan _jsonComma => [(byte)',']; private static ReadOnlySpan _jsonClosingBracket => [(byte)']']; @@ -85,23 +97,30 @@ public void Configure(IApplicationBuilder app) Configure( app, services.GetRequiredService(), - services.GetRequiredService(), - services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService(), services.GetRequiredService(), - services.GetRequiredService(), + services.GetRequiredService(), services.GetRequiredService()); } - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IJsonRpcProcessor jsonRpcProcessor, IJsonRpcService jsonRpcService, IJsonRpcLocalStats jsonRpcLocalStats, IJsonSerializer jsonSerializer, ApplicationLifetime lifetime) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, JsonRpcProcessor jsonRpcProcessor, JsonRpcService jsonRpcService, IJsonRpcLocalStats jsonRpcLocalStats, EthereumJsonSerializer jsonSerializer, ApplicationLifetime lifetime) { + // Register source-generated type info resolvers before warmup + EthereumJsonSerializer.AddTypeInfoResolver(JsonRpcResponseJsonContext.Default); + EthereumJsonSerializer.AddTypeInfoResolver(FacadeJsonContext.Default); + EthereumJsonSerializer.AddTypeInfoResolver(EthRpcJsonContext.Default); + + // Warm up System.Text.Json metadata for hot response types + EthereumJsonSerializer.WarmupSerializer( + new JsonRpcSuccessResponse { Id = 0 }, + new JsonRpcErrorResponse { Id = 0, Error = new Error { Code = 0, Message = string.Empty } }); + if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } - app.UseRouting(); - app.UseCors(); - IConfigProvider? configProvider = app.ApplicationServices.GetService(); IRpcAuthentication? rpcAuthentication = app.ApplicationServices.GetService(); @@ -117,10 +136,39 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IJsonRpc IJsonRpcUrlCollection jsonRpcUrlCollection = app.ApplicationServices.GetRequiredService(); IHealthChecksConfig healthChecksConfig = configProvider.GetConfig(); - // If request is local, don't use response compression, - // as it allocates a lot, but doesn't improve much for loopback + _jsonRpcProcessor = jsonRpcProcessor; + _jsonRpcService = jsonRpcService; + _jsonRpcLocalStats = jsonRpcLocalStats; + _jsonSerializer = jsonSerializer; + _jsonRpcConfig = jsonRpcConfig; + _rpcAuthentication = rpcAuthentication; + _logger = logger; + + // Engine API fast lane: authenticated engine port POST requests bypass + // routing, CORS, compression, and WebSocket middleware + app.Use(async (ctx, next) => + { + if (ctx.Request.Method != "POST" || + !(ctx.Request.ContentType?.Contains("application/json") ?? false) || + !jsonRpcUrlCollection.TryGetValue(ctx.Connection.LocalPort, out JsonRpcUrl jsonRpcUrl) || + !jsonRpcUrl.IsAuthenticated || + !jsonRpcUrl.RpcEndpoint.HasFlag(RpcEndpoint.Http)) + { + await next(); + return; + } + + await ProcessJsonRpcRequestCoreAsync(ctx, jsonRpcUrl); + }); + + app.UseRouting(); + app.UseCors(); + + // Skip response compression for localhost (low benefit, high allocation cost) + // and for Engine API requests (latency-sensitive consensus path) app.UseWhen(ctx => - !IsLocalhost(ctx.Connection.RemoteIpAddress), + !IsLocalhost(ctx.Connection.RemoteIpAddress!) && + !(jsonRpcUrlCollection.TryGetValue(ctx.Connection.LocalPort, out JsonRpcUrl url) && url.IsAuthenticated), builder => builder.UseResponseCompression()); if (initConfig.WebSocketsEnabled) @@ -154,182 +202,45 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IJsonRpc if (logger.IsError) logger.Error("Unable to initialize health checks. Check if you have Nethermind.HealthChecks.dll in your plugins folder.", e); } - IServiceProvider services = app.ApplicationServices; endpoints.MapDataFeeds(lifetime); } }); app.MapWhen( - (ctx) => ctx.Request.ContentType?.Contains("application/json") ?? false, + ctx => ctx.Request.ContentType?.Contains("application/json") ?? false, builder => builder.Run(async ctx => { - var method = ctx.Request.Method; + string method = ctx.Request.Method; if (method is not "POST" and not "GET") { - ctx.Response.StatusCode = (int)HttpStatusCode.MethodNotAllowed; - return; - } - - if (jsonRpcProcessor.ProcessExit.IsCancellationRequested) - { - ctx.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable; + ctx.Response.StatusCode = StatusCodes.Status405MethodNotAllowed; return; } if (!jsonRpcUrlCollection.TryGetValue(ctx.Connection.LocalPort, out JsonRpcUrl jsonRpcUrl) || !jsonRpcUrl.RpcEndpoint.HasFlag(RpcEndpoint.Http)) { - ctx.Response.StatusCode = (int)HttpStatusCode.NotFound; + ctx.Response.StatusCode = StatusCodes.Status404NotFound; return; } - if (jsonRpcUrl.IsAuthenticated) - { - if (!await rpcAuthentication!.Authenticate(ctx.Request.Headers.Authorization)) - { - await PushErrorResponse(StatusCodes.Status401Unauthorized, ErrorCodes.InvalidRequest, "Authentication error"); - return; - } - } - if (method == "GET" && ctx.Request.Headers.Accept.Count > 0 && - !ctx.Request.Headers.Accept[0].Contains("text/html", StringComparison.Ordinal)) + !ctx.Request.Headers.Accept[0]!.Contains("text/html", StringComparison.Ordinal)) { await ctx.Response.WriteAsync("Nethermind JSON RPC"); } else if (ctx.Request.ContentType?.Contains("application/json") == false) { - await PushErrorResponse(StatusCodes.Status415UnsupportedMediaType, ErrorCodes.InvalidRequest, "Missing 'application/json' Content-Type header"); + await PushErrorResponseAsync(ctx, StatusCodes.Status415UnsupportedMediaType, ErrorCodes.InvalidRequest, "Missing 'application/json' Content-Type header"); } else { - if (jsonRpcUrl.MaxRequestBodySize is not null) - ctx.Features.Get().MaxRequestBodySize = jsonRpcUrl.MaxRequestBodySize; - - long startTime = Stopwatch.GetTimestamp(); - CountingPipeReader request = new(ctx.Request.BodyReader); - try - { - using JsonRpcContext jsonRpcContext = JsonRpcContext.Http(jsonRpcUrl); - await foreach (JsonRpcResult result in jsonRpcProcessor.ProcessAsync(request, jsonRpcContext)) - { - using (result) - { - await using Stream stream = jsonRpcConfig.BufferResponses ? RecyclableStream.GetStream("http") : null; - CountingWriter resultWriter = stream is not null ? new CountingStreamPipeWriter(stream) : new CountingStreamPipeWriter(ctx.Response.Body); - try - { - ctx.Response.ContentType = "application/json"; - ctx.Response.StatusCode = GetStatusCode(result); - - if (result.IsCollection) - { - resultWriter.Write(_jsonOpeningBracket); - bool first = true; - JsonRpcBatchResultAsyncEnumerator enumerator = result.BatchedResponses.GetAsyncEnumerator(CancellationToken.None); - try - { - while (await enumerator.MoveNextAsync()) - { - JsonRpcResult.Entry entry = enumerator.Current; - using (entry) - { - if (!first) - { - resultWriter.Write(_jsonComma); - } - - first = false; - await jsonSerializer.SerializeAsync(resultWriter, entry.Response); - _ = jsonRpcLocalStats.ReportCall(entry.Report); - - // We reached the limit and don't want to responded to more request in the batch - if (!jsonRpcContext.IsAuthenticated && resultWriter.WrittenCount > jsonRpcConfig.MaxBatchResponseBodySize) - { - if (logger.IsWarn) - logger.Warn( - $"The max batch response body size exceeded. The current response size {resultWriter.WrittenCount}, and the config setting is JsonRpc.{nameof(jsonRpcConfig.MaxBatchResponseBodySize)} = {jsonRpcConfig.MaxBatchResponseBodySize}"); - enumerator.IsStopped = true; - } - } - } - } - finally - { - await enumerator.DisposeAsync(); - } - - resultWriter.Write(_jsonClosingBracket); - } - else - { - await jsonSerializer.SerializeAsync(resultWriter, result.Response); - } - await resultWriter.CompleteAsync(); - if (stream is not null) - { - ctx.Response.ContentLength = resultWriter.WrittenCount; - stream.Seek(0, SeekOrigin.Begin); - await stream.CopyToAsync(ctx.Response.Body); - } - } - catch (Exception e) when (e.InnerException is OperationCanceledException) - { - await SerializeTimeoutException(resultWriter); - } - catch (OperationCanceledException) - { - await SerializeTimeoutException(resultWriter); - } - finally - { - await ctx.Response.CompleteAsync(); - } - - long handlingTimeMicroseconds = (long)Stopwatch.GetElapsedTime(startTime).TotalMicroseconds; - _ = jsonRpcLocalStats.ReportCall(result.IsCollection - ? new RpcReport("# collection serialization #", handlingTimeMicroseconds, true) - : result.Report.Value, handlingTimeMicroseconds, resultWriter.WrittenCount); - - Interlocked.Add(ref Metrics.JsonRpcBytesSentHttp, resultWriter.WrittenCount); - - // There should be only one response because we don't expect multiple JSON tokens in the request - break; - } - } - } - catch (Microsoft.AspNetCore.Http.BadHttpRequestException e) - { - if (logger.IsDebug) logger.Debug($"Couldn't read request.{Environment.NewLine}{e}"); - await PushErrorResponse(e.StatusCode, e.StatusCode == StatusCodes.Status413PayloadTooLarge - ? ErrorCodes.LimitExceeded - : ErrorCodes.InvalidRequest, - e.Message); - } - finally - { - Interlocked.Add(ref Nethermind.JsonRpc.Metrics.JsonRpcBytesReceivedHttp, ctx.Request.ContentLength ?? request.Length); - } - } - Task SerializeTimeoutException(CountingWriter resultStream) - { - JsonRpcErrorResponse? error = jsonRpcService.GetErrorResponse(ErrorCodes.Timeout, "Request was canceled due to enabled timeout."); - return jsonSerializer.SerializeAsync(resultStream, error); - } - async Task PushErrorResponse(int statusCode, int errorCode, string message) - { - JsonRpcErrorResponse? response = jsonRpcService.GetErrorResponse(errorCode, message); - ctx.Response.ContentType = "application/json"; - ctx.Response.StatusCode = statusCode; - await jsonSerializer.SerializeAsync(ctx.Response.BodyWriter, response); - await ctx.Response.CompleteAsync(); + await ProcessJsonRpcRequestCoreAsync(ctx, jsonRpcUrl); } })); if (healthChecksConfig.Enabled) { - string executableDir = Path.GetDirectoryName(Environment.ProcessPath) ?? Directory.GetCurrentDirectory(); - string wwwrootPath = Path.Combine(executableDir, "wwwroot"); - PhysicalFileProvider fileProvider = new(wwwrootPath); + var fileProvider = new ManifestEmbeddedFileProvider(typeof(Startup).Assembly, "wwwroot"); app.UseDefaultFiles(new DefaultFilesOptions { FileProvider = fileProvider }); app.UseStaticFiles(new StaticFileOptions { FileProvider = fileProvider }); @@ -363,51 +274,314 @@ private static bool IsResourceUnavailableError(JsonRpcResponse? response) or JsonRpcErrorResponse { Error.Code: ErrorCodes.LimitExceeded }; } - private sealed class CountingPipeReader : PipeReader + private async Task PushErrorResponseAsync(HttpContext ctx, int statusCode, int errorCode, string message) { - private readonly PipeReader _wrappedReader; - private ReadOnlySequence _currentSequence; + ctx.Response.ContentType = "application/json"; + ctx.Response.StatusCode = statusCode; + JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(errorCode, message); + await _jsonSerializer.SerializeAsync(ctx.Response.BodyWriter, response); + await ctx.Response.CompleteAsync(); + } - public long Length { get; private set; } + private async Task ProcessJsonRpcRequestCoreAsync(HttpContext ctx, JsonRpcUrl jsonRpcUrl) + { + if (_jsonRpcProcessor.ProcessExit.IsCancellationRequested) + { + ctx.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; + return; + } - public CountingPipeReader(PipeReader stream) + if (jsonRpcUrl.IsAuthenticated && !await _rpcAuthentication!.Authenticate(ctx.Request.Headers.Authorization)) { - _wrappedReader = stream; + await PushErrorResponseAsync(ctx, StatusCodes.Status401Unauthorized, ErrorCodes.InvalidRequest, "Authentication error"); + return; } + if (jsonRpcUrl.MaxRequestBodySize is not null) + ctx.Features.Get()!.MaxRequestBodySize = jsonRpcUrl.MaxRequestBodySize; + + long startTime = Stopwatch.GetTimestamp(); + // Skip CountingPipeReader when Content-Length is known + long? knownContentLength = ctx.Request.ContentLength; + CountingPipeReader? countingReader = knownContentLength > 0 ? null : new(ctx.Request.BodyReader); + PipeReader request = countingReader ?? ctx.Request.BodyReader; + try + { + using JsonRpcContext jsonRpcContext = JsonRpcContext.Http(jsonRpcUrl); + await foreach (JsonRpcResult result in _jsonRpcProcessor.ProcessAsync(request, jsonRpcContext)) + { + using (result) + { + // Authenticated single responses bypass buffering to avoid double-copy + bool bufferResponse = _jsonRpcConfig.BufferResponses && !(jsonRpcUrl.IsAuthenticated && !result.IsCollection); + await using Stream stream = bufferResponse ? RecyclableStream.GetStream("http") : null; + CountingWriter resultWriter = stream is not null ? new CountingStreamPipeWriter(stream) : new CountingStreamPipeWriter(ctx.Response.Body); + try + { + ctx.Response.ContentType = "application/json"; + ctx.Response.StatusCode = GetStatusCode(result); + + // Flush headers before body for unbuffered responses + if (stream is null) + { + await ctx.Response.StartAsync(); + } + + if (result.IsCollection) + { + resultWriter.Write(_jsonOpeningBracket); + bool first = true; + JsonRpcBatchResultAsyncEnumerator enumerator = result.BatchedResponses.GetAsyncEnumerator(CancellationToken.None); + try + { + while (await enumerator.MoveNextAsync()) + { + JsonRpcResult.Entry entry = enumerator.Current; + using (entry) + { + if (!first) resultWriter.Write(_jsonComma); + first = false; + await _jsonSerializer.SerializeAsync(resultWriter, entry.Response); + _ = _jsonRpcLocalStats.ReportCall(entry.Report); + + // Stop batch if non-authenticated response exceeds configured size limit + if (!jsonRpcContext.IsAuthenticated && resultWriter.WrittenCount > _jsonRpcConfig.MaxBatchResponseBodySize) + { + if (_logger.IsWarn) _logger.Warn($"The max batch response body size exceeded. The current response size {resultWriter.WrittenCount}, and the config setting is JsonRpc.{nameof(_jsonRpcConfig.MaxBatchResponseBodySize)} = {_jsonRpcConfig.MaxBatchResponseBodySize}"); + enumerator.IsStopped = true; + } + } + } + } + finally + { + await enumerator.DisposeAsync(); + } + resultWriter.Write(_jsonClosingBracket); + } + else if (result.Response is JsonRpcSuccessResponse { Result: IStreamableResult streamable }) + { + await WriteStreamableResponseAsync(resultWriter, result.Response, streamable, ctx.RequestAborted); + } + else + { + WriteJsonRpcResponse(resultWriter, result.Response); + } + await resultWriter.CompleteAsync(); + if (stream is not null) + { + ctx.Response.ContentLength = resultWriter.WrittenCount; + stream.Seek(0, SeekOrigin.Begin); + await stream.CopyToAsync(ctx.Response.Body); + } + } + catch (Exception e) when (e is OperationCanceledException || e.InnerException is OperationCanceledException) + { + JsonRpcErrorResponse error = _jsonRpcService.GetErrorResponse(ErrorCodes.Timeout, "Request was canceled due to enabled timeout."); + await _jsonSerializer.SerializeAsync(resultWriter, error); + } + finally + { + await ctx.Response.CompleteAsync(); + } + + long handlingTimeMicroseconds = (long)Stopwatch.GetElapsedTime(startTime).TotalMicroseconds; + _ = _jsonRpcLocalStats.ReportCall(result.IsCollection + ? new RpcReport("# collection serialization #", handlingTimeMicroseconds, true) + : result.Report.Value, handlingTimeMicroseconds, resultWriter.WrittenCount); + Interlocked.Add(ref Metrics.JsonRpcBytesSentHttp, resultWriter.WrittenCount); + break; + } + } + } + catch (Microsoft.AspNetCore.Http.BadHttpRequestException e) + { + if (_logger.IsDebug) LogBadRequest(_logger, e); + ctx.Response.ContentType = "application/json"; + ctx.Response.StatusCode = e.StatusCode; + JsonRpcErrorResponse errResp = _jsonRpcService.GetErrorResponse( + e.StatusCode == StatusCodes.Status413PayloadTooLarge ? ErrorCodes.LimitExceeded : ErrorCodes.InvalidRequest, + e.Message); + await _jsonSerializer.SerializeAsync(ctx.Response.BodyWriter, errResp); + await ctx.Response.CompleteAsync(); + } + finally + { + Interlocked.Add(ref Metrics.JsonRpcBytesReceivedHttp, knownContentLength ?? countingReader?.Length ?? 0); + } + } + + /// + /// Writes a JSON-RPC response with typed serialization for the result/error payload, + /// avoiding polymorphic dispatch through the JsonRpcResponse base class hierarchy. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteJsonRpcResponse(IBufferWriter writer, JsonRpcResponse response) + { + using var jsonWriter = new Utf8JsonWriter(writer, new JsonWriterOptions { SkipValidation = true }); + + jsonWriter.WriteStartObject(); + jsonWriter.WriteString("jsonrpc"u8, "2.0"u8); + + if (response is JsonRpcSuccessResponse successResponse) + { + jsonWriter.WritePropertyName("result"u8); + object? result = successResponse.Result; + if (result is not null) + { + JsonSerializer.Serialize(jsonWriter, result, result.GetType(), EthereumJsonSerializer.JsonOptions); + } + else + { + jsonWriter.WriteNullValue(); + } + } + else if (response is JsonRpcErrorResponse errorResponse) + { + jsonWriter.WritePropertyName("error"u8); + if (errorResponse.Error is not null) + { + JsonSerializer.Serialize(jsonWriter, errorResponse.Error, EthereumJsonSerializer.JsonOptions); + } + else + { + jsonWriter.WriteNullValue(); + } + } + + jsonWriter.WritePropertyName("id"u8); + WriteId(jsonWriter, response.Id); + + jsonWriter.WriteEndObject(); + } + + private static void WriteId(Utf8JsonWriter writer, object? id) + { + switch (id) + { + case int intId: + writer.WriteNumberValue(intId); + break; + case long longId: + writer.WriteNumberValue(longId); + break; + case string strId: + writer.WriteStringValue(strId); + break; + case null: + writer.WriteNullValue(); + break; + default: + WriteOther(writer, id); + break; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + void WriteOther(Utf8JsonWriter writer, object? id) + { + JsonSerializer.Serialize(writer, id, id.GetType(), EthereumJsonSerializer.JsonOptions); + } + } + + private static async ValueTask WriteStreamableResponseAsync( + CountingWriter writer, JsonRpcResponse response, + IStreamableResult streamable, CancellationToken ct) + { + writer.Write("{\"jsonrpc\":\"2.0\",\"result\":"u8); + await streamable.WriteToAsync(writer, ct); + writer.Write(",\"id\":"u8); + WriteIdRaw(writer, response.Id); + writer.Write("}"u8); + } + + private static void WriteIdRaw(PipeWriter writer, object? id) + { + switch (id) + { + case int intId: + { + Span buf = writer.GetSpan(11); + intId.TryFormat(buf, out int written); + writer.Advance(written); + break; + } + case long longId: + { + Span buf = writer.GetSpan(20); + longId.TryFormat(buf, out int written); + writer.Advance(written); + break; + } + default: + WriteOther(writer, id); + break; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + void WriteOther(PipeWriter writer, object? id) + { + switch (id) + { + case string strId: + { + // JSON-RPC IDs are simple values (typically numeric); no escaping needed + Span buf = writer.GetSpan(strId.Length * 3 + 2); + buf[0] = (byte)'"'; + int len = Encoding.UTF8.GetBytes(strId, buf[1..]); + buf[len + 1] = (byte)'"'; + writer.Advance(len + 2); + break; + } + default: + writer.Write("null"u8); + break; + } + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void LogBadRequest(ILogger logger, Exception e) => + logger.Debug($"Couldn't read request.{Environment.NewLine}{e}"); + + private sealed class CountingPipeReader(PipeReader stream) : PipeReader + { + private ReadOnlySequence _currentSequence; + + public long Length { get; private set; } + public override void AdvanceTo(SequencePosition consumed) { Length += _currentSequence.GetOffset(consumed); - _wrappedReader.AdvanceTo(consumed); + stream.AdvanceTo(consumed); } public override void AdvanceTo(SequencePosition consumed, SequencePosition examined) { Length += _currentSequence.GetOffset(consumed); - _wrappedReader.AdvanceTo(consumed, examined); + stream.AdvanceTo(consumed, examined); } public override void CancelPendingRead() { - _wrappedReader.CancelPendingRead(); + stream.CancelPendingRead(); } public override void Complete(Exception? exception = null) { Length += _currentSequence.Length; - _wrappedReader.Complete(exception); + stream.Complete(exception); } public override async ValueTask ReadAsync(CancellationToken cancellationToken = default) { - ReadResult result = await _wrappedReader.ReadAsync(cancellationToken); + ReadResult result = await stream.ReadAsync(cancellationToken); _currentSequence = result.Buffer; return result; } public override bool TryRead(out ReadResult result) { - bool didRead = _wrappedReader.TryRead(out result); + bool didRead = stream.TryRead(out result); if (didRead) { _currentSequence = result.Buffer; diff --git a/src/Nethermind/Nethermind.Runner/JsonRpc/WebHost.cs b/src/Nethermind/Nethermind.Runner/JsonRpc/WebHost.cs index a2e4af7768d7..a8ac6bfc76a7 100644 --- a/src/Nethermind/Nethermind.Runner/JsonRpc/WebHost.cs +++ b/src/Nethermind/Nethermind.Runner/JsonRpc/WebHost.cs @@ -15,12 +15,13 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting.Internal; using Nethermind.Logging; namespace Nethermind.Runner.JsonRpc; -internal sealed partial class WebHost : IWebHost, IAsyncDisposable +internal sealed partial class WebHost : IHost, IAsyncDisposable { private ApplicationLifetime? _applicationLifetime; private readonly IConfiguration _config; @@ -81,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 12e8c198ce0e..bdc23ed5a3ec 100644 --- a/src/Nethermind/Nethermind.Runner/Monitoring/DataFeed.cs +++ b/src/Nethermind/Nethermind.Runner/Monitoring/DataFeed.cs @@ -10,7 +10,6 @@ using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Nethermind.Api; using Nethermind.Blockchain; using Nethermind.Blockchain.Receipts; using Nethermind.Consensus.Processing; @@ -57,7 +56,7 @@ public DataFeed( CancellationToken lifetime) { ArgumentNullException.ThrowIfNull(txPool); - ArgumentNullException.ThrowIfNull(syncPeerPool); + ArgumentNullException.ThrowIfNull(specProvider); ArgumentNullException.ThrowIfNull(receiptFinder); ArgumentNullException.ThrowIfNull(blockTree); ArgumentNullException.ThrowIfNull(syncPeerPool); @@ -426,7 +425,7 @@ private class ReceiptForWeb public UInt256 EffectiveGasPrice { get; set; } public Address? ContractAddress { get; set; } public LogEntryForWeb[] Logs { get; set; } - public long Status { get; set; } + public long? Status { get; set; } public UInt256 BlobGasPrice { get; set; } public ulong BlobGasUsed { get; set; } } diff --git a/src/Nethermind/Nethermind.Runner/Monitoring/DataFeedExtensions.cs b/src/Nethermind/Nethermind.Runner/Monitoring/DataFeedExtensions.cs index c5f8dd28d083..cb3a27d243f9 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/Monitoring/TxPool/Node.cs b/src/Nethermind/Nethermind.Runner/Monitoring/TxPool/Node.cs index 4ac4fa7d3aee..8b1a5b34d17e 100644 --- a/src/Nethermind/Nethermind.Runner/Monitoring/TxPool/Node.cs +++ b/src/Nethermind/Nethermind.Runner/Monitoring/TxPool/Node.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; - namespace Nethermind.Runner.Monitoring.TransactionPool; internal class Node(string name, bool inclusion = false) diff --git a/src/Nethermind/Nethermind.Runner/NLog.config b/src/Nethermind/Nethermind.Runner/NLog.config index 308420fbc7c8..17d3b6cd2a81 100644 --- a/src/Nethermind/Nethermind.Runner/NLog.config +++ b/src/Nethermind/Nethermind.Runner/NLog.config @@ -2,7 +2,9 @@ + autoReload="true" + throwExceptions="false" + flushAllBeforeShutdown="true"> @@ -57,7 +59,7 @@ - + @@ -76,7 +78,7 @@ - + @@ -135,7 +137,7 @@ - You can also use wilcards + You can also use wildcards --> @@ -174,6 +176,9 @@ + + + diff --git a/src/Nethermind/Nethermind.Runner/Nethermind.Runner.csproj b/src/Nethermind/Nethermind.Runner/Nethermind.Runner.csproj index 3e4ba1abd453..cb91c1f95589 100644 --- a/src/Nethermind/Nethermind.Runner/Nethermind.Runner.csproj +++ b/src/Nethermind/Nethermind.Runner/Nethermind.Runner.csproj @@ -8,12 +8,19 @@ 0 true true + true false false + false false 03db39d0-4200-473e-9ff8-4a48d496381f + + true + linux-arm64;linux-x64;osx-arm64;osx-x64;win-x64 + + Fast Linux @@ -26,6 +33,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -33,7 +41,6 @@ - @@ -81,17 +88,11 @@ PreserveNewest true - - PreserveNewest - PreserveNewest - true - - - PreserveNewest - + + Always diff --git a/src/Nethermind/Nethermind.Runner/NethermindPlugins.cs b/src/Nethermind/Nethermind.Runner/NethermindPlugins.cs index f80a09991c73..ac3d28dbb8ec 100644 --- a/src/Nethermind/Nethermind.Runner/NethermindPlugins.cs +++ b/src/Nethermind/Nethermind.Runner/NethermindPlugins.cs @@ -10,7 +10,6 @@ public static class NethermindPlugins { public static readonly IReadOnlyList EmbeddedPlugins = [ - typeof(Nethermind.Analytics.AnalyticsPlugin), typeof(Nethermind.Consensus.AuRa.AuRaPlugin), typeof(Nethermind.Consensus.Clique.CliquePlugin), typeof(Nethermind.Consensus.Ethash.EthashPlugin), diff --git a/src/Nethermind/Nethermind.Runner/Program.cs b/src/Nethermind/Nethermind.Runner/Program.cs index 6bc82e98bb20..2440b46e7819 100644 --- a/src/Nethermind/Nethermind.Runner/Program.cs +++ b/src/Nethermind/Nethermind.Runner/Program.cs @@ -38,7 +38,9 @@ using ILogger = Nethermind.Logging.ILogger; using NullLogger = Nethermind.Logging.NullLogger; using DotNettyLoggerFactory = DotNetty.Common.Internal.Logging.InternalLoggerFactory; +#if !DEBUG using DotNettyLeakDetector = DotNetty.Common.ResourceLeakDetector; +#endif DataFeed.StartTime = Environment.TickCount64; Console.Title = ProductInfo.Name; @@ -50,6 +52,13 @@ ILogger logger = new(SimpleConsoleLogger.Instance); ProcessExitSource? processExitSource = default; var unhandledError = "A critical error has occurred"; +Option[] deprecatedOptions = +[ + BasicOptions.ConfigurationDirectory, + BasicOptions.DatabasePath, + BasicOptions.LoggerConfigurationSource, + BasicOptions.PluginsDirectory +]; AppDomain.CurrentDomain.UnhandledException += (sender, e) => { @@ -213,10 +222,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)) @@ -225,9 +237,6 @@ static Option CreateOption(string name, Type configType) => foreach (Type configType in configTypes.Where(ct => !ct.IsAssignableTo(typeof(INoCategoryConfig))).OrderBy(c => c.Name)) { - if (configType is null) - continue; - ConfigCategoryAttribute? typeLevel = configType.GetCustomAttribute(); if (typeLevel is not null && typeLevel.DisabledForCli) @@ -238,19 +247,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 +268,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); } } @@ -272,14 +281,6 @@ static Option CreateOption(string name, Type configType) => void CheckForDeprecatedOptions(ParseResult parseResult) { - Option[] deprecatedOptions = - [ - BasicOptions.ConfigurationDirectory, - BasicOptions.DatabasePath, - BasicOptions.LoggerConfigurationSource, - BasicOptions.PluginsDirectory - ]; - foreach (Token token in parseResult.Tokens) { foreach (Option option in deprecatedOptions) @@ -429,6 +430,8 @@ RootCommand CreateRootCommand() BasicOptions.PluginsDirectory ]; + rootCommand.Description = "Nethermind Ethereum execution client"; + var versionOption = (VersionOption)rootCommand.Children.SingleOrDefault(c => c is VersionOption); if (versionOption is not null) @@ -436,11 +439,11 @@ RootCommand CreateRootCommand() versionOption.Action = new AsynchronousCommandLineAction(parseResult => { parseResult.InvocationConfiguration.Output.WriteLine($""" - Version: {ProductInfo.Version} - Commit: {ProductInfo.Commit} - Build date: {ProductInfo.BuildTimestamp:u} - Runtime: {ProductInfo.Runtime} - Platform: {ProductInfo.OS} {ProductInfo.OSArchitecture} + Version: {ProductInfo.Version} + Commit: {ProductInfo.Commit} + Source date: {ProductInfo.SourceDate:u} + Runtime: {ProductInfo.Runtime} + Platform: {ProductInfo.OS} {ProductInfo.OSArchitecture} """); return ExitCodes.Ok; @@ -484,30 +487,31 @@ void ResolveDataDirectory(string? path, IInitConfig initConfig, IKeyStoreConfig { if (string.IsNullOrWhiteSpace(path)) { - initConfig.BaseDbPath ??= string.Empty.GetApplicationResourcePath("db"); - keyStoreConfig.KeyStoreDirectory ??= string.Empty.GetApplicationResourcePath("keystore"); - initConfig.LogDirectory ??= string.Empty.GetApplicationResourcePath("logs"); + initConfig.BaseDbPath ??= "db".GetApplicationResourcePath(); + initConfig.LogDirectory ??= "logs".GetApplicationResourcePath(); + keyStoreConfig.KeyStoreDirectory ??= "keystore".GetApplicationResourcePath(); } else { string newDbPath = initConfig.BaseDbPath.GetApplicationResourcePath(path); - string newKeyStorePath = keyStoreConfig.KeyStoreDirectory.GetApplicationResourcePath(path); string newLogDirectory = initConfig.LogDirectory.GetApplicationResourcePath(path); + string newKeyStorePath = keyStoreConfig.KeyStoreDirectory.GetApplicationResourcePath(path); string newSnapshotPath = snapshotConfig.SnapshotDirectory.GetApplicationResourcePath(path); if (logger.IsInfo) { logger.Info($"{nameof(initConfig.BaseDbPath)}: {Path.GetFullPath(newDbPath)}"); - logger.Info($"{nameof(keyStoreConfig.KeyStoreDirectory)}: {Path.GetFullPath(newKeyStorePath)}"); logger.Info($"{nameof(initConfig.LogDirectory)}: {Path.GetFullPath(newLogDirectory)}"); + logger.Info($"{nameof(keyStoreConfig.KeyStoreDirectory)}: {Path.GetFullPath(newKeyStorePath)}"); if (snapshotConfig.Enabled) logger.Info($"{nameof(snapshotConfig.SnapshotDirectory)}: {Path.GetFullPath(newSnapshotPath)}"); } initConfig.BaseDbPath = newDbPath; - keyStoreConfig.KeyStoreDirectory = newKeyStorePath; + initConfig.DataDir = path; initConfig.LogDirectory = newLogDirectory; + keyStoreConfig.KeyStoreDirectory = newKeyStorePath; snapshotConfig.SnapshotDirectory = newSnapshotPath; } } diff --git a/src/Nethermind/Nethermind.Runner/Properties/launchSettings.json b/src/Nethermind/Nethermind.Runner/Properties/launchSettings.json index d98931d69082..6540a12fc1a4 100644 --- a/src/Nethermind/Nethermind.Runner/Properties/launchSettings.json +++ b/src/Nethermind/Nethermind.Runner/Properties/launchSettings.json @@ -35,13 +35,6 @@ "ASPNETCORE_ENVIRONMENT": "Development" } }, - "Holesky": { - "commandName": "Project", - "commandLineArgs": "-c holesky --data-dir .data", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "Hoodi": { "commandName": "Project", "commandLineArgs": "-c hoodi --data-dir .data", @@ -105,9 +98,9 @@ "ASPNETCORE_ENVIRONMENT": "Development" } }, - "Taiko Hekla": { + "Taiko Hoodi": { "commandName": "Project", - "commandLineArgs": "-c taiko-hekla --data-dir .data", + "commandLineArgs": "-c taiko-hoodi --data-dir .data", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -121,11 +114,11 @@ }, "Docker": { "commandName": "Docker", - "commandLineArgs": "-c holesky --data-dir /data --jsonrpc-enginehost 0.0.0.0 --jsonrpc-engineport 8551 --jsonrpc-host 0.0.0.0" + "commandLineArgs": "-c hoodi --data-dir /data --jsonrpc-enginehost 0.0.0.0 --jsonrpc-engineport 8551 --jsonrpc-host 0.0.0.0" }, "WSL": { "commandName": "WSL2", - "commandLineArgs": "\"{OutDir}/nethermind.dll\" -c holesky --data-dir .data", + "commandLineArgs": "\"{OutDir}/nethermind.dll\" -c hoodi --data-dir .data", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, diff --git a/src/Nethermind/Nethermind.Runner/configs/arena-z-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/arena-z-mainnet.json index 3c23a801db48..bab58793cb55 100644 --- a/src/Nethermind/Nethermind.Runner/configs/arena-z-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/arena-z-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://rpc.arena-z.gg" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/arena-z-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/arena-z-sepolia.json index 6cff259a23a6..e5f4106176c5 100644 --- a/src/Nethermind/Nethermind.Runner/configs/arena-z-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/arena-z-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://testnet-rpc.arena-z.gg" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/automata-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/automata-mainnet.json index 86f9a12856a2..c30c764476e6 100644 --- a/src/Nethermind/Nethermind.Runner/configs/automata-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/automata-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://automata-mainnet.alt.technology/" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/base-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/base-mainnet.json index 4702b06729fb..932eb67f0559 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": 36100000, - "PivotHash": "0x114bfd8d8872291a20112f14e54c7db226ad46a8ed4a08183973ead45718b79d" + "PivotNumber": 42150000, + "PivotHash": "0x54691ca9f732f6b3a2fd2b85ecf7359744c34bbc9ca08c619fdebe857196fb4f" }, "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 102551520364..571e90d9457b 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": 31610000, - "PivotHash": "0x6899ed4a4f02968caebc48669e2bb9d6f21e14a99565c751350e28953567761f" + "PivotNumber": 37660000, + "PivotHash": "0x1c23e29f8f4e5e3cc356bfeb79f01ceb5d9cf0026d39667f66bb1032c655ca9d" }, "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/bob-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/bob-mainnet.json index 9bdb07f48e6e..d79f8353c610 100644 --- a/src/Nethermind/Nethermind.Runner/configs/bob-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/bob-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://rpc.gobob.xyz" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/boba-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/boba-mainnet.json index d75822c652ac..3343389f324a 100644 --- a/src/Nethermind/Nethermind.Runner/configs/boba-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/boba-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://mainnet.boba.network" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/boba-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/boba-sepolia.json index 628f4bd262b9..c488fa4552f3 100644 --- a/src/Nethermind/Nethermind.Runner/configs/boba-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/boba-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://sepolia.boba.network" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/camp-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/camp-sepolia.json index 246b54e4d8cd..2bcfaa686a52 100644 --- a/src/Nethermind/Nethermind.Runner/configs/camp-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/camp-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://rpc.camp-network-testnet.gelato.digital" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/snax-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/celo-sep-sepolia.json similarity index 62% rename from src/Nethermind/Nethermind.Runner/configs/snax-mainnet.json rename to src/Nethermind/Nethermind.Runner/configs/celo-sep-sepolia.json index 7ba2f7d3a39b..6452392575c1 100644 --- a/src/Nethermind/Nethermind.Runner/configs/snax-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/celo-sep-sepolia.json @@ -1,10 +1,10 @@ { "$schema": "https://raw.githubusercontent.com/NethermindEth/core-scripts/refs/heads/main/schemas/config.json", "Init": { - "ChainSpecPath": "chainspec/snax-mainnet.json.zst", - "GenesisHash": "0x518aadbc56e4ca8b03aa141c13b2fc246a9eae88edea09ee477f3d620b00d5ae", - "BaseDbPath": "nethermind_db/snax-mainnet", - "LogFileName": "snax-mainnet.log" + "ChainSpecPath": "chainspec/celo-sep-sepolia.json.zst", + "GenesisHash": "0x1b65cd292881564a4ba788e0822ef07f6dd558c76466ee9d1f07b57065b392f4", + "BaseDbPath": "nethermind_db/celo-sep-sepolia", + "LogFileName": "celo-sep-sepolia.log" }, "TxPool": { "BlobsSupport": "Disabled" @@ -26,12 +26,12 @@ "PruningBoundary": 256 }, "Blocks": { - "SecondsPerSlot": 2 + "SecondsPerSlot": 1 }, "Merge": { "Enabled": true }, "Optimism": { - "SequencerUrl": "https://mainnet.snaxchain.io" + "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 2f4b49fb3637..7ef8547654c1 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": 17990000, - "PivotHash": "0x7aaf5863f3ee3416c1bd0aea25cca5202dcf9e96ee41fa721b729e768d5c72c8", + "PivotNumber": 19780000, + "PivotHash": "0xac62a1145915936b74ef989b8ef0110be4542283e904fef2826726866fc6725e", "PivotTotalDifficulty": "231708131825107706987652208063906496124457284", "FastSyncCatchUpHeightDelta": 10000000000, "UseGethLimitsInFastBlocks": false diff --git a/src/Nethermind/Nethermind.Runner/configs/cyber-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/cyber-mainnet.json index 98d5e30e4d8c..bd2a0fb4c361 100644 --- a/src/Nethermind/Nethermind.Runner/configs/cyber-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/cyber-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://cyber.alt.technology/" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/cyber-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/cyber-sepolia.json index 087568083464..2873df985edf 100644 --- a/src/Nethermind/Nethermind.Runner/configs/cyber-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/cyber-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://cyber.alt.technology/" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/energyweb.json b/src/Nethermind/Nethermind.Runner/configs/energyweb.json index b4c226ce5318..c48d2021117e 100644 --- a/src/Nethermind/Nethermind.Runner/configs/energyweb.json +++ b/src/Nethermind/Nethermind.Runner/configs/energyweb.json @@ -25,13 +25,10 @@ "Metrics": { "NodeName": "Energy_Web" }, - "Mining": { - "MinGasPrice": 1 - }, "Blocks": { "GasToken": "EWT" }, "Merge": { "Enabled": false } -} \ No newline at end of file +} diff --git a/src/Nethermind/Nethermind.Runner/configs/energyweb_archive.json b/src/Nethermind/Nethermind.Runner/configs/energyweb_archive.json index f8810b6c4ff1..c105e8995b52 100644 --- a/src/Nethermind/Nethermind.Runner/configs/energyweb_archive.json +++ b/src/Nethermind/Nethermind.Runner/configs/energyweb_archive.json @@ -25,9 +25,6 @@ "Enabled": false, "IntervalSeconds": 5 }, - "Mining": { - "MinGasPrice": 1 - }, "Receipt": { "TxLookupLimit": 0 }, diff --git a/src/Nethermind/Nethermind.Runner/configs/ethernity-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/ethernity-mainnet.json index edd971404c23..a0da27de9c7c 100644 --- a/src/Nethermind/Nethermind.Runner/configs/ethernity-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/ethernity-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://mainnet.ethernitychain.io" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/ethernity-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/ethernity-sepolia.json index 2ec1cc4eb6d2..0fcb8d3694c1 100644 --- a/src/Nethermind/Nethermind.Runner/configs/ethernity-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/ethernity-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://testnet.ethernitychain.io" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/fraxtal-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/fraxtal-mainnet.json index d8cfd74ddd10..3d0f5f85f91b 100644 --- a/src/Nethermind/Nethermind.Runner/configs/fraxtal-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/fraxtal-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://rpc.frax.com" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/funki-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/funki-mainnet.json index 76bed6a6ee47..03f3a9f72cf1 100644 --- a/src/Nethermind/Nethermind.Runner/configs/funki-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/funki-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://rpc-mainnet.funkichain.com" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/funki-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/funki-sepolia.json index a8c822ed3980..43684359e3c8 100644 --- a/src/Nethermind/Nethermind.Runner/configs/funki-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/funki-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://funki-testnet.alt.technology" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/gnosis.json b/src/Nethermind/Nethermind.Runner/configs/gnosis.json index 3406ad3b0437..10ddaf8c213a 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": 42330000, - "PivotHash": "0x31957266d3c971a5090266ca6eec717f443b894f0e496e5735d5c2ccfa697607", + "PivotNumber": 44670000, + "PivotHash": "0x229a26df1c0b804793d81de764b4bcd9de5e810174f4e21ddc787d650235cc16", "PivotTotalDifficulty": "8626000110427538733349499292577475819600160930", "UseGethLimitsInFastBlocks": false, "FastSyncCatchUpHeightDelta": 10000000000, diff --git a/src/Nethermind/Nethermind.Runner/configs/gnosis_archive.json b/src/Nethermind/Nethermind.Runner/configs/gnosis_archive.json index 5130fa60d453..0f06a6cd4820 100644 --- a/src/Nethermind/Nethermind.Runner/configs/gnosis_archive.json +++ b/src/Nethermind/Nethermind.Runner/configs/gnosis_archive.json @@ -11,9 +11,6 @@ "Enabled": true, "EnginePort": 8551 }, - "Mining": { - "MinGasPrice": "1000000000" - }, "Blocks": { "SecondsPerSlot": 5, "BlockProductionTimeoutMs": 3000, diff --git a/src/Nethermind/Nethermind.Runner/configs/hashkeychain-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/hashkeychain-mainnet.json index 092edd950c0e..2e2ec620d81f 100644 --- a/src/Nethermind/Nethermind.Runner/configs/hashkeychain-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/hashkeychain-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://hashkeychain-mainnet.alt.technology" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/holesky.json b/src/Nethermind/Nethermind.Runner/configs/holesky.json deleted file mode 100644 index ac05e9bbc1f4..000000000000 --- a/src/Nethermind/Nethermind.Runner/configs/holesky.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/NethermindEth/core-scripts/refs/heads/main/schemas/config.json", - "Init": { - "ChainSpecPath": "chainspec/holesky.json", - "GenesisHash": "0xb5f7f912443c940f21fd611f12828d75b534364ed9e95ca4e307729a4661bde4", - "BaseDbPath": "nethermind_db/holesky", - "LogFileName": "holesky.log" - }, - "TxPool": { - "Size": 1024 - }, - "Sync": { - "FastSync": true, - "SnapSync": true, - "FastSyncCatchUpHeightDelta": "10000000000" - }, - "Metrics": { - "NodeName": "Holesky" - }, - "Blocks": { - "TargetBlockGasLimit": 60000000 - }, - "JsonRpc": { - "Enabled": true, - "Timeout": 20000, - "Host": "127.0.0.1", - "Port": 8545, - "EngineHost": "127.0.0.1", - "EnginePort": 8551, - "EngineEnabledModules": "net,eth,subscribe,engine,web3,client,flashbots" - }, - "Merge": { - "Enabled": true - } -} diff --git a/src/Nethermind/Nethermind.Runner/configs/holesky_archive.json b/src/Nethermind/Nethermind.Runner/configs/holesky_archive.json deleted file mode 100644 index 52e790c09b17..000000000000 --- a/src/Nethermind/Nethermind.Runner/configs/holesky_archive.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/NethermindEth/core-scripts/refs/heads/main/schemas/config.json", - "Init": { - "ChainSpecPath": "chainspec/holesky.json", - "GenesisHash": "0xb5f7f912443c940f21fd611f12828d75b534364ed9e95ca4e307729a4661bde4", - "BaseDbPath": "nethermind_db/holesky_archive", - "LogFileName": "holesky_archive.log" - }, - "TxPool": { - "Size": 1024 - }, - "Metrics": { - "NodeName": "Holesky Archive" - }, - "Blocks": { - "TargetBlockGasLimit": 60000000 - }, - "Receipt": { - "TxLookupLimit": 0 - }, - "Pruning": { - "Mode": "None" - }, - "JsonRpc": { - "Enabled": true, - "Timeout": 20000, - "Host": "127.0.0.1", - "Port": 8545, - "EngineHost": "127.0.0.1", - "EnginePort": 8551, - "EngineEnabledModules": "net,eth,subscribe,engine,web3,client" - }, - "Merge": { - "Enabled": true - } -} diff --git a/src/Nethermind/Nethermind.Runner/configs/hoodi.json b/src/Nethermind/Nethermind.Runner/configs/hoodi.json index f0529fbb6c8f..d09ca0c29c1c 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" @@ -27,9 +27,12 @@ "Port": 8545, "EngineHost": "127.0.0.1", "EnginePort": 8551, - "EngineEnabledModules": "net,eth,subscribe,engine,web3,client,flashbots" + "EngineEnabledModules": [ "net", "eth", "subscribe", "engine", "web3", "client", "flashbots" ] }, "Merge": { "Enabled": true + }, + "Discovery": { + "DiscoveryVersion": "All" } } diff --git a/src/Nethermind/Nethermind.Runner/configs/hoodi_archive.json b/src/Nethermind/Nethermind.Runner/configs/hoodi_archive.json index 296142409c44..347cae8e380a 100644 --- a/src/Nethermind/Nethermind.Runner/configs/hoodi_archive.json +++ b/src/Nethermind/Nethermind.Runner/configs/hoodi_archive.json @@ -28,7 +28,7 @@ "Port": 8545, "EngineHost": "127.0.0.1", "EnginePort": 8551, - "EngineEnabledModules": "net,eth,subscribe,engine,web3,client,flashbots" + "EngineEnabledModules": [ "net", "eth", "subscribe", "engine", "web3", "client", "flashbots" ] }, "Merge": { "Enabled": true diff --git a/src/Nethermind/Nethermind.Runner/configs/ink-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/ink-mainnet.json index 38504228297a..b60ad9afb771 100644 --- a/src/Nethermind/Nethermind.Runner/configs/ink-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/ink-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://rpc-gel.inkonchain.com" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/ink-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/ink-sepolia.json index c0de447238a4..03e7b161e4c4 100644 --- a/src/Nethermind/Nethermind.Runner/configs/ink-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/ink-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://rpc-gel-sepolia.inkonchain.com" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.json index e16c5574f563..bbfcd15344a9 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": 19870000, - "PivotHash": "0x84d796ac1e45b7afb917b6982adf81b69d3f4e12c79897811d93d2249c0a4b1d", - "PivotTotalDifficulty": "36316120" + "PivotNumber": 22290000, + "PivotHash": "0x4bba26f5d256f972384413be51b4bfb5fd6c21d192d5fb55880d7baadc682631", + "PivotTotalDifficulty": "39936580" }, "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 d6ab25b20670..b638cd8b5ca9 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": 13480000, - "PivotHash": "0x8da352578c2ae41884ea6038557a995bd9e68ceb348ea1d6d63baf61210c1137", - "PivotTotalDifficulty": "22696204" + "PivotNumber": 15900000, + "PivotHash": "0xc545fbce23bbd9e6d756210003ef4b3c5021f84763b82cb33ba3f739cbe796fc", + "PivotTotalDifficulty": "26391193" }, "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 91a4a83e6fde..f461ff99a533 100644 --- a/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json @@ -17,9 +17,9 @@ }, "Sync": { "SnapSync": true, - "PivotNumber": 23870000, - "PivotHash": "0x86831e1abd1a1ce41632622cc0ef9f138fbdea36565f2b5edfeaae30074ef190", - "PivotTotalDifficulty": "47740001", + "PivotNumber": 28870000, + "PivotHash": "0x9d00c48eaf9e84f48936baeea8aba0866422524641546af30c9bb4afa256c367", + "PivotTotalDifficulty": "49575263", "HeaderStateDistance": 6 }, "JsonRpc": { diff --git a/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json index 8dba21318497..eb127802e2d4 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": 18740000, - "PivotHash": "0x5a7078f32b191d45994522667182abaaf8d934d0f4eb44773c70a04d9fe2d1a5", + "PivotNumber": 24850000, + "PivotHash": "0xd5816d5b0b0024581511a44f029b9df64940b3d20ebff9cd8b2790fab67e4950", "PivotTotalDifficulty": "37331807", "HeaderStateDistance": 6 }, diff --git a/src/Nethermind/Nethermind.Runner/configs/lisk-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/lisk-mainnet.json index 3ea18b58b827..328cff8f05af 100644 --- a/src/Nethermind/Nethermind.Runner/configs/lisk-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/lisk-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://rpc.api.lisk.com" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/lisk-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/lisk-sepolia.json index a6e20173132f..d21a5722f34f 100644 --- a/src/Nethermind/Nethermind.Runner/configs/lisk-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/lisk-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://rpc.sepolia-api.lisk.com" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/lyra-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/lyra-mainnet.json index 140da498c3c2..f78b7f1ad24e 100644 --- a/src/Nethermind/Nethermind.Runner/configs/lyra-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/lyra-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://rpc.lyra.finance" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/mainnet.json b/src/Nethermind/Nethermind.Runner/configs/mainnet.json index cc192fe7fe52..9063a4642f8f 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": 23456000, - "PivotHash": "0x6bcae00d5fbb7f52b3cdd0296382b48936f53617d3ff72db97ff76883b3448f4", + "PivotNumber": 24457000, + "PivotHash": "0x1b315b4d85f8c804569022f1e0a021def8c2029ac4b55edeea03e2cf7c1f936e", "PivotTotalDifficulty": "58750003716598352816469", - "FastSyncCatchUpHeightDelta": "10000000000", + "FastSyncCatchUpHeightDelta": 10000000000, "AncientReceiptsBarrier": 15537394, "AncientBodiesBarrier": 15537394 }, @@ -24,7 +24,7 @@ "NodeName": "Mainnet" }, "Blocks": { - "TargetBlockGasLimit": 45000000 + "TargetBlockGasLimit": 60000000 }, "JsonRpc": { "Enabled": true, diff --git a/src/Nethermind/Nethermind.Runner/configs/mainnet_archive.json b/src/Nethermind/Nethermind.Runner/configs/mainnet_archive.json index baf1328e0b4d..8df9f147d9ac 100644 --- a/src/Nethermind/Nethermind.Runner/configs/mainnet_archive.json +++ b/src/Nethermind/Nethermind.Runner/configs/mainnet_archive.json @@ -18,7 +18,7 @@ "NodeName": "Mainnet Archive" }, "Blocks": { - "TargetBlockGasLimit": 45000000 + "TargetBlockGasLimit": 60000000 }, "Receipt": { "TxLookupLimit": 0 diff --git a/src/Nethermind/Nethermind.Runner/configs/metal-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/metal-mainnet.json index 204251607134..7c217972a0f0 100644 --- a/src/Nethermind/Nethermind.Runner/configs/metal-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/metal-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://rpc.metall2.com" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/metal-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/metal-sepolia.json index 853157afbfe0..f1c84bb655f9 100644 --- a/src/Nethermind/Nethermind.Runner/configs/metal-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/metal-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://testnet.rpc.metall2.com" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/mint-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/mint-mainnet.json index d5dfb292bd8e..21d524f4dafa 100644 --- a/src/Nethermind/Nethermind.Runner/configs/mint-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/mint-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://rpc.mintchain.io" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/mode-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/mode-mainnet.json index 1133bd83fcf5..838b1b051c4d 100644 --- a/src/Nethermind/Nethermind.Runner/configs/mode-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/mode-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://mainnet-sequencer.mode.network" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/mode-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/mode-sepolia.json index 5db9fc547200..08474c9cc0fd 100644 --- a/src/Nethermind/Nethermind.Runner/configs/mode-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/mode-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://sepolia.mode.network" } -} +} \ 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 a2ec5fa30545..8e20ed622711 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": 141700000, - "PivotHash": "0x05cc432a277d3d0c04d934c51f44a540d474e1f50f0546ec3964dd07f48fbdfb" + "PivotNumber": 147750000, + "PivotHash": "0xc5ca711298ccd110764eff891270897df703ba081239562286b263fbc046755e" }, "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 1fbe0504d59e..6d79b23d20ef 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": 33600000, - "PivotHash": "0xa5fe3044774eeb3bb6de7de91caf749905dd3a9ed985a5d8b94612e4d9cff8be" + "PivotNumber": 39640000, + "PivotHash": "0x60392267e63a958936e102312fb81a90f023c23b372279e1b25112bdf92ea42c" }, "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/orderly-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/orderly-mainnet.json index 8cdaa7427624..52c3b27c3caa 100644 --- a/src/Nethermind/Nethermind.Runner/configs/orderly-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/orderly-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://rpc.orderly.network" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/ozean-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/ozean-sepolia.json index e476cf69660c..a38c80ae97e9 100644 --- a/src/Nethermind/Nethermind.Runner/configs/ozean-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/ozean-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://ozean-testnet.rpc.caldera.xyz/http" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/pivotal-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/pivotal-sepolia.json index d4bfd6425a3e..8dda3a43954c 100644 --- a/src/Nethermind/Nethermind.Runner/configs/pivotal-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/pivotal-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://sepolia.pivotalprotocol.com/" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/poacore_validator.json b/src/Nethermind/Nethermind.Runner/configs/poacore_validator.json index dd854c73d6a1..84af9ca90f43 100644 --- a/src/Nethermind/Nethermind.Runner/configs/poacore_validator.json +++ b/src/Nethermind/Nethermind.Runner/configs/poacore_validator.json @@ -1,8 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/NethermindEth/core-scripts/refs/heads/main/schemas/config.json", "Init": { - "StoreReceipts": false, - "IsMining": true, "ChainSpecPath": "chainspec/poacore.json", "GenesisHash": "0x39f02c003dde5b073b3f6e1700fc0b84b4877f6839bb23edadd3d2d82a488634", "BaseDbPath": "nethermind_db/poacore", @@ -32,5 +30,13 @@ }, "Merge": { "Enabled": false + }, + "Mining": + { + "Enabled": true + }, + "Receipt": + { + "StoreReceipts": false } } diff --git a/src/Nethermind/Nethermind.Runner/configs/polynomial-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/polynomial-mainnet.json index 9a357c8a0f19..3d90623e18f2 100644 --- a/src/Nethermind/Nethermind.Runner/configs/polynomial-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/polynomial-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://rpc.polynomial.fi" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/race-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/race-mainnet.json index 4f347426b253..7daa781170f3 100644 --- a/src/Nethermind/Nethermind.Runner/configs/race-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/race-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://racemainnet.io" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/race-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/race-sepolia.json index 36b0ec38e0b7..26f827d313cc 100644 --- a/src/Nethermind/Nethermind.Runner/configs/race-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/race-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://racetestnet.io" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/redstone-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/redstone-mainnet.json index 9a2dad49a284..c567b0e61271 100644 --- a/src/Nethermind/Nethermind.Runner/configs/redstone-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/redstone-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://rpc.redstonechain.com" } -} +} \ 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 239ac4fe77b4..00765e81652d 100644 --- a/src/Nethermind/Nethermind.Runner/configs/sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/sepolia.json @@ -5,7 +5,6 @@ "GenesisHash": "0x25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9", "BaseDbPath": "nethermind_db/sepolia", "LogFileName": "sepolia.log", - "StaticNodesPath": "Data/static-nodes-sepolia.json", "MemoryHint": 1024000000 }, "TxPool": { @@ -18,8 +17,8 @@ "FastSync": true, "SnapSync": true, "UseGethLimitsInFastBlocks": true, - "PivotNumber": 9293000, - "PivotHash": "0x3d0f3b35a13d60cf1159f017e7c5680f507c963b1994192863b4e507f9f0256b", + "PivotNumber": 10261000, + "PivotHash": "0x4afc23c05b485ec4a9cdf17eb9d53e847d24d4ad580287a3db80ac5b06d5f241", "PivotTotalDifficulty": "17000018015853232", "FastSyncCatchUpHeightDelta": 10000000000, "AncientReceiptsBarrier": 1450409, @@ -39,5 +38,8 @@ }, "Merge": { "Enabled": true + }, + "Discovery": { + "DiscoveryVersion": "All" } } \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/sepolia_archive.json b/src/Nethermind/Nethermind.Runner/configs/sepolia_archive.json index dc9fc01936e4..c39b2ba8fce3 100644 --- a/src/Nethermind/Nethermind.Runner/configs/sepolia_archive.json +++ b/src/Nethermind/Nethermind.Runner/configs/sepolia_archive.json @@ -5,7 +5,6 @@ "GenesisHash": "0x25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9", "BaseDbPath": "nethermind_db/sepolia_archive", "LogFileName": "sepolia.log", - "StaticNodesPath": "Data/static-nodes-sepolia.json", "MemoryHint": 1024000000 }, "TxPool": { diff --git a/src/Nethermind/Nethermind.Runner/configs/settlus-mainnet-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/settlus-mainnet-mainnet.json index a886855ec6b4..1e8a3d93b862 100644 --- a/src/Nethermind/Nethermind.Runner/configs/settlus-mainnet-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/settlus-mainnet-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://settlus-mainnet-sequencer.g.alchemy.com/" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/settlus-sepolia-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/settlus-sepolia-sepolia.json index 9a7a52cc8749..e56a98d27203 100644 --- a/src/Nethermind/Nethermind.Runner/configs/settlus-sepolia-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/settlus-sepolia-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://settlus-sep-testnet-sequencer.g.alchemy.com/" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/shape-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/shape-mainnet.json index 9c13539eef1a..a6b15850740f 100644 --- a/src/Nethermind/Nethermind.Runner/configs/shape-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/shape-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://shape-mainnet-sequencer.g.alchemy.com" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/shape-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/shape-sepolia.json index 97a79ca02ba5..8cded2c56e90 100644 --- a/src/Nethermind/Nethermind.Runner/configs/shape-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/shape-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://shape-sepolia-sequencer.g.alchemy.com" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/soneium-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/soneium-mainnet.json index b039acb0b284..ae9a769fb319 100644 --- a/src/Nethermind/Nethermind.Runner/configs/soneium-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/soneium-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://rpc.soneium.org" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/soneium-minato-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/soneium-minato-sepolia.json index aa7388a217ec..5768e1879a67 100644 --- a/src/Nethermind/Nethermind.Runner/configs/soneium-minato-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/soneium-minato-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://rpc.minato.soneium.org" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/spaceneth.json b/src/Nethermind/Nethermind.Runner/configs/spaceneth.json index 94333218fbc0..a9d0deccc1fe 100644 --- a/src/Nethermind/Nethermind.Runner/configs/spaceneth.json +++ b/src/Nethermind/Nethermind.Runner/configs/spaceneth.json @@ -5,7 +5,6 @@ "KeepDevWalletInMemory": true, "DiscoveryEnabled": false, "PeerManagerEnabled": false, - "IsMining": true, "ChainSpecPath": "chainspec/spaceneth.json", "BaseDbPath": "spaceneth_db", "LogFileName": "spaceneth.log", @@ -47,7 +46,6 @@ "Subscribe", "Trace", "TxPool", - "Vault", "Web3" ] }, @@ -56,5 +54,9 @@ }, "Merge": { "Enabled": false + }, + "Mining": + { + "Enabled": true } } diff --git a/src/Nethermind/Nethermind.Runner/configs/spaceneth_persistent.json b/src/Nethermind/Nethermind.Runner/configs/spaceneth_persistent.json index 2232c6cc1d3d..efbc825140ea 100644 --- a/src/Nethermind/Nethermind.Runner/configs/spaceneth_persistent.json +++ b/src/Nethermind/Nethermind.Runner/configs/spaceneth_persistent.json @@ -5,7 +5,6 @@ "KeepDevWalletInMemory": true, "DiscoveryEnabled": false, "PeerManagerEnabled": false, - "IsMining": true, "ChainSpecPath": "chainspec/spaceneth.json", "BaseDbPath": "spaceneth_db", "LogFileName": "spaceneth.log", @@ -42,7 +41,6 @@ "Subscribe", "Trace", "TxPool", - "Vault", "Web3" ] }, @@ -52,5 +50,9 @@ }, "Metrics": { "NodeName": "Spaceneth" + }, + "Mining": + { + "Enabled": true } } diff --git a/src/Nethermind/Nethermind.Runner/configs/sseed-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/sseed-mainnet.json index 7e9cd051f6b4..128a3cdb0c33 100644 --- a/src/Nethermind/Nethermind.Runner/configs/sseed-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/sseed-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://mainnet.superseed.xyz" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/surge-hoodi.json b/src/Nethermind/Nethermind.Runner/configs/surge-hoodi.json index bf5fe7ae33bd..c05e1cafcdf3 100644 --- a/src/Nethermind/Nethermind.Runner/configs/surge-hoodi.json +++ b/src/Nethermind/Nethermind.Runner/configs/surge-hoodi.json @@ -2,7 +2,7 @@ "$schema": "https://raw.githubusercontent.com/NethermindEth/core-scripts/refs/heads/main/schemas/config.json", "Init": { "ChainSpecPath": "chainspec/surge-hoodi.json", - "GenesisHash": "0xf4ca134e6355f2225d89119e9ddb917910b4fad86cf8c9d8640b8ddab292c0a1", + "GenesisHash": "0xf2a617aaf5d55cc0032081d9c67b6a03b785d063ab48f5d69b5f6013930bb200", "BaseDbPath": "nethermind_db/surge-hoodi", "LogFileName": "surge-hoodi.log" }, @@ -13,7 +13,14 @@ "Sync": { "FastSync": true, "SnapSync": true, - "FastSyncCatchUpHeightDelta": "10000000000" + "FastSyncCatchUpHeightDelta": 10000000000 + }, + "Pruning": { + "PruningBoundary": 1000 + }, + "Surge": { + "L1EthApiEndpoint": "https://rpc.hoodi.ethpandaops.io/", + "TaikoInboxAddress": "0x624ec8F33DA83707f360D6d25136AA0741713BC4" }, "Metrics": { "NodeName": "Surge Hoodi" diff --git a/src/Nethermind/Nethermind.Runner/configs/swan-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/swan-mainnet.json index 96a53ffd5741..435532390082 100644 --- a/src/Nethermind/Nethermind.Runner/configs/swan-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/swan-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://sequencer-mainnet.swanchain.org" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/swell-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/swell-mainnet.json index f2cdec3a4e63..86b70258293c 100644 --- a/src/Nethermind/Nethermind.Runner/configs/swell-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/swell-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://swell-mainnet.alt.technology" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/taiko-hekla.json b/src/Nethermind/Nethermind.Runner/configs/taiko-hoodi.json similarity index 61% rename from src/Nethermind/Nethermind.Runner/configs/taiko-hekla.json rename to src/Nethermind/Nethermind.Runner/configs/taiko-hoodi.json index c8b7c4fc93b4..30f9cbc8e31b 100644 --- a/src/Nethermind/Nethermind.Runner/configs/taiko-hekla.json +++ b/src/Nethermind/Nethermind.Runner/configs/taiko-hoodi.json @@ -1,10 +1,10 @@ { "$schema": "https://raw.githubusercontent.com/NethermindEth/core-scripts/refs/heads/main/schemas/config.json", "Init": { - "ChainSpecPath": "chainspec/taiko-hekla.json", - "GenesisHash": "0x1f5554042aa50dc0712936ae234d8803b80b84251f85d074756a2f391896e109", - "BaseDbPath": "nethermind_db/taiko-hekla", - "LogFileName": "taiko-hekla.log" + "ChainSpecPath": "chainspec/taiko-hoodi.json", + "GenesisHash": "0x8e3d16acf3ecc1fbe80309b04e010b90c9ccb3da14e98536cfe66bb93407d228", + "BaseDbPath": "nethermind_db/taiko-hoodi", + "LogFileName": "taiko-hoodi.log" }, "TxPool": { "BlobsSupport": "Disabled" diff --git a/src/Nethermind/Nethermind.Runner/configs/tbn-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/tbn-mainnet.json index a874930cadec..c7c288913631 100644 --- a/src/Nethermind/Nethermind.Runner/configs/tbn-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/tbn-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://sequencer.bnry.mainnet.zeeve.net" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/tbn-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/tbn-sepolia.json index 8bda1839a754..22462abfc6fa 100644 --- a/src/Nethermind/Nethermind.Runner/configs/tbn-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/tbn-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://sequencer.rpc.bnry.testnet.zeeve.net" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/unichain-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/unichain-mainnet.json index a0dd88fd3c27..9239c6afe4fb 100644 --- a/src/Nethermind/Nethermind.Runner/configs/unichain-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/unichain-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://mainnet-sequencer.unichain.org" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/unichain-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/unichain-sepolia.json index b33ccd1de63d..8f7513678525 100644 --- a/src/Nethermind/Nethermind.Runner/configs/unichain-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/unichain-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://sepolia-sequencer.unichain.org" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/volta.json b/src/Nethermind/Nethermind.Runner/configs/volta.json index 0c1f4d979d16..e2b950f84f26 100644 --- a/src/Nethermind/Nethermind.Runner/configs/volta.json +++ b/src/Nethermind/Nethermind.Runner/configs/volta.json @@ -1,7 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/NethermindEth/core-scripts/refs/heads/main/schemas/config.json", "Init": { - "IsMining": false, "ChainSpecPath": "chainspec/volta.json", "GenesisHash": "0xebd8b413ca7b7f84a8dd20d17519ce2b01954c74d94a0a739a3e416abe0e43e5", "BaseDbPath": "nethermind_db/volta", @@ -29,13 +28,10 @@ "Metrics": { "NodeName": "Volta" }, - "Mining": { - "MinGasPrice": 1 - }, "Blocks": { "GasToken": "VT" }, "Merge": { "Enabled": false } -} \ No newline at end of file +} diff --git a/src/Nethermind/Nethermind.Runner/configs/volta_archive.json b/src/Nethermind/Nethermind.Runner/configs/volta_archive.json index ccc549916b85..163ca3b253b3 100644 --- a/src/Nethermind/Nethermind.Runner/configs/volta_archive.json +++ b/src/Nethermind/Nethermind.Runner/configs/volta_archive.json @@ -22,9 +22,6 @@ "Receipt": { "TxLookupLimit": 0 }, - "Mining": { - "MinGasPrice": 1 - }, "Pruning": { "Mode": "None" }, diff --git a/src/Nethermind/Nethermind.Runner/configs/worldchain-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/worldchain-mainnet.json index 3c6b8e7aa378..ac4884475eee 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": 19830000, - "PivotHash": "0x7478479974223df5e6b129fd7a0488c683c9f3749c357defb0e830268967de2f" + "PivotNumber": 25880000, + "PivotHash": "0x8fa42f29309ccc1455c2d5af57f22a9941dcb13d7db23cc49508977bfb922bb6" }, "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 a84204036f9f..e2efd7ba62d3 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": 19220000, - "PivotHash": "0x200da772945e5245072bf995f3a07d97b442eae41cbd89f8074d1c79ac3cb313" + "PivotNumber": 25270000, + "PivotHash": "0x4e5f1ecdcbc3931cc6dc40d3c5db56c00d08dac306caf26ccee31d7b6d0dadaf" }, "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/configs/xterio-eth-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/xterio-eth-mainnet.json index 886e2218270c..7e2e6907cfbb 100644 --- a/src/Nethermind/Nethermind.Runner/configs/xterio-eth-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/xterio-eth-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://xterio-eth.alt.technology/" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/zora-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/zora-mainnet.json index 76989c8925d9..0fc8a6d876ce 100644 --- a/src/Nethermind/Nethermind.Runner/configs/zora-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/zora-mainnet.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://rpc.zora.energy" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/zora-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/zora-sepolia.json index 7f2b4eee5f41..31eb7c421282 100644 --- a/src/Nethermind/Nethermind.Runner/configs/zora-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/zora-sepolia.json @@ -34,4 +34,4 @@ "Optimism": { "SequencerUrl": "https://sepolia.rpc.zora.energy" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/packages.lock.json b/src/Nethermind/Nethermind.Runner/packages.lock.json new file mode 100644 index 000000000000..4c14de3e87d7 --- /dev/null +++ b/src/Nethermind/Nethermind.Runner/packages.lock.json @@ -0,0 +1,2101 @@ +{ + "version": 2, + "dependencies": { + "net10.0": { + "AspNetCore.HealthChecks.UI.Client": { + "type": "Direct", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "1Ub3Wvvbz7CMuFNWgLEc9qqQibiMoovDML/WHrwr5J83RPgtI20giCR92s/ipLgu7IIuqw+W/y7WpIeHqAICxg==", + "dependencies": { + "AspNetCore.HealthChecks.UI.Core": "9.0.0" + } + }, + "Microsoft.Build.Tasks.Git": { + "type": "Direct", + "requested": "[10.0.103, )", + "resolved": "10.0.103", + "contentHash": "QoiCMcPuxC6eqRQmrmF9zBY96ejIznXtve/lJJbonGD9I5Aygf2AUCOWslGiCEtBbfWRSuUnepBjuuVOdAl5ag==" + }, + "Microsoft.Extensions.FileProviders.Embedded": { + "type": "Direct", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "kw/xPl7m4Gv6bqx2ojihTtWiN2K2AklyMIrvncuSi2MOdwu0oMKoyh0G3p2Brt7m43Q9ER0IaA2G4EGjfgDh/w==" + }, + "Microsoft.VisualStudio.Azure.Containers.Tools.Targets": { + "type": "Direct", + "requested": "[1.23.0, )", + "resolved": "1.23.0", + "contentHash": "2wDnb4umupJZ/1ikgWozFVpggH1mlHQFc0odXVv2ZagL3RYwXgW9zmC15fiqIBzmaC0vLZUnLGwDY+p8ZR7Syw==" + }, + "NLog.Targets.Seq": { + "type": "Direct", + "requested": "[4.0.2, )", + "resolved": "4.0.2", + "contentHash": "6p4QWHiRvkObfZok3wrNRPlgJ3Kp1r+Pv3BadK++Zdj+o+/LD86Gq0No1PMNEEhJUnvpSJK8BaE2uDa2k8DhbQ==", + "dependencies": { + "NLog": "5.2.5" + } + }, + "Pyroscope": { + "type": "Direct", + "requested": "[0.14.1, )", + "resolved": "0.14.1", + "contentHash": "i8BoY82ZBrBOmgal7Zbf69CzQ2tRJkV0v7se1yB54rsOuuxFIzcUMplyD0VIg6Gl+EkVORNpm5OUL5tJGhR+lg==" + }, + "System.CommandLine": { + "type": "Direct", + "requested": "[2.0.3, )", + "resolved": "2.0.3", + "contentHash": "5nY9hlrGGFEmyecNUux58sohD2Q16U6jlFBYwH57b2IVUs+u7LfMFaHwOtw2tuIi8CUl6jKXw5s4FuIt9aLtug==" + }, + "AspNetCore.HealthChecks.UI.Core": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "TVriy4hgYnhfqz6NAzv8qe62Q8wf82iKUL6WV9selqeFZTq1ILi39Sic6sFQegRysvAVcnxKP/vY8z9Fk8x6XQ==" + }, + "AspNetCore.HealthChecks.UI.Data": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "bZkXUdiqXOkmhe3XHF+OIgPQKR8OXS8/+iDWegmxSXtbaIYwsT5+B0URT+P4OPvhj5tR2goMMZe5KwJBVHr+1g==", + "dependencies": { + "AspNetCore.HealthChecks.UI.Core": "9.0.0", + "Microsoft.EntityFrameworkCore": "8.0.0" + } + }, + "BinaryEncoding": { + "type": "Transitive", + "resolved": "1.4.0", + "contentHash": "1cnkP90c+zNcRyabjKSA3VYJvpYfkGEpXeekfF8KdTFo3VyUUFOioAsANbG8nsMyedGcmUOqHWd1d3fOXke4VA==", + "dependencies": { + "NETStandard.Library": "1.6.1" + } + }, + "Common.Logging": { + "type": "Transitive", + "resolved": "3.4.1", + "contentHash": "5eZ/vgEOqzLg4PypZqnJ+wMhhgHyckicbZY4iDxqQ4FtOz0CpdYZ0xQ78aszMzeAJZiLLb5VdR9tPfunVQLz6g==", + "dependencies": { + "Common.Logging.Core": "3.4.1" + } + }, + "Common.Logging.Core": { + "type": "Transitive", + "resolved": "3.4.1", + "contentHash": "wLHldZHvxsSD6Ahonfj00/SkfHfKqO+YT6jsUwVm8Rch1REL9IArHAcSLXxYxYfu5/4ydGtmXvOtaH3AkVPu0A==" + }, + "FastEnum.Core": { + "type": "Transitive", + "resolved": "2.0.6", + "contentHash": "qtumBzs1baLfSvXTqAJgbPD2WIp4Om7d1NfVYY0XEGXOOkL+0wTf/J6WrY2jYcaLyHU8snM5KNm0oWoNjZa/JA==" + }, + "FastEnum.Generators": { + "type": "Transitive", + "resolved": "2.0.6", + "contentHash": "JF/zidWZSftVTG/1N4ndc6qgh2QarBcGAc5nZFhrb0/jFIo6yf1YyLSXB56vpuReg1zy8KHASfkMvWKztt1XXA==" + }, + "Fractions": { + "type": "Transitive", + "resolved": "7.3.0", + "contentHash": "2bETFWLBc8b7Ut2SVi+bxhGVwiSpknHYGBh2PADyGWONLkTxT7bKyDRhF8ao+XUv90tq8Fl7GTPxSI5bacIRJw==" + }, + "FSharp.Core": { + "type": "Transitive", + "resolved": "6.0.2", + "contentHash": "8GZqv6buY71KQlWT+cl2eMi+aNX9xQ61RgI3Pzv9zPxPOX6tWCLRrBj0MYQ3h871r2RhiHUl7f0AXUD/POr8eA==" + }, + "Grpc.Core": { + "type": "Transitive", + "resolved": "2.46.6", + "contentHash": "ZoRg3KmOJ2urTF4+u3H0b1Yv10xzz2Y/flFWS2tnRmj8dbKLeiJaSRqu4LOBD3ova90evqLkVZ85kUkC4JT4lw==", + "dependencies": { + "Grpc.Core.Api": "2.46.6" + } + }, + "Grpc.Core.Api": { + "type": "Transitive", + "resolved": "2.46.6", + "contentHash": "Z7HJGqJYyKb53qfp1jf0wRDYs3sxOnkAFxXAW6q52LLmX/zxzjtFLI9eaWO5UC0weiWjn4iT1FzR+tj9qYZAMg==" + }, + "Humanizer.Core": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==" + }, + "IPNetwork2": { + "type": "Transitive", + "resolved": "2.1.2", + "contentHash": "DZF2SbtyqukkLSzyu0KqcXUBdhDXQ/K3QWM27vlvdPo3W+iI81pgUCNTwyynNyVc5Q9AIn0znFVVhHGbhiofUg==", + "dependencies": { + "NETStandard.Library": "1.6.1" + } + }, + "Keccak256": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "duyRtj4I3+yZZZC7Ma5S/cxzWn5CLPRcXeXtmBcLS3TpjwLm74afQEGzfYEWma8H/dbpUiHl2ozYszKuQ8QpEg==" + }, + "libsodium": { + "type": "Transitive", + "resolved": "1.0.20", + "contentHash": "fMO6HpAbvLagobzBH6eU36riWF01lCAweX34D5eugqjuXA+WS5MnV1ngE+2Sw3LvGvxZlmyLp9416t57dMZ5og==" + }, + "Makaretu.Dns": { + "type": "Transitive", + "resolved": "2.0.1", + "contentHash": "qe/5w5Q/i2Xz2ZnCq3E49kLAtVFRuuBn40C1mQPWbLDwlHM0KVDzCkFqwxDedv7JgQis66Z9AZKyo1zCTRrdYA==", + "dependencies": { + "SimpleBase": "1.3.1" + } + }, + "Makaretu.Dns.Multicast": { + "type": "Transitive", + "resolved": "0.27.0", + "contentHash": "MTMq3vca9yw8sMW0knox/lZcAkDghmH6FVLTRInJZWbuYE99QI/9k9EujsXusWQ8oDySXb/gX92bfZipKehQgw==", + "dependencies": { + "Common.Logging": "3.4.1", + "IPNetwork2": "2.1.2", + "Makaretu.Dns": "2.0.1", + "Tmds.LibC": "0.2.0" + } + }, + "MathNet.Numerics": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "pg1W2VwaEQMAiTpGK840hZgzavnqjlCMTVSbtVCXVyT+7AX4mc1o89SPv4TBlAjhgCOo9c1Y+jZ5m3ti2YgGgA==" + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==" + }, + "Microsoft.ClearScript.Core": { + "type": "Transitive", + "resolved": "7.5.0", + "contentHash": "7BfzQZA7LdgpfJGSy/GBzKuURb32UpJGQObH5WAavkUxS/u9h/KaEl8N4812F6f4UWVrtwI5XdRgLMu6ukt38A==" + }, + "Microsoft.ClearScript.V8.ICUData": { + "type": "Transitive", + "resolved": "7.5.0", + "contentHash": "brX7rIjvZPt/ZDSZOPf36ULxhlsFcEgg2WLeOXCFkexTH7MWgUGy//6vMry/QvTLhSgDrs5z+8SbEU1krnuxRg==" + }, + "Microsoft.CodeAnalysis.Common": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "lwAbIZNdnY0SUNoDmZHkVUwLO8UyNnyyh1t/4XsbFxi4Ounb3xszIYZaWhyj5ZjyfcwqwmtMbE7fUTVCqQEIdQ==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.3.3" + } + }, + "Microsoft.CodeAnalysis.CSharp.Workspaces": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "h74wTpmGOp4yS4hj+EvNzEiPgg/KVs2wmSfTZ81upJZOtPkJsVkgfsgtxxqmAeapjT/vLKfmYV0bS8n5MNVP+g==", + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.CodeAnalysis.CSharp": "[4.5.0]", + "Microsoft.CodeAnalysis.Common": "[4.5.0]", + "Microsoft.CodeAnalysis.Workspaces.Common": "[4.5.0]" + } + }, + "Microsoft.CodeAnalysis.Workspaces.Common": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "l4dDRmGELXG72XZaonnOeORyD/T5RpEu5LGHOUIhnv+MmUWDY/m1kWXGwtcgQ5CJ5ynkFiRnIYzTKXYjUs7rbw==", + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.Bcl.AsyncInterfaces": "6.0.0", + "Microsoft.CodeAnalysis.Common": "[4.5.0]", + "System.Composition": "6.0.0" + } + }, + "Microsoft.EntityFrameworkCore": { + "type": "Transitive", + "resolved": "8.0.11", + "contentHash": "stbjWBTtpQ1HtqXMFyKnXFTr76PvaOHI2b2h85JqBi3eZr00nspvR/a90Zwh8CQ4rVawqLiTG0+0yZQWaav+sQ==", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "8.0.11", + "Microsoft.EntityFrameworkCore.Analyzers": "8.0.11" + } + }, + "Microsoft.EntityFrameworkCore.Abstractions": { + "type": "Transitive", + "resolved": "8.0.11", + "contentHash": "++zY0Ea724ku1jptWJmF7jm3I4IXTexfT4qi1ETcSFFF7qj+qm6rRgN7mTuKkwIETuXk0ikfzudryRjUGrrNKQ==" + }, + "Microsoft.EntityFrameworkCore.Analyzers": { + "type": "Transitive", + "resolved": "8.0.11", + "contentHash": "NI/AJQjtC7qgWM8Nr85sRkwlog2AnFer5RKP8xTUH0RuPF3nN0tGXBEeYJOLZWp+/+M/C6O7MMDRhKRE8bZwIA==" + }, + "Microsoft.EntityFrameworkCore.Design": { + "type": "Transitive", + "resolved": "8.0.11", + "contentHash": "KxOvpbaKiUmbLvenr0T/4F1Vdm0Sq+iajLbesQK7/WKB/Dx+FQHCZ0f5jCXrVWK2QKF9eHzQ5JPA1L6hcb25FQ==", + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.CodeAnalysis.CSharp.Workspaces": "4.5.0", + "Microsoft.EntityFrameworkCore.Relational": "8.0.11", + "Microsoft.Extensions.DependencyModel": "8.0.2", + "Mono.TextTemplating": "2.2.1" + } + }, + "Microsoft.EntityFrameworkCore.InMemory": { + "type": "Transitive", + "resolved": "8.0.11", + "contentHash": "gFFdubZlRfudSw94OxO7s8OvLQZncnR0S1ll//wRtZP3mMYVeq8DrMnEILK6hBJwbTBB1SNm3apg9SdvdE/ySg==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "8.0.11" + } + }, + "Microsoft.EntityFrameworkCore.Relational": { + "type": "Transitive", + "resolved": "8.0.11", + "contentHash": "3TuuW3i5I4Ro0yoaHmi2MqEDGObOVuhLaMEnd/heaLB1fcvm4fu4PevmC4BOWnI0vo176AIlV5o4rEQciLoohw==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "8.0.11" + } + }, + "Microsoft.Extensions.DependencyModel": { + "type": "Transitive", + "resolved": "8.0.2", + "contentHash": "mUBDZZRgZrSyFOsJ2qJJ9fXfqd/kXJwf3AiDoqLD9m6TjY5OO/vLNOb9fb4juC0487eq4hcGN/M2Rh/CKS7QYw==" + }, + "Microsoft.IdentityModel.Abstractions": { + "type": "Transitive", + "resolved": "8.15.0", + "contentHash": "e/DApa1GfxUqHSBHcpiQg8yaghKAvFVBQFcWh25jNoRobDZbduTUACY8bZ54eeGWXvimGmEDdF0zkS5Dq16XPQ==" + }, + "Microsoft.IdentityModel.Logging": { + "type": "Transitive", + "resolved": "8.15.0", + "contentHash": "1gJLjhy0LV2RQMJ9NGzi5Tnb2l+c37o8D8Lrk2mrvmb6OQHZ7XJstd/XxvncXgBpad4x9CGXdipbZzJJCXKyAg==", + "dependencies": { + "Microsoft.IdentityModel.Abstractions": "8.15.0" + } + }, + "Microsoft.IdentityModel.Tokens": { + "type": "Transitive", + "resolved": "8.15.0", + "contentHash": "zUE9ysJXBtXlHHRtcRK3Sp8NzdCI1z/BRDTXJQ2TvBoI0ENRtnufYIep0O5TSCJRJGDwwuLTUx+l/bEYZUxpCA==", + "dependencies": { + "Microsoft.IdentityModel.Logging": "8.15.0" + } + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + }, + "Mono.TextTemplating": { + "type": "Transitive", + "resolved": "2.2.1", + "contentHash": "KZYeKBET/2Z0gY1WlTAK7+RHTl7GSbtvTLDXEZZojUdAPqpQNDL6tHv7VUpqfX5VEOh+uRGKaZXkuD253nEOBQ==", + "dependencies": { + "System.CodeDom": "4.4.0" + } + }, + "Multiformats.Base": { + "type": "Transitive", + "resolved": "2.0.2", + "contentHash": "uMUDZLjkdI7zrkRFCC7tPV//1y9NnFNQnvyrzoLrn9lPNvSGQhHoA5BEBxO58S5Ow3R580UP8W6mfWDKtIuSYQ==" + }, + "Multiformats.Hash": { + "type": "Transitive", + "resolved": "1.5.0", + "contentHash": "f9HstrBNHUWs0WFhYH7H4H3VatzTVop+XWp0QDFW7f9JzeIj2fnz21P0IrgwR8H6wl1ujAEh+5yf30XlqRDcaQ==", + "dependencies": { + "BinaryEncoding": "1.4.0", + "Multiformats.Base": "2.0.1", + "Portable.BouncyCastle": "1.8.5", + "System.Composition": "1.2.0", + "murmurhash": "1.0.2" + } + }, + "murmurhash": { + "type": "Transitive", + "resolved": "1.0.2", + "contentHash": "Yw9+sYL3qdTEXDKAEeiXsVwsP2K2nyWOxgvbDD1w5j+yu0CYk5edLvGmmJHqqFxuBFrVsgb7iF2XGprRlt+SEA==" + }, + "NBitcoin.Secp256k1": { + "type": "Transitive", + "resolved": "3.1.5", + "contentHash": "HGOj4qoTGdHQ6lYjGOmYrxMgbTyyXonunPq+btFalAedumQ2tJxykiMlygEGNnEUoOXCAIV4fvbCdCthFw3LOQ==" + }, + "Nethermind.DotNetty.Codecs": { + "type": "Transitive", + "resolved": "1.0.2.76", + "contentHash": "8K50pknD0si+zyO8tOsYMlGvEmtwu7KEU6AYbEoj9wFflenBsPHbI4o22LBU66AKoTFYyJwXllZSvlMZxuqV/g==", + "dependencies": { + "Nethermind.DotNetty.Buffers": "1.0.2.76", + "Nethermind.DotNetty.Common": "1.0.2.76", + "Nethermind.DotNetty.Transport": "1.0.2.76" + } + }, + "Nethermind.DotNetty.Common": { + "type": "Transitive", + "resolved": "1.0.2.76", + "contentHash": "rraxAOK8Pww3ReW2NkCCr/pwXTp88gI4lXaeA5TriPnp1wZg8jJdZYIj2m2+HKkVtw1C1F1sRA7FzfgBodA3Tw==" + }, + "Nethermind.Libp2p.Core": { + "type": "Transitive", + "resolved": "1.0.0-preview.45", + "contentHash": "jk63RB4s0LwGnAJKRLRM0lN2wFlNgMk92wJcLOfEXyCLnLuC/6JocELz0fmao3WQbJIFC895x3GYvvkDsUS4nw==", + "dependencies": { + "BouncyCastle.Cryptography": "2.4.0", + "Google.Protobuf": "3.28.3", + "Nethermind.Multiformats.Address": "1.1.8", + "SimpleBase": "4.0.2" + } + }, + "Nethermind.Libp2p.Protocols.Identify": { + "type": "Transitive", + "resolved": "1.0.0-preview.45", + "contentHash": "8hEzwS9eYYy1xvmXq+6VcfKcOflEmnPdT5UA8Lhr0cdK2af+wV6tMwHhuvqF0L7N+bdaFMNcUUhBhy17jafutw==", + "dependencies": { + "Google.Protobuf": "3.28.3", + "Nethermind.Libp2p.Core": "1.0.0-preview.45", + "Nethermind.Libp2p.Protocols.IpTcp": "1.0.0-preview.45" + } + }, + "Nethermind.Libp2p.Protocols.IpTcp": { + "type": "Transitive", + "resolved": "1.0.0-preview.45", + "contentHash": "vDoUfrz/45OEKc9TMEs9l0wPWW4r49opS/J+bh3zUTMLaWWf9jl8zkbPh5mz9moBh1JdDVuLRSPT3zRd8/Gvkg==", + "dependencies": { + "Nethermind.Libp2p.Core": "1.0.0-preview.45" + } + }, + "Nethermind.Libp2p.Protocols.MDns": { + "type": "Transitive", + "resolved": "1.0.0-preview.45", + "contentHash": "NZhHziBoq9mGeqV8o0QMoKOnBLjyPEIhEIcsnL6rdV4nJ1PwRWort8O811dh4aWq8rxVm1dkj66U4Q/ZwEX+JQ==", + "dependencies": { + "Makaretu.Dns.Multicast": "0.27.0", + "Nethermind.Libp2p.Core": "1.0.0-preview.45" + } + }, + "Nethermind.Libp2p.Protocols.Multistream": { + "type": "Transitive", + "resolved": "1.0.0-preview.45", + "contentHash": "JPYycuQ68VRMdCvlJoHA6iB3ROxCVUH0y0Nw03jtxttu9i6aQw4byGQTx+VyidTeij0H2I+Nkuf0oAB6xiDoHg==", + "dependencies": { + "Nethermind.Libp2p.Core": "1.0.0-preview.45" + } + }, + "Nethermind.Libp2p.Protocols.Noise": { + "type": "Transitive", + "resolved": "1.0.0-preview.45", + "contentHash": "+LvFZaZn8BvEMKHlIlX7N7PgKlOrExZQLITHtcTkLqua0qvjsgwCKYyr/3KwWQm2BX0YBlwp99bOW3d7mlDW4g==", + "dependencies": { + "BouncyCastle.Cryptography": "2.4.0", + "Google.Protobuf": "3.28.3", + "Nethermind.Libp2p.Core": "1.0.0-preview.45", + "Noise.NET": "1.0.0", + "libsodium": "1.0.20" + } + }, + "Nethermind.Libp2p.Protocols.Ping": { + "type": "Transitive", + "resolved": "1.0.0-preview.45", + "contentHash": "a0jomAxlnaWOVrLl5pnhYJO5XtGTILxExCC0ElHxv4uNBjaxcarBERreaf0Dku/8VWofOleNkPJWQpqu6W8RCA==", + "dependencies": { + "Nethermind.Libp2p.Core": "1.0.0-preview.45" + } + }, + "Nethermind.Libp2p.Protocols.Plaintext": { + "type": "Transitive", + "resolved": "1.0.0-preview.45", + "contentHash": "8WBSnSmro8g1W+7k+3pdE1DaTRUZqGEtSM8KUBw/4V0iOH4J2MIxAhLtB6Qq51ywY6T4WXFl5+aM4KDufGeITQ==", + "dependencies": { + "Google.Protobuf": "3.28.3", + "Nethermind.Libp2p.Core": "1.0.0-preview.45" + } + }, + "Nethermind.Libp2p.Protocols.Pubsub": { + "type": "Transitive", + "resolved": "1.0.0-preview.45", + "contentHash": "G9M3cYBr0fPcKA7n4EfyaIdntMSxxq/TquFbYF/i41pGq06pcAQSxH2c2qEUGNBYJfz8ao4lUoRRC755cNskSg==", + "dependencies": { + "BouncyCastle.Cryptography": "2.4.0", + "Google.Protobuf": "3.28.3", + "Nethermind.Libp2p.Core": "1.0.0-preview.45", + "Nethermind.Libp2p.Protocols.Identify": "1.0.0-preview.45" + } + }, + "Nethermind.Libp2p.Protocols.Quic": { + "type": "Transitive", + "resolved": "1.0.0-preview.45", + "contentHash": "TfdjpazHxi/Pxfa8oR3wPDoWzgTJJ9L0OHQo5hkozte9mpa8sUim+cfIiEC9qtAjq4D1/MfpvfQaZZzeRh2akQ==", + "dependencies": { + "BouncyCastle.Cryptography": "2.4.0", + "Nethermind.Libp2p.Core": "1.0.0-preview.45" + } + }, + "Nethermind.Libp2p.Protocols.Relay": { + "type": "Transitive", + "resolved": "1.0.0-preview.45", + "contentHash": "r91tuuF9pRzLGSZ8Vp/MInN4KFqQZDyKEsv1CkCsOOlkS/IGzFMWofYchgouQGrj7/yvAiPjjd3c8wlWpMp4dg==", + "dependencies": { + "Google.Protobuf": "3.28.3", + "Nethermind.Libp2p.Core": "1.0.0-preview.45" + } + }, + "Nethermind.Libp2p.Protocols.Tls": { + "type": "Transitive", + "resolved": "1.0.0-preview.45", + "contentHash": "BJXIfz9T1zPRJjVHGn4qJLvZu2vKnjoSoT9Zd+nYePc+C4ESwhtFuuHHSirnuKqJ/GVY2v8lvhb+fnjYSV3E8w==", + "dependencies": { + "Nethermind.Libp2p.Core": "1.0.0-preview.45", + "Nethermind.Libp2p.Protocols.Quic": "1.0.0-preview.45" + } + }, + "Nethermind.Libp2p.Protocols.Yamux": { + "type": "Transitive", + "resolved": "1.0.0-preview.45", + "contentHash": "ElNnTVoTxpHZMGTFbKTndQ1C3jFMMVLQfK1wzJVAw5sD294Yur42UKxdHtrzQJEJ/XHARz5ORxwtWcbprCQLDA==", + "dependencies": { + "Nethermind.Libp2p.Core": "1.0.0-preview.45" + } + }, + "Nethermind.Multiformats.Address": { + "type": "Transitive", + "resolved": "1.1.8", + "contentHash": "+nRuuVXjj/Okj/RAJtJUZ/nDRjwMfjJnF1+4Z7gKX2MjMsxR92KPJbsP4fY4IaEwXMDjNJsXJu78z2C06tElzw==", + "dependencies": { + "BinaryEncoding": "1.4.0", + "Nethermind.Multiformats.Base": "2.0.3-preview.1", + "Nethermind.Multiformats.Hash": "1.5.2-preview.1" + } + }, + "Nethermind.Multiformats.Base": { + "type": "Transitive", + "resolved": "2.0.3-preview.1", + "contentHash": "jrowPK2H6xlBnW4uwlGG6mZQvkHj1m9ktlItJEL26zZAH1UKAN7/kUv+SYDJYrKVAstQ4XuQ3KlisXREldrKBg==" + }, + "Nethermind.Multiformats.Hash": { + "type": "Transitive", + "resolved": "1.5.2-preview.1", + "contentHash": "NBS6KNsXeL19MHE4WdT6XkkGqsvqAFX0JiOxPA+BA/+HnvrHaQm1Z1EdGN9R3mdJZtxLk113Ccr3lfIL8d/b1w==", + "dependencies": { + "BinaryEncoding": "1.4.0", + "BouncyCastle.Cryptography": "2.4.0", + "Nethermind.Multiformats.Base": "2.0.3-preview.1", + "System.Composition": "1.2.0", + "murmurhash": "1.0.2" + } + }, + "NETStandard.Library": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "7jnbRU+L08FXKMxqUflxEXtVymWvNOrS8yHgu9s6EM8Anr6T/wIX4nZ08j/u3Asz+tCufp3YVwFSEvFTPYmBPA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "Noise.NET": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "fYnHQ8yZcj9W0fPGbzMkZUnE14aGGTFS8WE0Ow2hXiGhJ61Tv71cTi1yuugHxPCLyb87JpWMkq4lix8Rf06vtA==", + "dependencies": { + "NETStandard.Library": "1.6.1", + "libsodium": "1.0.16" + } + }, + "PierTwo.Lantern.Discv5.Enr": { + "type": "Transitive", + "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.7" + } + }, + "PierTwo.Lantern.Discv5.Rlp": { + "type": "Transitive", + "resolved": "1.0.0-preview.7", + "contentHash": "tAwonG4x8SWFBxd06JvzYNo0xvTsDoM9xfk2tnwIcFzCvY7PORvpOiy9AQcyjqomFQmCNqF4ezwZoRZJV32iQg==" + }, + "Polly.Core": { + "type": "Transitive", + "resolved": "8.6.5", + "contentHash": "t+sUVrIwvo7UmsgHGgOG9F0GDZSRIm47u2ylH17Gvcv1q5hNEwgD5GoBlFyc0kh/pebmPyrAgvGsR/65ZBaXlg==" + }, + "Portable.BouncyCastle": { + "type": "Transitive", + "resolved": "1.8.5", + "contentHash": "EaCgmntbH1sOzemRTqyXSqYjB6pLH7VCYHhhDYZ59guHSD5qPwhIYa7kfy0QUlmTRt9IXhaXdFhNuBUArp70Ng==" + }, + "prometheus-net": { + "type": "Transitive", + "resolved": "8.2.1", + "contentHash": "3wVgdEPOCBF752s2xps5T+VH+c9mJK8S8GKEDg49084P6JZMumTZI5Te6aJ9MQpX0sx7om6JOnBpIi7ZBmmiDQ==" + }, + "SimpleBase": { + "type": "Transitive", + "resolved": "4.0.2", + "contentHash": "sNKHP2Qzy4DafamgH44UGg1YeyHFT08AMgHPraxYt4CVBoHHYD5f0MjbBfdmtGca69xikPU5aV8H+MMP7ZnfIg==" + }, + "System.CodeDom": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "2sCCb7doXEwtYAbqzbF/8UAeDRMNmPaQbU2q50Psg1J9KzumyVVCgKQY8s53WIPTufNT0DpSe9QRvVjOzfDWBA==" + }, + "System.Composition": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "d7wMuKQtfsxUa7S13tITC8n1cQzewuhD5iDjZtK2prwFfKVzdYtgrTHgjaV03Zq7feGQ5gkP85tJJntXwInsJA==", + "dependencies": { + "System.Composition.AttributedModel": "6.0.0", + "System.Composition.Convention": "6.0.0", + "System.Composition.Hosting": "6.0.0", + "System.Composition.Runtime": "6.0.0", + "System.Composition.TypedParts": "6.0.0" + } + }, + "System.Composition.AttributedModel": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "WK1nSDLByK/4VoC7fkNiFuTVEiperuCN/Hyn+VN30R+W2ijO1d0Z2Qm0ScEl9xkSn1G2MyapJi8xpf4R8WRa/w==" + }, + "System.Composition.Convention": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "XYi4lPRdu5bM4JVJ3/UIHAiG6V6lWWUlkhB9ab4IOq0FrRsp0F4wTyV4Dj+Ds+efoXJ3qbLqlvaUozDO7OLeXA==", + "dependencies": { + "System.Composition.AttributedModel": "6.0.0" + } + }, + "System.Composition.Hosting": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "w/wXjj7kvxuHPLdzZ0PAUt++qJl03t7lENmb2Oev0n3zbxyNULbWBlnd5J5WUMMv15kg5o+/TCZFb6lSwfaUUQ==", + "dependencies": { + "System.Composition.Runtime": "6.0.0" + } + }, + "System.Composition.Runtime": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "qkRH/YBaMPTnzxrS5RDk1juvqed4A6HOD/CwRcDGyPpYps1J27waBddiiq1y93jk2ZZ9wuA/kynM+NO0kb3PKg==" + }, + "System.Composition.TypedParts": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "iUR1eHrL8Cwd82neQCJ00MpwNIBs4NZgXzrPqx8NJf/k4+mwBO0XCRmHYJT4OLSwDDqh5nBLJWkz5cROnrGhRA==", + "dependencies": { + "System.Composition.AttributedModel": "6.0.0", + "System.Composition.Hosting": "6.0.0", + "System.Composition.Runtime": "6.0.0" + } + }, + "System.Reactive": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "31kfaW4ZupZzPsI5PVe77VhnvFF55qgma7KZr/E0iFTs6fmdhhG8j0mgEx620iLTey1EynOkEfnyTjtNEpJzGw==" + }, + "Testably.Abstractions.FileSystem.Interface": { + "type": "Transitive", + "resolved": "10.0.0", + "contentHash": "tZOXFLGjkh8TxgMgKeEcM2HAlz9DwndGl6TFLo6ISHcszFX3FkuPMrtVbmqVjhooWNXrgJ/a9cH9ym5MZL1LAg==" + }, + "Tmds.LibC": { + "type": "Transitive", + "resolved": "0.2.0", + "contentHash": "+RvLuNHOLW7cxzgDe9yHLoayBgjsuH2/gJtJnuVMxweKrxxYT6TwQNAmt06SFWpjwk68aRcwwD4FfMMA6tZvVA==" + }, + "YamlDotNet": { + "type": "Transitive", + "resolved": "16.3.0", + "contentHash": "SgMOdxbz8X65z8hraIs6hOEdnkH6hESTAIUa7viEngHOYaH+6q5XJmwr1+yb9vJpNQ19hCQY69xbFsLtXpobQA==" + }, + "nethermind.abi": { + "type": "Project", + "dependencies": { + "MathNet.Numerics.FSharp": "[5.0.0, )", + "Nethermind.Core": "[1.37.0-unstable, )" + } + }, + "nethermind.api": { + "type": "Project", + "dependencies": { + "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": { + "type": "Project", + "dependencies": { + "Microsoft.ClearScript.V8.Native.linux-arm64": "[7.5.0, )", + "Microsoft.ClearScript.V8.Native.linux-x64": "[7.5.0, )", + "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.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.37.0-unstable, )", + "NonBlocking": "[2.1.2, )", + "System.Configuration.ConfigurationManager": "[10.0.3, )" + } + }, + "nethermind.consensus": { + "type": "Project", + "dependencies": { + "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.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.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.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": { + "type": "Project", + "dependencies": { + "Autofac": "[9.0.0, )", + "Autofac.Extensions.DependencyInjection": "[10.0.0, )", + "FastEnum": "[2.0.6, )", + "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", + "Microsoft.IdentityModel.JsonWebTokens": "[8.15.0, )", + "Nethermind.Crypto.SecP256k1": "[1.5.0, )", + "Nethermind.Logging": "[1.37.0-unstable, )", + "Nethermind.Numerics.Int256": "[1.4.0, )", + "NonBlocking": "[2.1.2, )", + "TestableIO.System.IO.Abstractions.Wrappers": "[22.1.0, )" + } + }, + "nethermind.crypto": { + "type": "Project", + "dependencies": { + "BouncyCastle.Cryptography": "[2.6.2, )", + "Ckzg.Bindings": "[2.1.5.1551, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Crypto.Bls": "[1.0.5, )", + "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", + "System.Security.Cryptography.ProtectedData": "[10.0.3, )" + } + }, + "nethermind.db": { + "type": "Project", + "dependencies": { + "Nethermind.Config": "[1.37.0-unstable, )", + "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", + "Nethermind.TurboPForBindings": "[1.0.0, )", + "NonBlocking": "[2.1.2, )" + } + }, + "nethermind.db.rocks": { + "type": "Project", + "dependencies": { + "ConcurrentHashSet": "[1.3.0, )", + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.Db": "[1.37.0-unstable, )", + "NonBlocking": "[2.1.2, )", + "RocksDB": "[10.4.2.63147, 10.4.2.63147]" + } + }, + "nethermind.db.rpc": { + "type": "Project", + "dependencies": { + "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.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.3.0, )" + } + }, + "nethermind.ethstats": { + "type": "Project", + "dependencies": { + "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.37.0-unstable, )", + "Nethermind.Crypto": "[1.37.0-unstable, )", + "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", + "Nethermind.Specs": "[1.37.0-unstable, )" + } + }, + "nethermind.evm.precompiles": { + "type": "Project", + "dependencies": { + "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.37.0-unstable, )", + "Nethermind.GmpBindings": "[1.0.3, )", + "Nethermind.MclBindings": "[1.0.5, )", + "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", + "Nethermind.Specs": "[1.37.0-unstable, )" + } + }, + "nethermind.externalsigner.plugin": { + "type": "Project", + "dependencies": { + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.JsonRpc": "[1.37.0-unstable, )" + } + }, + "nethermind.facade": { + "type": "Project", + "dependencies": { + "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.37.0-unstable, )" + } + }, + "nethermind.grpc": { + "type": "Project", + "dependencies": { + "Google.Protobuf": "[3.33.5, )", + "Google.Protobuf.Tools": "[3.33.5, )", + "Grpc": "[2.46.6, )", + "Nethermind.Config": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Serialization.Json": "[1.37.0-unstable, )" + } + }, + "nethermind.healthchecks": { + "type": "Project", + "dependencies": { + "AspNetCore.HealthChecks.UI": "[9.0.0, )", + "AspNetCore.HealthChecks.UI.InMemory.Storage": "[9.0.0, )", + "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.37.0-unstable, )" + } + }, + "nethermind.hive": { + "type": "Project", + "dependencies": { + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.Init": "[1.37.0-unstable, )" + } + }, + "nethermind.init": { + "type": "Project", + "dependencies": { + "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.37.0-unstable, )", + "Nethermind.Init": "[1.37.0-unstable, )" + } + }, + "nethermind.jsonrpc": { + "type": "Project", + "dependencies": { + "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.37.0-unstable, )", + "Nethermind.Init": "[1.37.0-unstable, )" + } + }, + "nethermind.keystore": { + "type": "Project", + "dependencies": { + "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, )" + } + }, + "nethermind.logging": { + "type": "Project" + }, + "nethermind.logging.nlog": { + "type": "Project", + "dependencies": { + "NLog": "[5.5.1, )", + "Nethermind.Logging": "[1.37.0-unstable, )" + } + }, + "nethermind.merge.aura": { + "type": "Project", + "dependencies": { + "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.37.0-unstable, )" + } + }, + "nethermind.merkleization": { + "type": "Project", + "dependencies": { + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Serialization.Ssz": "[1.37.0-unstable, )" + } + }, + "nethermind.monitoring": { + "type": "Project", + "dependencies": { + "Nethermind.Config": "[1.37.0-unstable, )", + "Nethermind.Logging": "[1.37.0-unstable, )", + "prometheus-net.AspNetCore": "[8.2.1, )" + } + }, + "nethermind.network": { + "type": "Project", + "dependencies": { + "Crc32.NET": "[1.2.0, )", + "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.37.0-unstable, )", + "Nethermind.Network.Stats": "[1.37.0-unstable, )", + "Nethermind.Synchronization": "[1.37.0-unstable, )", + "Snappier": "[1.3.0, )" + } + }, + "nethermind.network.contract": { + "type": "Project", + "dependencies": { + "Nethermind.Config": "[1.37.0-unstable, )" + } + }, + "nethermind.network.discovery": { + "type": "Project", + "dependencies": { + "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.37.0-unstable, )", + "Nethermind.Network.Enr": "[1.37.0-unstable, )" + } + }, + "nethermind.network.enr": { + "type": "Project", + "dependencies": { + "Nethermind.Crypto": "[1.37.0-unstable, )", + "Nethermind.Network": "[1.37.0-unstable, )" + } + }, + "nethermind.network.stats": { + "type": "Project", + "dependencies": { + "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.5, )", + "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.37.0-unstable, )", + "Snappier": "[1.3.0, )" + } + }, + "nethermind.seq": { + "type": "Project", + "dependencies": { + "Nethermind.Config": "[1.37.0-unstable, )" + } + }, + "nethermind.serialization.json": { + "type": "Project", + "dependencies": { + "Microsoft.ClearScript.V8": "[7.5.0, )", + "Nethermind.Core": "[1.37.0-unstable, )" + } + }, + "nethermind.serialization.rlp": { + "type": "Project", + "dependencies": { + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.DotNetty.Buffers": "[1.0.2.76, )" + } + }, + "nethermind.serialization.ssz": { + "type": "Project", + "dependencies": { + "Nethermind.Core": "[1.37.0-unstable, )" + } + }, + "nethermind.shutter": { + "type": "Project", + "dependencies": { + "Google.Protobuf": "[3.33.5, )", + "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.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.37.0-unstable, )", + "Nethermind.Logging": "[1.37.0-unstable, )", + "Nethermind.Serialization.Json": "[1.37.0-unstable, )" + } + }, + "nethermind.specs": { + "type": "Project", + "dependencies": { + "Nethermind.Config": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Serialization.Json": "[1.37.0-unstable, )", + "ZstdSharp.Port": "[0.8.7, )" + } + }, + "nethermind.state": { + "type": "Project", + "dependencies": { + "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.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.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.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": { + "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.37.0-unstable, )", + "Open.NAT.Core": "[2.1.0.5, )" + } + }, + "nethermind.wallet": { + "type": "Project", + "dependencies": { + "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.37.0-unstable, )", + "Nethermind.Consensus": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Init": "[1.37.0-unstable, )" + } + }, + "AspNetCore.HealthChecks.UI": { + "type": "CentralTransitive", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "r/bGIiFDm/Bv+S/OCcE1/YzM4Zul1nEKGRA42xcGqxaYmaQf1T+gFEReNNA+5nHXuXt5TRuBuVag1emXuwqe6w==", + "dependencies": { + "AspNetCore.HealthChecks.UI.Data": "9.0.0", + "KubernetesClient": "15.0.1", + "Microsoft.EntityFrameworkCore.Design": "8.0.11" + } + }, + "AspNetCore.HealthChecks.UI.InMemory.Storage": { + "type": "CentralTransitive", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "xuq+HBH8Ckz9r5d+yP1pk7cgIbPLBqCGfYu0MxtvbtsxIo7cv5nQoUCJsdgqN5/a4ndVUJZco6GjWFk6r4xN1w==", + "dependencies": { + "AspNetCore.HealthChecks.UI.Data": "9.0.0", + "Microsoft.EntityFrameworkCore.InMemory": "8.0.11" + } + }, + "Autofac": { + "type": "CentralTransitive", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "G8TpUMVIq1pEAMuAao8h5MKduY91SotjgK93wQb5LaxbJUVE0/XjCA6t2SOp+AkPC3GB/C2MAiF2D7krYjraFw==" + }, + "Autofac.Extensions.DependencyInjection": { + "type": "CentralTransitive", + "requested": "[10.0.0, )", + "resolved": "10.0.0", + "contentHash": "ZjR/onUlP7BzQ7VBBigQepWLAyAzi3VRGX3pP6sBqkPRiT61fsTZqbTpRUKxo30TMgbs1o3y6bpLbETix4SJog==", + "dependencies": { + "Autofac": "8.1.0" + } + }, + "BouncyCastle.Cryptography": { + "type": "CentralTransitive", + "requested": "[2.6.2, )", + "resolved": "2.6.2", + "contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" + }, + "Ckzg.Bindings": { + "type": "CentralTransitive", + "requested": "[2.1.5.1551, )", + "resolved": "2.1.5.1551", + "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" + }, + "CommunityToolkit.HighPerformance": { + "type": "CentralTransitive", + "requested": "[8.4.0, )", + "resolved": "8.4.0", + "contentHash": "flxspiBs0G/0GMp7IK2J2ijV9bTG6hEwFc/z6ekHqB6nwRJ4Ry2yLdx+TkbCUYFCl4XhABkAwomeKbT6zM2Zlg==" + }, + "ConcurrentHashSet": { + "type": "CentralTransitive", + "requested": "[1.3.0, )", + "resolved": "1.3.0", + "contentHash": "a30gfk4WDn2f7sOisXpko+CrQ7s5nL1ZWk4afnRwwRQHWPRrRSZpzn5Dz2YWpJtS90NJqkytQ/MkzHq4QYTIbg==" + }, + "Crc32.NET": { + "type": "CentralTransitive", + "requested": "[1.2.0, )", + "resolved": "1.2.0", + "contentHash": "wNW/huzolu8MNKUnwCVKxjfAlCFpeI8AZVfF46iAWJ1+P6bTU1AZct7VAkDDEjgeeTJCVTkGZaD6jSd/fOiUkA==", + "dependencies": { + "NETStandard.Library": "2.0.0" + } + }, + "DnsClient": { + "type": "CentralTransitive", + "requested": "[1.8.0, )", + "resolved": "1.8.0", + "contentHash": "RRwtaCXkXWsx0mmsReGDqCbRLtItfUbkRJlet1FpdciVhyMGKcPd57T1+8Jki9ojHlq9fntVhXQroOOgRak8DQ==" + }, + "FastEnum": { + "type": "CentralTransitive", + "requested": "[2.0.6, )", + "resolved": "2.0.6", + "contentHash": "R5iVHk/hC7aTj/+kHWWQ6zyqGS0fHyvnvjKBoKsYG2NW4/oZYzbW9cu1M7tADbyMKQ9mV4ibhrvXglkpnUfDdw==", + "dependencies": { + "FastEnum.Core": "2.0.6", + "FastEnum.Generators": "2.0.6" + } + }, + "Google.Protobuf": { + "type": "CentralTransitive", + "requested": "[3.33.5, )", + "resolved": "3.33.5", + "contentHash": "XEzLpCTosZb5I6eGSPn7rAES0VfkJkn3Cqydh0W39POdZwkdhPhOmAROTFJF9g0ardst4ulNXRm/q/iXwNu+Qw==" + }, + "Google.Protobuf.Tools": { + "type": "CentralTransitive", + "requested": "[3.33.5, )", + "resolved": "3.33.5", + "contentHash": "A1UnkTCZvOsIW1+S8papxEx0CxrcIZlxEC+audWbvovU7cZAaF6Rfb2yJgPsTkzqU+dpyBpHE5v1tLPQ+NF4rQ==" + }, + "Grpc": { + "type": "CentralTransitive", + "requested": "[2.46.6, )", + "resolved": "2.46.6", + "contentHash": "vpC39vgCxbVBnsn8boeQv2aGIdEFqQMnjsoDvyYfr6tC93Fe9KqNG1mAiF6KLzJTwk6aiWHGNYE2EqmDUzk5ng==", + "dependencies": { + "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, )", + "resolved": "5.0.0", + "contentHash": "lKYhd68fReW5odX/q+Uzxw3357Duq3zmvkYvnZVqqcc2r/EmrYGDoOdUGuHnhfr8yj9V34js5gQH/7IWcxZJxg==", + "dependencies": { + "FSharp.Core": "6.0.2", + "MathNet.Numerics": "5.0.0" + } + }, + "Microsoft.ClearScript.V8": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "k2xS9o6Oz57xx24nVhOqrkmJN/sDdkYTwdfmu2XS5KpnmAyqeVJBCfc9Wh83UvKUYb7lQBHSCk7gk2hLyJoG4Q==", + "dependencies": { + "Microsoft.ClearScript.Core": "7.5.0", + "Microsoft.ClearScript.V8.ICUData": "7.5.0", + "Newtonsoft.Json": "13.0.3" + } + }, + "Microsoft.ClearScript.V8.Native.linux-arm64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "UU+3Bef3UnwQgP8hKobT09ucYuYubVFiseAsuRUvmjvOBVu7yHRES+nXBNYSvDi88fMTp/HBUknpYQdrfoDemQ==" + }, + "Microsoft.ClearScript.V8.Native.linux-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "snoN9oRwKqShA32IsuCanLjNtP8hros2WOrOBL7g+ED3AV40qwrsbfKwWq37BzogrfsF1aEVoDkBpE19Az7DVQ==" + }, + "Microsoft.ClearScript.V8.Native.osx-arm64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "CkMgeX0I0+bXUzoaVoJdV86/k0H2PEukqCoZ8zQ28msB6YHeRX6FJTfvOQ0l6UTX5HaBHGG3CWUI04uBYe6M+A==" + }, + "Microsoft.ClearScript.V8.Native.osx-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "yXoXLWKJJgW5V6ez1aMa+ZS2nCef0X4iTYzPS9bTSYl9y7D4R2Ie2KrfR8nLO2rhOKimIMx3MH49Zh1CYruN/g==" + }, + "Microsoft.ClearScript.V8.Native.win-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "DKMxDLboTNflYkwDQ/ELrSf1vXTpew5UZ8xzrXSVKYFBU570VA6NKh1etEGhufuCuDyU7Je5L2g6H+19Dbl+tA==" + }, + "Microsoft.CodeAnalysis.Analyzers": { + "type": "CentralTransitive", + "requested": "[4.14.0, )", + "resolved": "3.3.3", + "contentHash": "j/rOZtLMVJjrfLRlAMckJLPW/1rze9MT1yfWqSIbUPGRu1m1P0fuo9PmqapwsmePfGB5PJrudQLvmUOAMF0DqQ==" + }, + "Microsoft.CodeAnalysis.CSharp": { + "type": "CentralTransitive", + "requested": "[5.0.0, )", + "resolved": "4.5.0", + "contentHash": "cM59oMKAOxvdv76bdmaKPy5hfj+oR+zxikWoueEB7CwTko7mt9sVKZI8Qxlov0C/LuKEG+WQwifepqL3vuTiBQ==", + "dependencies": { + "Microsoft.CodeAnalysis.Common": "[4.5.0]" + } + }, + "Microsoft.IdentityModel.JsonWebTokens": { + "type": "CentralTransitive", + "requested": "[8.15.0, )", + "resolved": "8.15.0", + "contentHash": "3513f5VzvOZy3ELd42wGnh1Q3e83tlGAuXFSNbENpgWYoAhLLzgFtd5PiaOPGAU0gqKhYGVzKavghLUGfX3HQg==", + "dependencies": { + "Microsoft.IdentityModel.Tokens": "8.15.0" + } + }, + "Microsoft.IO.RecyclableMemoryStream": { + "type": "CentralTransitive", + "requested": "[3.0.1, )", + "resolved": "3.0.1", + "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" + }, + "Nethermind.Crypto.Bls": { + "type": "CentralTransitive", + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "SYdQOFVVcC3R3VAm9Dv+u4Mc1yqHZETxt4tN3a+AFeOnEtUmpcjwVwYkkiiUIIrr6vQVVOUuwsDmaa9l3u45IQ==" + }, + "Nethermind.Crypto.SecP256k1": { + "type": "CentralTransitive", + "requested": "[1.5.0, )", + "resolved": "1.5.0", + "contentHash": "+mNlEgN1gYDB6f4jRcYssaE6/AlSoPr7eLDQHQoX+tXcnGRzgnArezPwz82TsWxruQGDh5h9Qfowa0xt4Xz59g==" + }, + "Nethermind.Crypto.SecP256r1": { + "type": "CentralTransitive", + "requested": "[1.0.0-preview.6, )", + "resolved": "1.0.0-preview.6", + "contentHash": "wFfpg1ofZz5nsjN8TAKUg0mdUCskmOUO0lFk3LcoeRkVnQ5Rw2rYzsJxgPFfnxAABH/EPPs62S7oF8E0Ayjjeg==" + }, + "Nethermind.DotNetty.Buffers": { + "type": "CentralTransitive", + "requested": "[1.0.2.76, )", + "resolved": "1.0.2.76", + "contentHash": "bI9wc+xazOgsgibMvPaMWtQ4dNWktBBCo3fZeUVFgEsDBMwagl3GqIozx4D1I1GbozkhCpUD55Q2KGx0CuDHHQ==", + "dependencies": { + "Nethermind.DotNetty.Common": "1.0.2.76" + } + }, + "Nethermind.DotNetty.Handlers": { + "type": "CentralTransitive", + "requested": "[1.0.2.76, )", + "resolved": "1.0.2.76", + "contentHash": "/x4hbadusLECVrnMVWpjLtwJ1fJQmbMXaD4Y8kZN+X0BJqAGXReN62HnaFWkgL8w3CJAB0aJySAE1sC5xlZjpg==", + "dependencies": { + "Nethermind.DotNetty.Buffers": "1.0.2.76", + "Nethermind.DotNetty.Codecs": "1.0.2.76", + "Nethermind.DotNetty.Common": "1.0.2.76", + "Nethermind.DotNetty.Transport": "1.0.2.76" + } + }, + "Nethermind.DotNetty.Transport": { + "type": "CentralTransitive", + "requested": "[1.0.2.76, )", + "resolved": "1.0.2.76", + "contentHash": "ZbTY44wAfOY8LPc/+/CO3NBezNzO6ZPpaZJTWJ7tzo7+IO2WuAvIoRGRPf5DOAhOozYZvUaDvaO7glKrzS6JAQ==", + "dependencies": { + "Nethermind.DotNetty.Buffers": "1.0.2.76", + "Nethermind.DotNetty.Common": "1.0.2.76" + } + }, + "Nethermind.GmpBindings": { + "type": "CentralTransitive", + "requested": "[1.0.3, )", + "resolved": "1.0.3", + "contentHash": "EE12z2k4ku0ugfI01utaQR8EbBoEMLI4QAKKGrfz5Fvbw/YtXTqDDzvKtBTleOB9YBH7oTpH9T9ZFtKgKZMj2g==" + }, + "Nethermind.Libp2p": { + "type": "CentralTransitive", + "requested": "[1.0.0-preview.45, )", + "resolved": "1.0.0-preview.45", + "contentHash": "OLV+fEkqbG+kFfQnFgkOloX8a1QPCJnTZfTkviOKgWo7vVlOnmePIvy0Tk963LVpmRmjreMCTvLLOh3yB1ZCJQ==", + "dependencies": { + "Nethermind.Libp2p.Core": "1.0.0-preview.45", + "Nethermind.Libp2p.Protocols.Identify": "1.0.0-preview.45", + "Nethermind.Libp2p.Protocols.MDns": "1.0.0-preview.45", + "Nethermind.Libp2p.Protocols.Multistream": "1.0.0-preview.45", + "Nethermind.Libp2p.Protocols.Noise": "1.0.0-preview.45", + "Nethermind.Libp2p.Protocols.Ping": "1.0.0-preview.45", + "Nethermind.Libp2p.Protocols.Plaintext": "1.0.0-preview.45", + "Nethermind.Libp2p.Protocols.Pubsub": "1.0.0-preview.45", + "Nethermind.Libp2p.Protocols.PubsubPeerDiscovery": "1.0.0-preview.45", + "Nethermind.Libp2p.Protocols.Relay": "1.0.0-preview.45", + "Nethermind.Libp2p.Protocols.Tls": "1.0.0-preview.45", + "Nethermind.Libp2p.Protocols.Yamux": "1.0.0-preview.45" + } + }, + "Nethermind.Libp2p.Protocols.PubsubPeerDiscovery": { + "type": "CentralTransitive", + "requested": "[1.0.0-preview.45, )", + "resolved": "1.0.0-preview.45", + "contentHash": "VK0g5fehGQbarfAzqwCFfNLhU7BBQBEkVypjt/ToOD619FhDMjqJJ1FuBPC0EP6Gmf2whG55cYnIBoGQdrNgtA==", + "dependencies": { + "Makaretu.Dns.Multicast": "0.27.0", + "Nethermind.Libp2p.Core": "1.0.0-preview.45", + "Nethermind.Libp2p.Protocols.Pubsub": "1.0.0-preview.45" + } + }, + "Nethermind.MclBindings": { + "type": "CentralTransitive", + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" + }, + "Nethermind.Numerics.Int256": { + "type": "CentralTransitive", + "requested": "[1.4.0, )", + "resolved": "1.4.0", + "contentHash": "w8HRMsdpX9fG9kcELJeJPEKIZgOUTCe47ebtejCvfBYQVlabA9blqba6QWIt5oG8cRSgnVlQ24DsdGsLzqFM+Q==" + }, + "Nethermind.TurboPForBindings": { + "type": "CentralTransitive", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" + }, + "Nito.Collections.Deque": { + "type": "CentralTransitive", + "requested": "[1.2.1, )", + "resolved": "1.2.1", + "contentHash": "P60Vz4oDByus/scUCwPiuWqnd9IEdsBqb9383PBPW1G2SUw3+/h04VSRKZvH4C70/DqKDSM4wgz5gw95sfkDyg==" + }, + "NLog": { + "type": "CentralTransitive", + "requested": "[5.5.1, )", + "resolved": "5.5.1", + "contentHash": "ZpLbXSi91tZJPjzNefE+3NugAkQtpWZpSLfZWJGcP1y8qMiXDKxW/nAQ+LYWo7GwH2iYsbKOdsLCK9S3BeRQxA==" + }, + "NonBlocking": { + "type": "CentralTransitive", + "requested": "[2.1.2, )", + "resolved": "2.1.2", + "contentHash": "yTP24PcuFmUw1RxQgYmIMxvpAJ1ciT/zv8Sb7OZHTuM/x9Tupz+DvEqeu9HykSYmI3/bGuy1ZZ7k/rZgfuIAuw==" + }, + "Open.NAT.Core": { + "type": "CentralTransitive", + "requested": "[2.1.0.5, )", + "resolved": "2.1.0.5", + "contentHash": "F/4WoNK1rYCMGZM6B1LVlgxf2wLogJc2ohMZxwmJw7Aky2Hc1IgFZvEj/cxcv5QQSFTvPN5AWYKomFXHukOUIg==" + }, + "PierTwo.Lantern.Discv5.WireProtocol": { + "type": "CentralTransitive", + "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.7", + "PierTwo.Lantern.Discv5.Rlp": "1.0.0-preview.7" + } + }, + "Polly": { + "type": "CentralTransitive", + "requested": "[8.6.5, )", + "resolved": "8.6.5", + "contentHash": "VqtW2ZE/ALvQMAH1cQY3qZ2cF2OXa3oe/HKMdOv6Q02HCoEW0rsFNfcBONXlHBe1TnjWW1vdRxBEkPeq0/2FHA==", + "dependencies": { + "Polly.Core": "8.6.5" + } + }, + "prometheus-net.AspNetCore": { + "type": "CentralTransitive", + "requested": "[8.2.1, )", + "resolved": "8.2.1", + "contentHash": "/4TfTvbwIDqpaKTiWvEsjUywiHYF9zZvGZF5sK15avoDsUO/WPQbKsF8TiMaesuphdFQPK2z52P0zk6j26V0rQ==", + "dependencies": { + "prometheus-net": "8.2.1" + } + }, + "RocksDB": { + "type": "CentralTransitive", + "requested": "[10.4.2.63147, 10.4.2.63147]", + "resolved": "10.4.2.63147", + "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" + }, + "SCrypt": { + "type": "CentralTransitive", + "requested": "[2.0.0.2, )", + "resolved": "2.0.0.2", + "contentHash": "ZSDp3iPpXw4KW+e1yKQbarn1byN8aP+B0FNy0oMPC+htu6XVxqr1ezeSfG4kmRWG4VUu1RT0Zp3HzkH6inrIIw==" + }, + "Snappier": { + "type": "CentralTransitive", + "requested": "[1.3.0, )", + "resolved": "1.3.0", + "contentHash": "yYANMXm5MUiF9jzsOI7WH5Cj1HYzcWEIEVI2Ljq8N7hS/zwLCBx+GoGeyc7aJFAvpRcbSIbdNaelVomHqd6UDQ==" + }, + "System.Configuration.ConfigurationManager": { + "type": "CentralTransitive", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "69ZT/MYxQSxwdiiHRtI08noXiG5drj/bXDDZISmeWkNUtbIfYgmTiof16tCVOLTdmSQY7W7gwxkMliKdreWHGQ==", + "dependencies": { + "System.Security.Cryptography.ProtectedData": "10.0.3" + } + }, + "System.Security.Cryptography.ProtectedData": { + "type": "CentralTransitive", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "JCKbH/CN5l0CSoJBILEvJmNQVp5vV+FY3q2ue4K9p4eDT4mFEv0bjTQCV+MD6Qk1b/qk9fWmZZKhG1TklbXw1Q==" + }, + "TestableIO.System.IO.Abstractions.Wrappers": { + "type": "CentralTransitive", + "requested": "[22.1.0, )", + "resolved": "22.1.0", + "contentHash": "IsW3jQqIiTN4GwdWFx+dzgRL5XR75UDTFVGuuIackPf2d7eH0KKyrx4wuIoASa1XnS9zhgLP39FKwJq6nbbx1w==", + "dependencies": { + "Testably.Abstractions.FileSystem.Interface": "10.0.0" + } + }, + "Websocket.Client": { + "type": "CentralTransitive", + "requested": "[5.3.0, )", + "resolved": "5.3.0", + "contentHash": "uhdDM+gruCEhHRCKCoyali1HJp0wSS/HBs5X9XZwULNKM2y5ML188TsvcEgWEFOx0NOaHfGNtfoC0cd1p2NOIg==", + "dependencies": { + "Microsoft.IO.RecyclableMemoryStream": "3.0.0", + "System.Reactive": "6.0.0" + } + }, + "ZstdSharp.Port": { + "type": "CentralTransitive", + "requested": "[0.8.7, )", + "resolved": "0.8.7", + "contentHash": "+4VpxvzEKaHpfTjsLRMhQHx6brqGBkIA+fjUM3wUW8ZoWpPFAeKrO2Nf4uZDeBjjzNDNxSRvLQH4b3S9Ku4JJQ==" + } + }, + "net10.0/linux-arm64": { + "Grpc.Core": { + "type": "Transitive", + "resolved": "2.46.6", + "contentHash": "ZoRg3KmOJ2urTF4+u3H0b1Yv10xzz2Y/flFWS2tnRmj8dbKLeiJaSRqu4LOBD3ova90evqLkVZ85kUkC4JT4lw==", + "dependencies": { + "Grpc.Core.Api": "2.46.6" + } + }, + "libsodium": { + "type": "Transitive", + "resolved": "1.0.20", + "contentHash": "fMO6HpAbvLagobzBH6eU36riWF01lCAweX34D5eugqjuXA+WS5MnV1ngE+2Sw3LvGvxZlmyLp9416t57dMZ5og==" + }, + "Tmds.LibC": { + "type": "Transitive", + "resolved": "0.2.0", + "contentHash": "+RvLuNHOLW7cxzgDe9yHLoayBgjsuH2/gJtJnuVMxweKrxxYT6TwQNAmt06SFWpjwk68aRcwwD4FfMMA6tZvVA==" + }, + "Ckzg.Bindings": { + "type": "CentralTransitive", + "requested": "[2.1.5.1551, )", + "resolved": "2.1.5.1551", + "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" + }, + "Microsoft.ClearScript.V8.Native.linux-arm64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "UU+3Bef3UnwQgP8hKobT09ucYuYubVFiseAsuRUvmjvOBVu7yHRES+nXBNYSvDi88fMTp/HBUknpYQdrfoDemQ==" + }, + "Microsoft.ClearScript.V8.Native.linux-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "snoN9oRwKqShA32IsuCanLjNtP8hros2WOrOBL7g+ED3AV40qwrsbfKwWq37BzogrfsF1aEVoDkBpE19Az7DVQ==" + }, + "Microsoft.ClearScript.V8.Native.osx-arm64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "CkMgeX0I0+bXUzoaVoJdV86/k0H2PEukqCoZ8zQ28msB6YHeRX6FJTfvOQ0l6UTX5HaBHGG3CWUI04uBYe6M+A==" + }, + "Microsoft.ClearScript.V8.Native.osx-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "yXoXLWKJJgW5V6ez1aMa+ZS2nCef0X4iTYzPS9bTSYl9y7D4R2Ie2KrfR8nLO2rhOKimIMx3MH49Zh1CYruN/g==" + }, + "Microsoft.ClearScript.V8.Native.win-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "DKMxDLboTNflYkwDQ/ELrSf1vXTpew5UZ8xzrXSVKYFBU570VA6NKh1etEGhufuCuDyU7Je5L2g6H+19Dbl+tA==" + }, + "Nethermind.Crypto.Bls": { + "type": "CentralTransitive", + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "SYdQOFVVcC3R3VAm9Dv+u4Mc1yqHZETxt4tN3a+AFeOnEtUmpcjwVwYkkiiUIIrr6vQVVOUuwsDmaa9l3u45IQ==" + }, + "Nethermind.Crypto.SecP256k1": { + "type": "CentralTransitive", + "requested": "[1.5.0, )", + "resolved": "1.5.0", + "contentHash": "+mNlEgN1gYDB6f4jRcYssaE6/AlSoPr7eLDQHQoX+tXcnGRzgnArezPwz82TsWxruQGDh5h9Qfowa0xt4Xz59g==" + }, + "Nethermind.Crypto.SecP256r1": { + "type": "CentralTransitive", + "requested": "[1.0.0-preview.6, )", + "resolved": "1.0.0-preview.6", + "contentHash": "wFfpg1ofZz5nsjN8TAKUg0mdUCskmOUO0lFk3LcoeRkVnQ5Rw2rYzsJxgPFfnxAABH/EPPs62S7oF8E0Ayjjeg==" + }, + "Nethermind.GmpBindings": { + "type": "CentralTransitive", + "requested": "[1.0.3, )", + "resolved": "1.0.3", + "contentHash": "EE12z2k4ku0ugfI01utaQR8EbBoEMLI4QAKKGrfz5Fvbw/YtXTqDDzvKtBTleOB9YBH7oTpH9T9ZFtKgKZMj2g==" + }, + "Nethermind.MclBindings": { + "type": "CentralTransitive", + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" + }, + "Nethermind.TurboPForBindings": { + "type": "CentralTransitive", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" + }, + "RocksDB": { + "type": "CentralTransitive", + "requested": "[10.4.2.63147, 10.4.2.63147]", + "resolved": "10.4.2.63147", + "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" + } + }, + "net10.0/linux-x64": { + "Grpc.Core": { + "type": "Transitive", + "resolved": "2.46.6", + "contentHash": "ZoRg3KmOJ2urTF4+u3H0b1Yv10xzz2Y/flFWS2tnRmj8dbKLeiJaSRqu4LOBD3ova90evqLkVZ85kUkC4JT4lw==", + "dependencies": { + "Grpc.Core.Api": "2.46.6" + } + }, + "libsodium": { + "type": "Transitive", + "resolved": "1.0.20", + "contentHash": "fMO6HpAbvLagobzBH6eU36riWF01lCAweX34D5eugqjuXA+WS5MnV1ngE+2Sw3LvGvxZlmyLp9416t57dMZ5og==" + }, + "Tmds.LibC": { + "type": "Transitive", + "resolved": "0.2.0", + "contentHash": "+RvLuNHOLW7cxzgDe9yHLoayBgjsuH2/gJtJnuVMxweKrxxYT6TwQNAmt06SFWpjwk68aRcwwD4FfMMA6tZvVA==" + }, + "Ckzg.Bindings": { + "type": "CentralTransitive", + "requested": "[2.1.5.1551, )", + "resolved": "2.1.5.1551", + "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" + }, + "Microsoft.ClearScript.V8.Native.linux-arm64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "UU+3Bef3UnwQgP8hKobT09ucYuYubVFiseAsuRUvmjvOBVu7yHRES+nXBNYSvDi88fMTp/HBUknpYQdrfoDemQ==" + }, + "Microsoft.ClearScript.V8.Native.linux-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "snoN9oRwKqShA32IsuCanLjNtP8hros2WOrOBL7g+ED3AV40qwrsbfKwWq37BzogrfsF1aEVoDkBpE19Az7DVQ==" + }, + "Microsoft.ClearScript.V8.Native.osx-arm64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "CkMgeX0I0+bXUzoaVoJdV86/k0H2PEukqCoZ8zQ28msB6YHeRX6FJTfvOQ0l6UTX5HaBHGG3CWUI04uBYe6M+A==" + }, + "Microsoft.ClearScript.V8.Native.osx-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "yXoXLWKJJgW5V6ez1aMa+ZS2nCef0X4iTYzPS9bTSYl9y7D4R2Ie2KrfR8nLO2rhOKimIMx3MH49Zh1CYruN/g==" + }, + "Microsoft.ClearScript.V8.Native.win-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "DKMxDLboTNflYkwDQ/ELrSf1vXTpew5UZ8xzrXSVKYFBU570VA6NKh1etEGhufuCuDyU7Je5L2g6H+19Dbl+tA==" + }, + "Nethermind.Crypto.Bls": { + "type": "CentralTransitive", + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "SYdQOFVVcC3R3VAm9Dv+u4Mc1yqHZETxt4tN3a+AFeOnEtUmpcjwVwYkkiiUIIrr6vQVVOUuwsDmaa9l3u45IQ==" + }, + "Nethermind.Crypto.SecP256k1": { + "type": "CentralTransitive", + "requested": "[1.5.0, )", + "resolved": "1.5.0", + "contentHash": "+mNlEgN1gYDB6f4jRcYssaE6/AlSoPr7eLDQHQoX+tXcnGRzgnArezPwz82TsWxruQGDh5h9Qfowa0xt4Xz59g==" + }, + "Nethermind.Crypto.SecP256r1": { + "type": "CentralTransitive", + "requested": "[1.0.0-preview.6, )", + "resolved": "1.0.0-preview.6", + "contentHash": "wFfpg1ofZz5nsjN8TAKUg0mdUCskmOUO0lFk3LcoeRkVnQ5Rw2rYzsJxgPFfnxAABH/EPPs62S7oF8E0Ayjjeg==" + }, + "Nethermind.GmpBindings": { + "type": "CentralTransitive", + "requested": "[1.0.3, )", + "resolved": "1.0.3", + "contentHash": "EE12z2k4ku0ugfI01utaQR8EbBoEMLI4QAKKGrfz5Fvbw/YtXTqDDzvKtBTleOB9YBH7oTpH9T9ZFtKgKZMj2g==" + }, + "Nethermind.MclBindings": { + "type": "CentralTransitive", + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" + }, + "Nethermind.TurboPForBindings": { + "type": "CentralTransitive", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" + }, + "RocksDB": { + "type": "CentralTransitive", + "requested": "[10.4.2.63147, 10.4.2.63147]", + "resolved": "10.4.2.63147", + "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" + } + }, + "net10.0/osx-arm64": { + "Grpc.Core": { + "type": "Transitive", + "resolved": "2.46.6", + "contentHash": "ZoRg3KmOJ2urTF4+u3H0b1Yv10xzz2Y/flFWS2tnRmj8dbKLeiJaSRqu4LOBD3ova90evqLkVZ85kUkC4JT4lw==", + "dependencies": { + "Grpc.Core.Api": "2.46.6" + } + }, + "libsodium": { + "type": "Transitive", + "resolved": "1.0.20", + "contentHash": "fMO6HpAbvLagobzBH6eU36riWF01lCAweX34D5eugqjuXA+WS5MnV1ngE+2Sw3LvGvxZlmyLp9416t57dMZ5og==" + }, + "Tmds.LibC": { + "type": "Transitive", + "resolved": "0.2.0", + "contentHash": "+RvLuNHOLW7cxzgDe9yHLoayBgjsuH2/gJtJnuVMxweKrxxYT6TwQNAmt06SFWpjwk68aRcwwD4FfMMA6tZvVA==" + }, + "Ckzg.Bindings": { + "type": "CentralTransitive", + "requested": "[2.1.5.1551, )", + "resolved": "2.1.5.1551", + "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" + }, + "Microsoft.ClearScript.V8.Native.linux-arm64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "UU+3Bef3UnwQgP8hKobT09ucYuYubVFiseAsuRUvmjvOBVu7yHRES+nXBNYSvDi88fMTp/HBUknpYQdrfoDemQ==" + }, + "Microsoft.ClearScript.V8.Native.linux-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "snoN9oRwKqShA32IsuCanLjNtP8hros2WOrOBL7g+ED3AV40qwrsbfKwWq37BzogrfsF1aEVoDkBpE19Az7DVQ==" + }, + "Microsoft.ClearScript.V8.Native.osx-arm64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "CkMgeX0I0+bXUzoaVoJdV86/k0H2PEukqCoZ8zQ28msB6YHeRX6FJTfvOQ0l6UTX5HaBHGG3CWUI04uBYe6M+A==" + }, + "Microsoft.ClearScript.V8.Native.osx-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "yXoXLWKJJgW5V6ez1aMa+ZS2nCef0X4iTYzPS9bTSYl9y7D4R2Ie2KrfR8nLO2rhOKimIMx3MH49Zh1CYruN/g==" + }, + "Microsoft.ClearScript.V8.Native.win-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "DKMxDLboTNflYkwDQ/ELrSf1vXTpew5UZ8xzrXSVKYFBU570VA6NKh1etEGhufuCuDyU7Je5L2g6H+19Dbl+tA==" + }, + "Nethermind.Crypto.Bls": { + "type": "CentralTransitive", + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "SYdQOFVVcC3R3VAm9Dv+u4Mc1yqHZETxt4tN3a+AFeOnEtUmpcjwVwYkkiiUIIrr6vQVVOUuwsDmaa9l3u45IQ==" + }, + "Nethermind.Crypto.SecP256k1": { + "type": "CentralTransitive", + "requested": "[1.5.0, )", + "resolved": "1.5.0", + "contentHash": "+mNlEgN1gYDB6f4jRcYssaE6/AlSoPr7eLDQHQoX+tXcnGRzgnArezPwz82TsWxruQGDh5h9Qfowa0xt4Xz59g==" + }, + "Nethermind.Crypto.SecP256r1": { + "type": "CentralTransitive", + "requested": "[1.0.0-preview.6, )", + "resolved": "1.0.0-preview.6", + "contentHash": "wFfpg1ofZz5nsjN8TAKUg0mdUCskmOUO0lFk3LcoeRkVnQ5Rw2rYzsJxgPFfnxAABH/EPPs62S7oF8E0Ayjjeg==" + }, + "Nethermind.GmpBindings": { + "type": "CentralTransitive", + "requested": "[1.0.3, )", + "resolved": "1.0.3", + "contentHash": "EE12z2k4ku0ugfI01utaQR8EbBoEMLI4QAKKGrfz5Fvbw/YtXTqDDzvKtBTleOB9YBH7oTpH9T9ZFtKgKZMj2g==" + }, + "Nethermind.MclBindings": { + "type": "CentralTransitive", + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" + }, + "Nethermind.TurboPForBindings": { + "type": "CentralTransitive", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" + }, + "RocksDB": { + "type": "CentralTransitive", + "requested": "[10.4.2.63147, 10.4.2.63147]", + "resolved": "10.4.2.63147", + "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" + } + }, + "net10.0/osx-x64": { + "Grpc.Core": { + "type": "Transitive", + "resolved": "2.46.6", + "contentHash": "ZoRg3KmOJ2urTF4+u3H0b1Yv10xzz2Y/flFWS2tnRmj8dbKLeiJaSRqu4LOBD3ova90evqLkVZ85kUkC4JT4lw==", + "dependencies": { + "Grpc.Core.Api": "2.46.6" + } + }, + "libsodium": { + "type": "Transitive", + "resolved": "1.0.20", + "contentHash": "fMO6HpAbvLagobzBH6eU36riWF01lCAweX34D5eugqjuXA+WS5MnV1ngE+2Sw3LvGvxZlmyLp9416t57dMZ5og==" + }, + "Tmds.LibC": { + "type": "Transitive", + "resolved": "0.2.0", + "contentHash": "+RvLuNHOLW7cxzgDe9yHLoayBgjsuH2/gJtJnuVMxweKrxxYT6TwQNAmt06SFWpjwk68aRcwwD4FfMMA6tZvVA==" + }, + "Ckzg.Bindings": { + "type": "CentralTransitive", + "requested": "[2.1.5.1551, )", + "resolved": "2.1.5.1551", + "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" + }, + "Microsoft.ClearScript.V8.Native.linux-arm64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "UU+3Bef3UnwQgP8hKobT09ucYuYubVFiseAsuRUvmjvOBVu7yHRES+nXBNYSvDi88fMTp/HBUknpYQdrfoDemQ==" + }, + "Microsoft.ClearScript.V8.Native.linux-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "snoN9oRwKqShA32IsuCanLjNtP8hros2WOrOBL7g+ED3AV40qwrsbfKwWq37BzogrfsF1aEVoDkBpE19Az7DVQ==" + }, + "Microsoft.ClearScript.V8.Native.osx-arm64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "CkMgeX0I0+bXUzoaVoJdV86/k0H2PEukqCoZ8zQ28msB6YHeRX6FJTfvOQ0l6UTX5HaBHGG3CWUI04uBYe6M+A==" + }, + "Microsoft.ClearScript.V8.Native.osx-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "yXoXLWKJJgW5V6ez1aMa+ZS2nCef0X4iTYzPS9bTSYl9y7D4R2Ie2KrfR8nLO2rhOKimIMx3MH49Zh1CYruN/g==" + }, + "Microsoft.ClearScript.V8.Native.win-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "DKMxDLboTNflYkwDQ/ELrSf1vXTpew5UZ8xzrXSVKYFBU570VA6NKh1etEGhufuCuDyU7Je5L2g6H+19Dbl+tA==" + }, + "Nethermind.Crypto.Bls": { + "type": "CentralTransitive", + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "SYdQOFVVcC3R3VAm9Dv+u4Mc1yqHZETxt4tN3a+AFeOnEtUmpcjwVwYkkiiUIIrr6vQVVOUuwsDmaa9l3u45IQ==" + }, + "Nethermind.Crypto.SecP256k1": { + "type": "CentralTransitive", + "requested": "[1.5.0, )", + "resolved": "1.5.0", + "contentHash": "+mNlEgN1gYDB6f4jRcYssaE6/AlSoPr7eLDQHQoX+tXcnGRzgnArezPwz82TsWxruQGDh5h9Qfowa0xt4Xz59g==" + }, + "Nethermind.Crypto.SecP256r1": { + "type": "CentralTransitive", + "requested": "[1.0.0-preview.6, )", + "resolved": "1.0.0-preview.6", + "contentHash": "wFfpg1ofZz5nsjN8TAKUg0mdUCskmOUO0lFk3LcoeRkVnQ5Rw2rYzsJxgPFfnxAABH/EPPs62S7oF8E0Ayjjeg==" + }, + "Nethermind.GmpBindings": { + "type": "CentralTransitive", + "requested": "[1.0.3, )", + "resolved": "1.0.3", + "contentHash": "EE12z2k4ku0ugfI01utaQR8EbBoEMLI4QAKKGrfz5Fvbw/YtXTqDDzvKtBTleOB9YBH7oTpH9T9ZFtKgKZMj2g==" + }, + "Nethermind.MclBindings": { + "type": "CentralTransitive", + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" + }, + "Nethermind.TurboPForBindings": { + "type": "CentralTransitive", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" + }, + "RocksDB": { + "type": "CentralTransitive", + "requested": "[10.4.2.63147, 10.4.2.63147]", + "resolved": "10.4.2.63147", + "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" + } + }, + "net10.0/win-x64": { + "Grpc.Core": { + "type": "Transitive", + "resolved": "2.46.6", + "contentHash": "ZoRg3KmOJ2urTF4+u3H0b1Yv10xzz2Y/flFWS2tnRmj8dbKLeiJaSRqu4LOBD3ova90evqLkVZ85kUkC4JT4lw==", + "dependencies": { + "Grpc.Core.Api": "2.46.6" + } + }, + "libsodium": { + "type": "Transitive", + "resolved": "1.0.20", + "contentHash": "fMO6HpAbvLagobzBH6eU36riWF01lCAweX34D5eugqjuXA+WS5MnV1ngE+2Sw3LvGvxZlmyLp9416t57dMZ5og==" + }, + "Tmds.LibC": { + "type": "Transitive", + "resolved": "0.2.0", + "contentHash": "+RvLuNHOLW7cxzgDe9yHLoayBgjsuH2/gJtJnuVMxweKrxxYT6TwQNAmt06SFWpjwk68aRcwwD4FfMMA6tZvVA==" + }, + "Ckzg.Bindings": { + "type": "CentralTransitive", + "requested": "[2.1.5.1551, )", + "resolved": "2.1.5.1551", + "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" + }, + "Microsoft.ClearScript.V8.Native.linux-arm64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "UU+3Bef3UnwQgP8hKobT09ucYuYubVFiseAsuRUvmjvOBVu7yHRES+nXBNYSvDi88fMTp/HBUknpYQdrfoDemQ==" + }, + "Microsoft.ClearScript.V8.Native.linux-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "snoN9oRwKqShA32IsuCanLjNtP8hros2WOrOBL7g+ED3AV40qwrsbfKwWq37BzogrfsF1aEVoDkBpE19Az7DVQ==" + }, + "Microsoft.ClearScript.V8.Native.osx-arm64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "CkMgeX0I0+bXUzoaVoJdV86/k0H2PEukqCoZ8zQ28msB6YHeRX6FJTfvOQ0l6UTX5HaBHGG3CWUI04uBYe6M+A==" + }, + "Microsoft.ClearScript.V8.Native.osx-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "yXoXLWKJJgW5V6ez1aMa+ZS2nCef0X4iTYzPS9bTSYl9y7D4R2Ie2KrfR8nLO2rhOKimIMx3MH49Zh1CYruN/g==" + }, + "Microsoft.ClearScript.V8.Native.win-x64": { + "type": "CentralTransitive", + "requested": "[7.5.0, )", + "resolved": "7.5.0", + "contentHash": "DKMxDLboTNflYkwDQ/ELrSf1vXTpew5UZ8xzrXSVKYFBU570VA6NKh1etEGhufuCuDyU7Je5L2g6H+19Dbl+tA==" + }, + "Nethermind.Crypto.Bls": { + "type": "CentralTransitive", + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "SYdQOFVVcC3R3VAm9Dv+u4Mc1yqHZETxt4tN3a+AFeOnEtUmpcjwVwYkkiiUIIrr6vQVVOUuwsDmaa9l3u45IQ==" + }, + "Nethermind.Crypto.SecP256k1": { + "type": "CentralTransitive", + "requested": "[1.5.0, )", + "resolved": "1.5.0", + "contentHash": "+mNlEgN1gYDB6f4jRcYssaE6/AlSoPr7eLDQHQoX+tXcnGRzgnArezPwz82TsWxruQGDh5h9Qfowa0xt4Xz59g==" + }, + "Nethermind.Crypto.SecP256r1": { + "type": "CentralTransitive", + "requested": "[1.0.0-preview.6, )", + "resolved": "1.0.0-preview.6", + "contentHash": "wFfpg1ofZz5nsjN8TAKUg0mdUCskmOUO0lFk3LcoeRkVnQ5Rw2rYzsJxgPFfnxAABH/EPPs62S7oF8E0Ayjjeg==" + }, + "Nethermind.GmpBindings": { + "type": "CentralTransitive", + "requested": "[1.0.3, )", + "resolved": "1.0.3", + "contentHash": "EE12z2k4ku0ugfI01utaQR8EbBoEMLI4QAKKGrfz5Fvbw/YtXTqDDzvKtBTleOB9YBH7oTpH9T9ZFtKgKZMj2g==" + }, + "Nethermind.MclBindings": { + "type": "CentralTransitive", + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" + }, + "Nethermind.TurboPForBindings": { + "type": "CentralTransitive", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" + }, + "RocksDB": { + "type": "CentralTransitive", + "requested": "[10.4.2.63147, 10.4.2.63147]", + "resolved": "10.4.2.63147", + "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" + } + } + } +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/wwwroot/index.html b/src/Nethermind/Nethermind.Runner/wwwroot/index.html index c9e34b49e66d..7f826e8facad 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 05cbff79ae19..4c493e6bdf57 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 bc00e5e9d94b..a7518b3d49d7 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 77f5825ba966..d164de5da097 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/ByteArray32Converter.cs b/src/Nethermind/Nethermind.Serialization.Json/ByteArray32Converter.cs index a09eb926f6d4..60849f194190 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/ByteArray32Converter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ByteArray32Converter.cs @@ -2,18 +2,24 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Text.Json; +using System.Text.Json.Serialization; using Nethermind.Core.Extensions; namespace Nethermind.Serialization.Json { - using System.Buffers; - using System.Runtime.CompilerServices; - using System.Text.Json; - using System.Text.Json.Serialization; public class Bytes32Converter : JsonConverter { + private const ushort HexPrefix = 0x7830; + [SkipLocalsInit] public override byte[] Read( ref Utf8JsonReader reader, @@ -21,36 +27,56 @@ public override byte[] Read( JsonSerializerOptions options) { ReadOnlySpan hex = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; - if (hex.StartsWith("0x"u8)) + if (hex.Length >= 2 && Unsafe.As(ref MemoryMarshal.GetReference(hex)) == HexPrefix) { - hex = hex[2..]; + hex = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref MemoryMarshal.GetReference(hex), 2), hex.Length - 2); } if (hex.Length > 64) { - throw new JsonException(); + ThrowJsonException(); } if (hex.Length < 64) { - Span hex32 = stackalloc byte[64]; - hex32.Fill((byte)'0'); - hex.CopyTo(hex32[(64 - hex.Length)..]); - return Bytes.FromUtf8HexString(hex32); + // Use Vector512 as 64-byte buffer instead of stackalloc (avoids GS cookie) + // Fill with '0' (0x30) using single vector broadcast + store + Vector512 hex32Storage = Vector512.Create((byte)'0'); + ref byte hex32Ref = ref Unsafe.As, byte>(ref hex32Storage); + hex.CopyTo(MemoryMarshal.CreateSpan(ref Unsafe.Add(ref hex32Ref, 64 - hex.Length), hex.Length)); + return Bytes.FromUtf8HexString(MemoryMarshal.CreateReadOnlySpan(ref hex32Ref, 64)); } return Bytes.FromUtf8HexString(hex); } + [DoesNotReturn, StackTraceHidden] + private static void ThrowJsonException() => throw new JsonException(); + + [SkipLocalsInit] public override void Write( Utf8JsonWriter writer, byte[] bytes, JsonSerializerOptions options) { - Span data = (bytes is null || bytes.Length < 32) ? stackalloc byte[32] : bytes; - if (bytes is not null && bytes.Length < 32) + // Use Vector256 as 32-byte buffer instead of stackalloc (avoids GS cookie) + Vector256 dataStorage = default; // Zero-filled (correct for left-padding) + ref byte dataRef = ref Unsafe.As, byte>(ref dataStorage); + ReadOnlySpan data; + + if (bytes is null || bytes.Length < 32) + { + if (bytes is not null) + { + int len = bytes.Length; + Unsafe.CopyBlockUnaligned(ref Unsafe.Add(ref dataRef, 32 - len), + ref MemoryMarshal.GetArrayDataReference(bytes), (uint)len); + } + data = MemoryMarshal.CreateReadOnlySpan(ref dataRef, 32); + } + else { - bytes.AsSpan().CopyTo(data[(32 - bytes.Length)..]); + data = bytes; } ByteArrayConverter.Convert(writer, data, skipLeadingZeros: false); diff --git a/src/Nethermind/Nethermind.Serialization.Json/CountingTextReader.cs b/src/Nethermind/Nethermind.Serialization.Json/CountingTextReader.cs deleted file mode 100644 index fa57f6b5a795..000000000000 --- a/src/Nethermind/Nethermind.Serialization.Json/CountingTextReader.cs +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Nethermind.Serialization.Json -{ - public class CountingTextReader : TextReader - { - private readonly TextReader _innerReader; - public int Length { get; private set; } - - public CountingTextReader(TextReader innerReader) - { - _innerReader = innerReader; - } - - public override void Close() - { - base.Close(); - _innerReader.Close(); - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (disposing) - { - _innerReader.Dispose(); - } - } - - public override int Peek() => _innerReader.Peek(); - - public override int Read() - { - Length++; - return _innerReader.Read(); - } - - public override int Read(char[] buffer, int index, int count) => IncrementLength(_innerReader.Read(buffer, index, count)); - - public override int Read(Span buffer) => IncrementLength(_innerReader.Read(buffer)); - - public override async Task ReadAsync(char[] buffer, int index, int count) => IncrementLength(await _innerReader.ReadAsync(buffer, index, count)); - - public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) => IncrementLength(await _innerReader.ReadAsync(buffer, cancellationToken)); - - public override int ReadBlock(char[] buffer, int index, int count) => IncrementLength(_innerReader.ReadBlock(buffer, index, count)); - - public override int ReadBlock(Span buffer) => IncrementLength(_innerReader.ReadBlock(buffer)); - - public override string ReadLine() => IncrementLength(_innerReader.ReadLine()); - - public override async ValueTask ReadBlockAsync(Memory buffer, CancellationToken cancellationToken = default) => IncrementLength(await _innerReader.ReadBlockAsync(buffer, cancellationToken)); - - public override async Task ReadBlockAsync(char[] buffer, int index, int count) => IncrementLength(await _innerReader.ReadBlockAsync(buffer, index, count)); - - public override async Task ReadLineAsync() => IncrementLength(await _innerReader.ReadLineAsync()); - - public override string ReadToEnd() => IncrementLength(_innerReader.ReadToEnd()); - - public override async Task ReadToEndAsync() => IncrementLength(await _innerReader.ReadToEndAsync()); - - private string IncrementLength(in string read) - { - if (!string.IsNullOrEmpty(read)) - { - Length += read.Length; - } - return read; - } - - private int IncrementLength(in int read) - { - Length += read; - return read; - } - } -} diff --git a/src/Nethermind/Nethermind.Serialization.Json/CountingTextWriter.cs b/src/Nethermind/Nethermind.Serialization.Json/CountingTextWriter.cs deleted file mode 100644 index 16beb11721d9..000000000000 --- a/src/Nethermind/Nethermind.Serialization.Json/CountingTextWriter.cs +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.IO; -using System.Text; - -namespace Nethermind.Serialization.Json -{ - public class CountingTextWriter : TextWriter - { - private readonly TextWriter _textWriter; - - public long Size { get; private set; } - - public CountingTextWriter(TextWriter textWriter) - { - _textWriter = textWriter ?? throw new ArgumentNullException(nameof(textWriter)); - } - - public override Encoding Encoding => _textWriter.Encoding; - - public override void Write(char value) - { - _textWriter.Write(value); - Size++; - } - - public override void Flush() - { - _textWriter.Flush(); - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - _textWriter.Dispose(); - } - } - } -} diff --git a/src/Nethermind/Nethermind.Serialization.Json/DoubleConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/DoubleConverter.cs index 46541c689b16..9828ac29f5c6 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/DoubleConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/DoubleConverter.cs @@ -48,7 +48,7 @@ public override double[] Read( { throw new JsonException(); } - using ArrayPoolList values = new ArrayPoolList(16); + using ArrayPoolListRef values = new(16); while (reader.Read() && reader.TokenType == JsonTokenType.Number) { values.Add(reader.GetDouble()); diff --git a/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs b/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs index cd7d544be18e..7ce9dc3bb296 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs @@ -9,58 +9,75 @@ using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using System.Threading; using System.Threading.Tasks; using Nethermind.Core.Collections; namespace Nethermind.Serialization.Json { - public class EthereumJsonSerializer : IJsonSerializer + public sealed class EthereumJsonSerializer : IJsonSerializer { public const int DefaultMaxDepth = 128; + private static readonly object _globalOptionsLock = new(); + + private static readonly List _additionalConverters = new(); + private static readonly List _additionalResolvers = new(); + private static bool _strictHexFormat; + private static int _optionsVersion; + private readonly int? _maxDepth; - private readonly JsonSerializerOptions _jsonOptions; + private readonly JsonConverter[] _instanceConverters; + private readonly object _instanceOptionsLock = new(); + + private JsonSerializerOptions _jsonOptions = null!; + private JsonSerializerOptions _jsonOptionsIndented = null!; + private int _instanceOptionsVersion; public EthereumJsonSerializer(IEnumerable converters, int maxDepth = DefaultMaxDepth) { _maxDepth = maxDepth; - _jsonOptions = CreateOptions(indented: false, maxDepth: maxDepth, converters: converters); + _instanceConverters = CopyConverters(converters); + RefreshInstanceOptions(); } public EthereumJsonSerializer(int maxDepth = DefaultMaxDepth) { _maxDepth = maxDepth; - _jsonOptions = maxDepth != DefaultMaxDepth ? CreateOptions(indented: false, maxDepth: maxDepth) : JsonOptions; + _instanceConverters = []; + RefreshInstanceOptions(); } public object Deserialize(string json, Type type) { - return JsonSerializer.Deserialize(json, type, _jsonOptions); + return JsonSerializer.Deserialize(json, type, GetSerializerOptions(indented: false)); } public T Deserialize(Stream stream) { - return JsonSerializer.Deserialize(stream, _jsonOptions); + return JsonSerializer.Deserialize(stream, GetSerializerOptions(indented: false)); } public T Deserialize(string json) { - return JsonSerializer.Deserialize(json, _jsonOptions); + return JsonSerializer.Deserialize(json, GetSerializerOptions(indented: false)); } public T Deserialize(ref Utf8JsonReader json) { - return JsonSerializer.Deserialize(ref json, _jsonOptions); + return JsonSerializer.Deserialize(ref json, GetSerializerOptions(indented: false)); } public string Serialize(T value, bool indented = false) { - return JsonSerializer.Serialize(value, indented ? JsonOptionsIndented : _jsonOptions); + return JsonSerializer.Serialize(value, GetSerializerOptions(indented)); } - private static JsonSerializerOptions CreateOptions(bool indented, IEnumerable converters = null, int maxDepth = DefaultMaxDepth) + private static JsonSerializerOptions CreateOptions(bool indented, IEnumerable instanceConverters = null, int maxDepth = DefaultMaxDepth) { - var options = new JsonSerializerOptions + SnapshotGlobalOptions(out bool strictHexFormat, out JsonConverter[] additionalConverters, out IJsonTypeInfoResolver[] additionalResolvers); + + var result = new JsonSerializerOptions { WriteIndented = indented, NewLine = "\n", @@ -71,6 +88,7 @@ private static JsonSerializerOptions CreateOptions(bool indented, IEnumerable()); - - return options; + result.Converters.AddRange(additionalConverters); + result.Converters.AddRange(instanceConverters ?? Array.Empty()); + return result; } - private static readonly List _additionalConverters = new(); public static void AddConverter(JsonConverter converter) { - _additionalConverters.Add(converter); + ArgumentNullException.ThrowIfNull(converter); + lock (_globalOptionsLock) + { + _additionalConverters.Add(converter); + RefreshGlobalOptionsNoLock(); + } + } + + public static void AddTypeInfoResolver(IJsonTypeInfoResolver resolver) + { + ArgumentNullException.ThrowIfNull(resolver); + lock (_globalOptionsLock) + { + for (int i = 0; i < _additionalResolvers.Count; i++) + { + if (ReferenceEquals(_additionalResolvers[i], resolver)) + { + return; + } + } - JsonOptions = CreateOptions(indented: false); - JsonOptionsIndented = CreateOptions(indented: true); + _additionalResolvers.Add(resolver); + RefreshGlobalOptionsNoLock(); + } + } + + public static bool StrictHexFormat + { + get => _strictHexFormat; + set + { + lock (_globalOptionsLock) + { + if (_strictHexFormat == value) + return; + + _strictHexFormat = value; + RefreshGlobalOptionsNoLock(); + } + } } public static JsonSerializerOptions JsonOptions { get; private set; } = CreateOptions(indented: false); public static JsonSerializerOptions JsonOptionsIndented { get; private set; } = CreateOptions(indented: true); - private static readonly StreamPipeWriterOptions optionsLeaveOpen = new(pool: MemoryPool.Shared, minimumBufferSize: 4096, leaveOpen: true); - private static readonly StreamPipeWriterOptions options = new(pool: MemoryPool.Shared, minimumBufferSize: 4096, leaveOpen: false); + private static readonly StreamPipeWriterOptions optionsLeaveOpen = new(pool: MemoryPool.Shared, minimumBufferSize: 16384, leaveOpen: true); + private static readonly StreamPipeWriterOptions options = new(pool: MemoryPool.Shared, minimumBufferSize: 16384, leaveOpen: false); private static CountingStreamPipeWriter GetPipeWriter(Stream stream, bool leaveOpen) { @@ -129,7 +183,7 @@ public long Serialize(Stream stream, T value, bool indented = false, bool lea { var countingWriter = GetPipeWriter(stream, leaveOpen); using var writer = new Utf8JsonWriter(countingWriter, CreateWriterOptions(indented)); - JsonSerializer.Serialize(writer, value, indented ? JsonOptionsIndented : _jsonOptions); + JsonSerializer.Serialize(writer, value, GetSerializerOptions(indented)); countingWriter.Complete(); long outputCount = countingWriter.WrittenCount; @@ -146,7 +200,7 @@ private JsonWriterOptions CreateWriterOptions(bool indented) public async ValueTask SerializeAsync(Stream stream, T value, CancellationToken cancellationToken, bool indented = false, bool leaveOpen = true) { var writer = GetPipeWriter(stream, leaveOpen); - await JsonSerializer.SerializeAsync(writer, value, indented ? JsonOptionsIndented : _jsonOptions, cancellationToken); + await JsonSerializer.SerializeAsync(writer, value, GetSerializerOptions(indented), cancellationToken); await writer.CompleteAsync(); long outputCount = writer.WrittenCount; @@ -154,12 +208,121 @@ public async ValueTask SerializeAsync(Stream stream, T value, Cancellat } public Task SerializeAsync(PipeWriter writer, T value, bool indented = false) - => JsonSerializer.SerializeAsync(writer, value, indented ? JsonOptionsIndented : _jsonOptions); + { + using var jsonWriter = new Utf8JsonWriter((IBufferWriter)writer, CreateWriterOptions(indented)); + JsonSerializer.Serialize(jsonWriter, value, GetSerializerOptions(indented)); + return Task.CompletedTask; + } + + /// + /// Pre-serializes instances to warm System.Text.Json metadata caches at startup. + /// + public static void WarmupSerializer(params object[] instances) + { + foreach (object instance in instances) + { + _ = JsonSerializer.SerializeToUtf8Bytes(instance, instance.GetType(), JsonOptions); + } + } public static void SerializeToStream(Stream stream, T value, bool indented = false) { JsonSerializer.Serialize(stream, value, indented ? JsonOptionsIndented : JsonOptions); } + + private JsonSerializerOptions GetSerializerOptions(bool indented) + { + EnsureInstanceOptionsCurrent(); + return indented ? _jsonOptionsIndented : _jsonOptions; + } + + private void EnsureInstanceOptionsCurrent() + { + int currentVersion = Volatile.Read(ref _optionsVersion); + if (_instanceOptionsVersion == currentVersion) + { + return; + } + + lock (_instanceOptionsLock) + { + if (_instanceOptionsVersion != currentVersion) + { + RefreshInstanceOptions(); + } + } + } + + private void RefreshInstanceOptions() + { + _jsonOptions = CreateOptions(indented: false, instanceConverters: _instanceConverters, maxDepth: _maxDepth ?? DefaultMaxDepth); + _jsonOptionsIndented = CreateOptions(indented: true, instanceConverters: _instanceConverters, maxDepth: _maxDepth ?? DefaultMaxDepth); + _instanceOptionsVersion = Volatile.Read(ref _optionsVersion); + } + + private static void RefreshGlobalOptionsNoLock() + { + JsonOptions = CreateOptions(indented: false); + JsonOptionsIndented = CreateOptions(indented: true); + Interlocked.Increment(ref _optionsVersion); + } + + private static void SnapshotGlobalOptions(out bool strictHexFormat, out JsonConverter[] additionalConverters, out IJsonTypeInfoResolver[] additionalResolvers) + { + lock (_globalOptionsLock) + { + strictHexFormat = _strictHexFormat; + additionalConverters = new JsonConverter[_additionalConverters.Count]; + for (int i = 0; i < _additionalConverters.Count; i++) + { + additionalConverters[i] = _additionalConverters[i]; + } + + additionalResolvers = new IJsonTypeInfoResolver[_additionalResolvers.Count]; + for (int i = 0; i < _additionalResolvers.Count; i++) + { + additionalResolvers[i] = _additionalResolvers[i]; + } + } + } + + private static IJsonTypeInfoResolver BuildTypeInfoResolver(IReadOnlyList additionalResolvers) + { + int additionalResolversCount = additionalResolvers.Count; + if (additionalResolversCount == 0) + { + return new DefaultJsonTypeInfoResolver(); + } + + IJsonTypeInfoResolver[] resolverChain = new IJsonTypeInfoResolver[additionalResolversCount + 1]; + for (int i = 0; i < additionalResolversCount; i++) + { + resolverChain[i] = additionalResolvers[i]; + } + + resolverChain[additionalResolversCount] = new DefaultJsonTypeInfoResolver(); + return JsonTypeInfoResolver.Combine(resolverChain); + } + + private static JsonConverter[] CopyConverters(IEnumerable converters) + { + ArgumentNullException.ThrowIfNull(converters); + + if (converters is JsonConverter[] convertersArray) + { + JsonConverter[] clone = new JsonConverter[convertersArray.Length]; + Array.Copy(convertersArray, clone, convertersArray.Length); + return clone; + } + + List list = new(); + foreach (JsonConverter converter in converters) + { + list.Add(converter); + } + + return [.. list]; + } } public static class JsonElementExtensions diff --git a/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs b/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs index 27d95de085e4..b6c0046f1b18 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs @@ -1,13 +1,37 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using System.Runtime.CompilerServices; using System.Threading; namespace Nethermind.Serialization.Json; public static class ForcedNumberConversion { - public static readonly AsyncLocal ForcedConversion = new(); + public static readonly ThreadAwareAsyncLocal ForcedConversion = new(); - public static NumberConversion GetFinalConversion() => ForcedConversion.Value ?? NumberConversion.Hex; + [ThreadStatic] + private static NumberConversion? _threadCache; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NumberConversion GetFinalConversion() => _threadCache ?? NumberConversion.Hex; + + /// + /// Wrapper around AsyncLocal that also updates a ThreadStatic cache for fast reads. + /// + public sealed class ThreadAwareAsyncLocal + { + private readonly AsyncLocal _asyncLocal = new(); + + public NumberConversion? Value + { + get => _asyncLocal.Value; + set + { + _asyncLocal.Value = value; + _threadCache = value; + } + } + } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/Hash256Converter.cs b/src/Nethermind/Nethermind.Serialization.Json/Hash256Converter.cs new file mode 100644 index 000000000000..6ff8b3c1eb3d --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Json/Hash256Converter.cs @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +#nullable enable +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text.Json; +using System.Text.Json.Serialization; +using Nethermind.Core.Crypto; + +namespace Nethermind.Serialization.Json; + +public class Hash256Converter : JsonConverter +{ + private readonly bool _strictHexFormat; + + public Hash256Converter(bool strictHexFormat = false) + { + _strictHexFormat = strictHexFormat; + } + + public override Hash256? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + + byte[]? bytes = ByteArrayConverter.Convert(ref reader, _strictHexFormat); + return bytes is null ? null : new Hash256(bytes); + } + + [SkipLocalsInit] + public override void Write( + Utf8JsonWriter writer, + Hash256 keccak, + JsonSerializerOptions options) + { + WriteHashHex(writer, in keccak.ValueHash256); + } + + /// + /// SIMD-accelerated hex encoding for 32-byte hashes. + /// Writes raw JSON (including quotes) via WriteRawValue to bypass the encoder entirely. + /// + [SkipLocalsInit] + internal static void WriteHashHex(Utf8JsonWriter writer, in ValueHash256 hash) + { + // Raw JSON: '"' + "0x" + 64 hex chars + '"' = 68 bytes + Unsafe.SkipInit(out HexWriter.HexBuffer72 rawBuf); + ref byte b = ref Unsafe.As(ref rawBuf); + + Unsafe.Add(ref b, 0) = (byte)'"'; + Unsafe.WriteUnaligned(ref Unsafe.Add(ref b, 1), (ushort)0x7830); // "0x" LE + + HexWriter.Encode32Bytes(ref Unsafe.Add(ref b, 3), hash.Bytes); + + Unsafe.Add(ref b, 67) = (byte)'"'; + + writer.WriteRawValue( + MemoryMarshal.CreateReadOnlySpan(ref b, 68), + skipInputValidation: true); + } + + // Methods needed to ser/de dictionary keys + public override Hash256 ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + byte[]? bytes = ByteArrayConverter.Convert(ref reader, _strictHexFormat); + return bytes is null ? null! : new Hash256(bytes); + } + + public override void WriteAsPropertyName(Utf8JsonWriter writer, Hash256 value, JsonSerializerOptions options) + { + writer.WritePropertyName(value.ToString()); + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Json/HexWriter.cs b/src/Nethermind/Nethermind.Serialization.Json/HexWriter.cs new file mode 100644 index 000000000000..ef0fbd2106fc --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Json/HexWriter.cs @@ -0,0 +1,428 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers.Binary; +using System.IO.Pipelines; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using System.Text.Json; +using Nethermind.Int256; + +namespace Nethermind.Serialization.Json; + +/// +/// Shared low-level hex encoding primitives used by JSON converters. +/// +public static class HexWriter +{ + /// + /// Encode the low 8 bytes of a Vector128 to 16 hex chars using SSSE3 PSHUFB. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Ssse3Encode8Bytes(ref byte dest, Vector128 input) + { + Vector128 hexLookup = Vector128.Create( + (byte)'0', (byte)'1', (byte)'2', (byte)'3', + (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'a', (byte)'b', + (byte)'c', (byte)'d', (byte)'e', (byte)'f'); + Vector128 mask = Vector128.Create((byte)0x0F); + + Vector128 hi = Sse2.ShiftRightLogical(input.AsUInt16(), 4).AsByte() & mask; + Vector128 lo = input & mask; + Ssse3.Shuffle(hexLookup, Sse2.UnpackLow(hi, lo)).StoreUnsafe(ref dest); + } + + /// + /// Encode 16 bytes of a Vector128 to 32 hex chars using SSSE3 PSHUFB. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Ssse3Encode16Bytes(ref byte dest, Vector128 input) + { + Vector128 hexLookup = Vector128.Create( + (byte)'0', (byte)'1', (byte)'2', (byte)'3', + (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'a', (byte)'b', + (byte)'c', (byte)'d', (byte)'e', (byte)'f'); + Vector128 mask = Vector128.Create((byte)0x0F); + + Vector128 hi = Sse2.ShiftRightLogical(input.AsUInt16(), 4).AsByte() & mask; + Vector128 lo = input & mask; + Ssse3.Shuffle(hexLookup, Sse2.UnpackLow(hi, lo)).StoreUnsafe(ref dest); + Ssse3.Shuffle(hexLookup, Sse2.UnpackHigh(hi, lo)).StoreUnsafe(ref Unsafe.Add(ref dest, 16)); + } + + /// + /// Encode 32 bytes to 64 hex chars using AVX-512 VBMI cross-lane byte permutation. + /// vpermi2b does arbitrary byte interleave across the full 256-bit register in a single + /// instruction, eliminating the UnpackLow/UnpackHigh + lane-crossing overhead of SSSE3/AVX2. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Avx512VbmiEncode32Bytes(ref byte dest, Vector256 input) + { + Vector256 mask = Vector256.Create((byte)0x0F); + Vector256 hi = Avx2.ShiftRightLogical(input.AsUInt16(), 4).AsByte() & mask; + Vector256 lo = input & mask; + + // vpermi2b: pick hi[i], lo[i] pairs across full 256-bit width + // indices 0-31 select from hi, 32-63 select from lo + Vector256 interleaved0 = Avx512Vbmi.VL.PermuteVar32x8x2(hi, + Vector256.Create( + (byte)0, 32, 1, 33, 2, 34, 3, 35, 4, 36, 5, 37, 6, 38, 7, 39, + 8, 40, 9, 41, 10, 42, 11, 43, 12, 44, 13, 45, 14, 46, 15, 47), lo); + + Vector256 interleaved1 = Avx512Vbmi.VL.PermuteVar32x8x2(hi, + Vector256.Create( + (byte)16, 48, 17, 49, 18, 50, 19, 51, 20, 52, 21, 53, 22, 54, 23, 55, + 24, 56, 25, 57, 26, 58, 27, 59, 28, 60, 29, 61, 30, 62, 31, 63), lo); + + // vpshufb: nibble-to-hex lookup (works within 128-bit lanes, lookup replicated in both) + Vector256 hexLookup = Vector256.Create( + (byte)'0', (byte)'1', (byte)'2', (byte)'3', + (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'a', (byte)'b', + (byte)'c', (byte)'d', (byte)'e', (byte)'f', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', + (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'a', (byte)'b', + (byte)'c', (byte)'d', (byte)'e', (byte)'f'); + + Avx2.Shuffle(hexLookup, interleaved0).StoreUnsafe(ref dest); + Avx2.Shuffle(hexLookup, interleaved1).StoreUnsafe(ref Unsafe.Add(ref dest, 32)); + } + + /// + /// 512-byte lookup table: for byte value i, HexByteLookup[i*2] and [i*2+1] are the + /// two lowercase hex ASCII chars. Single indexed load + 16-bit store per byte, + /// replacing ~10 ALU ops of a branchless arithmetic approach. + /// + private static ReadOnlySpan HexByteLookup => + "000102030405060708090a0b0c0d0e0f"u8 + + "101112131415161718191a1b1c1d1e1f"u8 + + "202122232425262728292a2b2c2d2e2f"u8 + + "303132333435363738393a3b3c3d3e3f"u8 + + "404142434445464748494a4b4c4d4e4f"u8 + + "505152535455565758595a5b5c5d5e5f"u8 + + "606162636465666768696a6b6c6d6e6f"u8 + + "707172737475767778797a7b7c7d7e7f"u8 + + "808182838485868788898a8b8c8d8e8f"u8 + + "909192939495969798999a9b9c9d9e9f"u8 + + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"u8 + + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"u8 + + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"u8 + + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"u8 + + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"u8 + + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"u8; + + /// + /// Scalar: encode one byte to 2 hex chars via lookup table. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void EncodeByte(ref byte dest, int byteVal) + { + Unsafe.WriteUnaligned(ref dest, + Unsafe.ReadUnaligned(ref Unsafe.Add(ref MemoryMarshal.GetReference(HexByteLookup), byteVal * 2))); + } + + /// + /// Scalar: encode a ulong (big-endian byte order) to 16 hex chars. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void EncodeUlongScalar(ref byte dest, ulong value) + { + ref byte lookup = ref MemoryMarshal.GetReference(HexByteLookup); + for (int i = 0; i < 8; i++) + { + int byteVal = (int)(value >> ((7 - i) << 3)) & 0xFF; + Unsafe.WriteUnaligned(ref Unsafe.Add(ref dest, i * 2), + Unsafe.ReadUnaligned(ref Unsafe.Add(ref lookup, byteVal * 2))); + } + } + + /// + /// Scalar: encode a byte span to hex chars. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void EncodeBytesScalar(ref byte dest, ReadOnlySpan src) + { + ref byte lookup = ref MemoryMarshal.GetReference(HexByteLookup); + for (int i = 0; i < src.Length; i++) + { + Unsafe.WriteUnaligned(ref Unsafe.Add(ref dest, i * 2), + Unsafe.ReadUnaligned(ref Unsafe.Add(ref lookup, src[i] * 2))); + } + } + + /// + /// Encode a ulong to 16 hex chars, dispatching to SSSE3 or scalar. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void EncodeUlong(ref byte dest, ulong value) + { + if (Ssse3.IsSupported) + { + ulong be = BinaryPrimitives.ReverseEndianness(value); + Ssse3Encode8Bytes(ref dest, Vector128.CreateScalarUnsafe(be).AsByte()); + } + else + { + EncodeUlongScalar(ref dest, value); + } + } + + /// + /// Encode 32 bytes to 64 hex chars, dispatching to AVX-512 VBMI, SSSE3, or scalar. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Encode32Bytes(ref byte dest, ReadOnlySpan src) + { + if (Avx512Vbmi.VL.IsSupported) + { + Avx512VbmiEncode32Bytes(ref dest, Vector256.LoadUnsafe(ref MemoryMarshal.GetReference(src))); + } + else if (Ssse3.IsSupported) + { + ref byte srcRef = ref MemoryMarshal.GetReference(src); + Ssse3Encode16Bytes(ref dest, Vector128.LoadUnsafe(ref srcRef)); + Ssse3Encode16Bytes(ref Unsafe.Add(ref dest, 32), Vector128.LoadUnsafe(ref srcRef, 16)); + } + else + { + EncodeBytesScalar(ref dest, src); + } + } + + /// + /// Write a non-zero ulong as a hex JSON string value ("0x...") using WriteRawValue. + /// Used by LongConverter and ULongConverter. + /// + [SkipLocalsInit] + internal static void WriteUlongHexRawValue(Utf8JsonWriter writer, ulong value) + { + // Use InlineArray to avoid GS cookie overhead from stackalloc + Unsafe.SkipInit(out HexBuffer24 rawBuf); + ref byte b = ref Unsafe.As(ref rawBuf); + + EncodeUlong(ref Unsafe.Add(ref b, 3), value); + + // nibbleCount: ceil(significantBits / 4), guaranteed >= 1 since value != 0 + // nint keeps Unsafe.Add in 64-bit register arithmetic, avoiding movsxd + nint nibbleCount = (nint)((67 - (uint)BitOperations.LeadingZeroCount(value)) >> 2); + nint spanStart = 16 - nibbleCount; + + ref byte spanRef = ref Unsafe.Add(ref b, spanStart); + spanRef = (byte)'"'; + Unsafe.WriteUnaligned(ref Unsafe.Add(ref spanRef, 1), (ushort)0x7830); // "0x" LE + Unsafe.Add(ref b, 19) = (byte)'"'; + + writer.WriteRawValue( + MemoryMarshal.CreateReadOnlySpan(ref spanRef, (int)nibbleCount + 4), + skipInputValidation: true); + } + + /// + /// Write a UInt256 as a hex JSON string value ("0x...") using WriteRawValue. + /// + [SkipLocalsInit] + internal static void WriteUInt256HexRawValue(Utf8JsonWriter writer, UInt256 value, bool zeroPadded = false) + { + Unsafe.SkipInit(out HexBuffer72 rawBuf); + ref byte buffer = ref Unsafe.As(ref rawBuf); + + BuildUInt256Hex(ref buffer, value, includeQuotes: true, zeroPadded, out nint spanStart, out int spanLength); + + writer.WriteRawValue( + MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref buffer, spanStart), spanLength), + skipInputValidation: true); + } + + /// + /// Write a UInt256 as a hex property name ("0x..."). + /// + [SkipLocalsInit] + internal static void WriteUInt256HexPropertyName(Utf8JsonWriter writer, UInt256 value, bool zeroPadded = false) + { + Unsafe.SkipInit(out HexBuffer72 rawBuf); + ref byte buffer = ref Unsafe.As(ref rawBuf); + + BuildUInt256Hex(ref buffer, value, includeQuotes: false, zeroPadded, out nint spanStart, out int spanLength); + + writer.WritePropertyName( + MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref buffer, spanStart), spanLength)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void BuildUInt256Hex(ref byte buffer, UInt256 value, bool includeQuotes, bool zeroPadded, out nint spanStart, out int spanLength) + { + nint hexOffset = includeQuotes ? 3 : 2; + EncodeUInt256Hex(ref Unsafe.Add(ref buffer, hexOffset), value); + + int nibbleCount = zeroPadded ? 64 : GetSignificantNibbleCount(value); + spanStart = zeroPadded ? 0 : 64 - nibbleCount; + ref byte spanRef = ref Unsafe.Add(ref buffer, spanStart); + + if (includeQuotes) + { + spanRef = (byte)'"'; + Unsafe.WriteUnaligned(ref Unsafe.Add(ref spanRef, 1), (ushort)0x7830); // "0x" LE + Unsafe.Add(ref spanRef, nibbleCount + 3) = (byte)'"'; + spanLength = nibbleCount + 4; + } + else + { + Unsafe.WriteUnaligned(ref spanRef, (ushort)0x7830); // "0x" LE + spanLength = nibbleCount + 2; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetSignificantNibbleCount(UInt256 value) + { + int leadingZeroBits; + if (value.u3 != 0) + { + leadingZeroBits = BitOperations.LeadingZeroCount(value.u3); + } + else if (value.u2 != 0) + { + leadingZeroBits = 64 + BitOperations.LeadingZeroCount(value.u2); + } + else if (value.u1 != 0) + { + leadingZeroBits = 128 + BitOperations.LeadingZeroCount(value.u1); + } + else + { + leadingZeroBits = 192 + BitOperations.LeadingZeroCount(value.u0); + } + + int nibbleCount = (259 - leadingZeroBits) >> 2; + return nibbleCount == 0 ? 1 : nibbleCount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void EncodeUInt256Hex(ref byte dest, UInt256 value) + { + if (Avx512Vbmi.VL.IsSupported) + { + Vector256 reversed = Avx512Vbmi.VL.PermuteVar32x8( + Vector256.LoadUnsafe(ref Unsafe.As(ref value)), + Vector256.Create( + (byte)31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)); + Avx512VbmiEncode32Bytes(ref dest, reversed); + } + else if (Ssse3.IsSupported) + { + Ssse3Encode16Bytes(ref dest, + Vector128.Create( + BinaryPrimitives.ReverseEndianness(value.u3), + BinaryPrimitives.ReverseEndianness(value.u2)).AsByte()); + + Ssse3Encode16Bytes(ref Unsafe.Add(ref dest, 32), + Vector128.Create( + BinaryPrimitives.ReverseEndianness(value.u1), + BinaryPrimitives.ReverseEndianness(value.u0)).AsByte()); + } + else + { + EncodeUlongScalar(ref dest, value.u3); + EncodeUlongScalar(ref Unsafe.Add(ref dest, 16), value.u2); + EncodeUlongScalar(ref Unsafe.Add(ref dest, 32), value.u1); + EncodeUlongScalar(ref Unsafe.Add(ref dest, 48), value.u0); + } + } + + /// + /// 24-byte inline buffer for ulong hex encoding (20 bytes needed, rounded up to + /// 3 x 8-byte ulong elements for alignment). Used instead of stackalloc to avoid + /// GS cookie (stack canary) overhead. The JIT inserts a cookie write in the prologue + /// and a verify + CORINFO_HELP_FAIL_FAST call in the epilogue for every stackalloc + /// buffer, adding ~35 bytes per method. Inline array structs are treated as regular + /// locals and avoid this. + /// + [InlineArray(3)] + private struct HexBuffer24 + { + private ulong _element0; + } + + /// + /// 72-byte inline buffer for hash/UInt256 hex encoding (68 bytes needed, rounded up + /// to 9 x 8-byte ulong elements for alignment). Used instead of stackalloc to avoid + /// GS cookie (stack canary) overhead. The JIT inserts a cookie write in the prologue + /// and a verify + CORINFO_HELP_FAIL_FAST call in the epilogue for every stackalloc + /// buffer, adding ~35 bytes per method. Inline array structs are treated as regular + /// locals and avoid this. + /// + [InlineArray(9)] + internal struct HexBuffer72 + { + private ulong _element0; + } + + private const int MaxHexRequest = 4096; + + /// + /// Writes a large byte array as hex directly into a + /// in chunks, bounded by the actual span size returned by GetSpan. + /// + public static void WriteHexChunked(PipeWriter writer, byte[] data) + { + ReadOnlySpan remaining = data; + while (remaining.Length > 0) + { + Span hex = writer.GetSpan(Math.Min(remaining.Length * 2, MaxHexRequest)); + int inputLen = Math.Min(remaining.Length, hex.Length / 2); + EncodeToHex(remaining[..inputLen], ref MemoryMarshal.GetReference(hex)); + writer.Advance(inputLen * 2); + + remaining = remaining[inputLen..]; + } + } + + /// + /// Writes a small byte array as hex in a single span into a . + /// + public static void WriteHexSmall(PipeWriter writer, byte[] data) + { + int hexLen = data.Length * 2; + Span hex = writer.GetSpan(hexLen); + int inputLen = Math.Min(data.Length, hex.Length / 2); + EncodeToHex(((ReadOnlySpan)data)[..inputLen], ref MemoryMarshal.GetReference(hex)); + writer.Advance(inputLen * 2); + } + + /// + /// Encode arbitrary-length bytes to hex using SIMD (AVX-512 VBMI / SSSE3) with scalar tail. + /// + private static void EncodeToHex(ReadOnlySpan src, ref byte dest) + { + int offset = 0; + + // 32-byte blocks: AVX-512 VBMI or 2x SSSE3 or scalar + while (offset + 32 <= src.Length) + { + Encode32Bytes(ref Unsafe.Add(ref dest, offset * 2), src.Slice(offset, 32)); + offset += 32; + } + + // 16-byte block via SSSE3 + if (Ssse3.IsSupported && offset + 16 <= src.Length) + { + Ssse3Encode16Bytes(ref Unsafe.Add(ref dest, offset * 2), + Vector128.LoadUnsafe(ref Unsafe.Add(ref MemoryMarshal.GetReference(src), offset))); + offset += 16; + } + + // Scalar tail + if (offset < src.Length) + { + EncodeBytesScalar(ref Unsafe.Add(ref dest, offset * 2), src[offset..]); + } + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Json/IJsonSerializer.cs b/src/Nethermind/Nethermind.Serialization.Json/IJsonSerializer.cs index dcb773e3de20..2f3b4d9b9ad0 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/IJsonSerializer.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/IJsonSerializer.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; using System.IO; using System.IO.Pipelines; using System.Threading; diff --git a/src/Nethermind/Nethermind.Serialization.Json/IntConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/IntConverter.cs index ebee61024f3a..1edf445d735a 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/IntConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/IntConverter.cs @@ -16,7 +16,7 @@ private static int FromString(ReadOnlySpan 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 37756065e77c..a2ecd66fc590 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs @@ -2,137 +2,141 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; +using System.Buffers.Text; using System.Globalization; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Text.Json.Serialization; +using Nethermind.Core.Extensions; -namespace Nethermind.Serialization.Json +namespace Nethermind.Serialization.Json; + +public class LongConverter : JsonConverter { - using Nethermind.Core.Extensions; - using System.Buffers; - using System.Buffers.Binary; - using System.Buffers.Text; - using System.Runtime.CompilerServices; - using System.Text.Json; - using System.Text.Json.Serialization; - - public class LongConverter : JsonConverter + public static long FromString(string s) { - public static long FromString(string s) + if (s is null) { - if (s is null) - { - throw new JsonException("null cannot be assigned to long"); - } + throw new JsonException("null cannot be assigned to long"); + } - if (s == Bytes.ZeroHexValue) - { - return 0L; - } + if (s == Bytes.ZeroHexValue) + { + return 0L; + } - if (s.StartsWith("0x0")) - { - return long.Parse(s.AsSpan(2), NumberStyles.AllowHexSpecifier); - } + if (s.StartsWith("0x0")) + { + return long.Parse(s.AsSpan(2), NumberStyles.AllowHexSpecifier); + } - if (s.StartsWith("0x")) - { - Span withZero = new(new char[s.Length - 1]); - withZero[0] = '0'; - s.AsSpan(2).CopyTo(withZero[1..]); - return long.Parse(withZero, NumberStyles.AllowHexSpecifier); - } + if (s.StartsWith("0x")) + { + Span withZero = new(new char[s.Length - 1]); + withZero[0] = '0'; + s.AsSpan(2).CopyTo(withZero[1..]); + return long.Parse(withZero, NumberStyles.AllowHexSpecifier); + } + + return long.Parse(s, NumberStyles.Integer); + } + + public override long ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + ReadOnlySpan hex = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; + return FromString(hex); + } - return long.Parse(s, NumberStyles.Integer); + public static long FromString(ReadOnlySpan s) + { + if (s.Length == 0) + { + throw new JsonException("null cannot be assigned to long"); } - public override long ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + if (s.SequenceEqual("0x0"u8)) { - ReadOnlySpan hex = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; - return FromString(hex); + return 0L; } - public static long FromString(ReadOnlySpan s) + long value; + if (s.StartsWith("0x"u8)) { - if (s.Length == 0) + s = s[2..]; + if (Utf8Parser.TryParse(s, out value, out _, 'x')) { - throw new JsonException("null cannot be assigned to long"); + return value; } + } + else if (Utf8Parser.TryParse(s, out value, out _)) + { + return value; + } - if (s.SequenceEqual("0x0"u8)) - { - return 0L; - } + ThrowJsonException(); + return default; - long value; - if (s.StartsWith("0x"u8)) - { - s = s[2..]; - if (Utf8Parser.TryParse(s, out value, out _, 'x')) - { - return value; - } - } - else if (Utf8Parser.TryParse(s, out value, out _)) - { - return value; - } + [DoesNotReturn, StackTraceHidden] + static void ThrowJsonException() => throw new JsonException("hex to long"); + } - throw new JsonException("hex to long"); + internal static long ReadCore(ref Utf8JsonReader reader) + { + if (reader.TokenType == JsonTokenType.Number) + { + return reader.GetInt64(); + } + else if (reader.TokenType == JsonTokenType.String) + { + return !reader.HasValueSequence + ? FromString(reader.ValueSpan) + : FromString(reader.ValueSequence.ToArray()); } - public override long Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) + ThrowJsonException(); + return default; + + [DoesNotReturn, StackTraceHidden] + static void ThrowJsonException() => 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, + long value, + JsonSerializerOptions options) + { + switch (ForcedNumberConversion.GetFinalConversion()) { - if (reader.TokenType == JsonTokenType.Number) - { - return reader.GetInt64(); - } - else if (reader.TokenType == JsonTokenType.String) - { - if (!reader.HasValueSequence) + case NumberConversion.Hex: + if (value == 0) { - return FromString(reader.ValueSpan); + writer.WriteStringValue("0x0"u8); } else { - return FromString(reader.ValueSequence.ToArray()); + HexWriter.WriteUlongHexRawValue(writer, (ulong)value); } - } - - throw new JsonException(); - } - - [SkipLocalsInit] - public override void Write( - Utf8JsonWriter writer, - long value, - JsonSerializerOptions options) - { - switch (ForcedNumberConversion.GetFinalConversion()) - { - case NumberConversion.Hex: - if (value == 0) - { - writer.WriteRawValue("\"0x0\""u8, skipInputValidation: true); - } - else - { - Span bytes = stackalloc byte[8]; - BinaryPrimitives.WriteInt64BigEndian(bytes, value); - ByteArrayConverter.Convert(writer, bytes, skipLeadingZeros: true); - } - break; - case NumberConversion.Decimal: - writer.WriteStringValue(value == 0 ? "0" : value.ToString(CultureInfo.InvariantCulture)); - break; - case NumberConversion.Raw: - writer.WriteNumberValue(value); - break; - default: - throw new NotSupportedException(); - - } + break; + case NumberConversion.Decimal: + writer.WriteStringValue(value == 0 ? "0" : value.ToString(CultureInfo.InvariantCulture)); + break; + case NumberConversion.Raw: + writer.WriteNumberValue(value); + break; + default: + throw new NotSupportedException(); } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/LongRawJsonConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/LongRawJsonConverter.cs index df81b47e6be5..eea8ecdd8f97 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/LongRawJsonConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/LongRawJsonConverter.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; using System.Text.Json; using System.Text.Json.Serialization; @@ -15,23 +14,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/MemoryByteConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/MemoryByteConverter.cs index 45780816a9b5..b8abc57fd60d 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/MemoryByteConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/MemoryByteConverter.cs @@ -2,10 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; using System.Text.Json; using System.Text.Json.Serialization; -using Nethermind.Core.Extensions; namespace Nethermind.Serialization.Json; @@ -25,17 +23,6 @@ public override void Write(Utf8JsonWriter writer, Memory value, JsonSerial public override Memory Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (reader.TokenType == JsonTokenType.Null) - { - return default; - } - - ReadOnlySpan hex = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; - if (hex.StartsWith("0x"u8)) - { - hex = hex[2..]; - } - - return Bytes.FromUtf8HexString(hex); + return ByteArrayConverter.Convert(ref reader); } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/Nethermind.Serialization.Json.csproj b/src/Nethermind/Nethermind.Serialization.Json/Nethermind.Serialization.Json.csproj index 28c5578b12f6..4bfceeffd188 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/Nethermind.Serialization.Json.csproj +++ b/src/Nethermind/Nethermind.Serialization.Json/Nethermind.Serialization.Json.csproj @@ -4,7 +4,6 @@ - diff --git a/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs index 9d023cdcf66f..0ca57dd9f5c6 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs @@ -2,41 +2,39 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Text.Json; +using System.Text.Json.Serialization; -namespace Nethermind.Serialization.Json +namespace Nethermind.Serialization.Json; + +public class NullableULongConverter : JsonConverter { - using System.Text.Json; - using System.Text.Json.Serialization; + private readonly ULongConverter _converter = new(); - public class NullableULongConverter : JsonConverter + public override ulong? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) { - private readonly ULongConverter _converter = new(); - - public override ulong? Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) + if (reader.TokenType == JsonTokenType.Null) { - if (reader.TokenType == JsonTokenType.Null) - { - return null; - } - - return _converter.Read(ref reader, typeToConvert, options); + return null; } - public override void Write( - Utf8JsonWriter writer, - ulong? value, - JsonSerializerOptions options) - { - if (!value.HasValue) - { - writer.WriteNullValue(); - return; - } + return _converter.Read(ref reader, typeToConvert, options); + } - _converter.Write(writer, value.GetValueOrDefault(), options); + public override void Write( + Utf8JsonWriter writer, + ulong? value, + JsonSerializerOptions options) + { + if (!value.HasValue) + { + writer.WriteNullValue(); + return; } + + _converter.Write(writer, value.GetValueOrDefault(), options); } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/PubSub/LogPublisher.cs b/src/Nethermind/Nethermind.Serialization.Json/PubSub/LogPublisher.cs deleted file mode 100644 index 75a0807cca7d..000000000000 --- a/src/Nethermind/Nethermind.Serialization.Json/PubSub/LogPublisher.cs +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Threading.Tasks; -using Nethermind.Core.PubSub; -using Nethermind.Logging; - -namespace Nethermind.Serialization.Json.PubSub -{ - public class LogPublisher : IPublisher - { - private readonly ILogger _logger; - private readonly IJsonSerializer _jsonSerializer; - - public LogPublisher(IJsonSerializer jsonSerializer, ILogManager logManager) - { - _logger = logManager.GetClassLogger(); - _jsonSerializer = jsonSerializer; - } - - public Task PublishAsync(T data) where T : class - { - if (_logger.IsInfo) _logger.Info(_jsonSerializer.Serialize(data)); - return Task.CompletedTask; - } - - public void Dispose() - { - } - } -} diff --git a/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs b/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs index 336fd38d6d69..611e64a0affc 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs @@ -99,28 +99,16 @@ public static UInt256 ReadHex(ReadOnlySpan hex) return new UInt256(in readOnlyBytes, isBigEndian: true); } - [SkipLocalsInit] public override void Write( Utf8JsonWriter writer, UInt256 value, JsonSerializerOptions options) { - NumberConversion usedConversion = ForcedNumberConversion.GetFinalConversion(); - if (value.IsZero) - { - writer.WriteRawValue(usedConversion == NumberConversion.ZeroPaddedHex - ? "\"0x0000000000000000000000000000000000000000000000000000000000000000\""u8 - : "\"0x0\""u8); - return; - } - switch (usedConversion) + NumberConversion conversion = ForcedNumberConversion.GetFinalConversion(); + switch (conversion) { case NumberConversion.Hex: - { - Span bytes = stackalloc byte[32]; - value.ToBigEndian(bytes); - ByteArrayConverter.Convert(writer, bytes); - } + HexWriter.WriteUInt256HexRawValue(writer, value); break; case NumberConversion.Decimal: writer.WriteRawValue(value.ToString(CultureInfo.InvariantCulture)); @@ -129,35 +117,23 @@ public override void Write( writer.WriteStringValue(((BigInteger)value).ToString(CultureInfo.InvariantCulture)); break; case NumberConversion.ZeroPaddedHex: - { - Span bytes = stackalloc byte[32]; - value.ToBigEndian(bytes); - ByteArrayConverter.Convert(writer, bytes, skipLeadingZeros: false); - } + HexWriter.WriteUInt256HexRawValue(writer, value, zeroPadded: true); break; default: - throw new NotSupportedException($"{usedConversion} format is not supported for {nameof(UInt256)}"); + throw new NotSupportedException($"{conversion} format is not supported for {nameof(UInt256)}"); } } public override UInt256 ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => ReadInternal(ref reader, JsonTokenType.PropertyName); - [SkipLocalsInit] public override void WriteAsPropertyName(Utf8JsonWriter writer, UInt256 value, JsonSerializerOptions options) { - NumberConversion usedConversion = ForcedNumberConversion.GetFinalConversion(); - if (value.IsZero) - { - writer.WritePropertyName(usedConversion == NumberConversion.ZeroPaddedHex - ? "0x0000000000000000000000000000000000000000000000000000000000000000"u8 - : "0x0"u8); - return; - } - switch (usedConversion) + NumberConversion conversion = ForcedNumberConversion.GetFinalConversion(); + switch (conversion) { case NumberConversion.Hex: - WriteHexPropertyName(writer, value, false); + HexWriter.WriteUInt256HexPropertyName(writer, value); break; case NumberConversion.Decimal: writer.WritePropertyName(value.ToString(CultureInfo.InvariantCulture)); @@ -166,25 +142,13 @@ public override void WriteAsPropertyName(Utf8JsonWriter writer, UInt256 value, J writer.WritePropertyName(((BigInteger)value).ToString(CultureInfo.InvariantCulture)); break; case NumberConversion.ZeroPaddedHex: - WriteHexPropertyName(writer, value, true); + HexWriter.WriteUInt256HexPropertyName(writer, value, zeroPadded: true); break; default: - throw new NotSupportedException($"{usedConversion} format is not supported for {nameof(UInt256)}"); + throw new NotSupportedException($"{conversion} format is not supported for {nameof(UInt256)}"); } } - private static void WriteHexPropertyName(Utf8JsonWriter writer, UInt256 value, bool isZeroPadded) - { - Span bytes = stackalloc byte[32]; - value.ToBigEndian(bytes); - ByteArrayConverter.Convert( - writer, - bytes, - static (w, h) => w.WritePropertyName(h), - skipLeadingZeros: !isZeroPadded, - addQuotations: false); - } - [DoesNotReturn, StackTraceHidden] private static void ThrowJsonException() { diff --git a/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs index 08d9afa23f23..d9f672dedf9e 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs @@ -2,104 +2,108 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Text.Json.Serialization; -namespace Nethermind.Serialization.Json -{ - using System.Buffers; - using System.Buffers.Binary; - using System.Buffers.Text; - using System.Globalization; - using System.Runtime.CompilerServices; - using System.Text.Json; - using System.Text.Json.Serialization; +namespace Nethermind.Serialization.Json; - public class ULongConverter : JsonConverter +public class ULongConverter : JsonConverter +{ + public static ulong FromString(ReadOnlySpan s) { - public static ulong FromString(ReadOnlySpan s) + if (s.Length == 0) { - 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)) - { - return 0uL; - } + if (s.SequenceEqual("0x0"u8)) + { + return 0uL; + } - ulong value; - if (s.StartsWith("0x"u8)) - { - s = s[2..]; - if (Utf8Parser.TryParse(s, out value, out _, 'x')) - { - return value; - } - } - else if (Utf8Parser.TryParse(s, out value, out _)) + ulong value; + if (s.StartsWith("0x"u8)) + { + s = s[2..]; + if (Utf8Parser.TryParse(s, out value, out _, 'x')) { return value; } - - throw new JsonException("hex to long"); } - - [SkipLocalsInit] - public override void Write( - Utf8JsonWriter writer, - ulong value, - JsonSerializerOptions options) + else if (Utf8Parser.TryParse(s, out value, out _)) { - NumberConversion usedConversion = ForcedNumberConversion.GetFinalConversion(); - switch (usedConversion) - { - case NumberConversion.Hex: - { - if (value == 0) - { - writer.WriteRawValue("\"0x0\""u8, skipInputValidation: true); - } - else - { - Span bytes = stackalloc byte[8]; - BinaryPrimitives.WriteUInt64BigEndian(bytes, value); - ByteArrayConverter.Convert(writer, bytes, skipLeadingZeros: true); - } - break; - } - case NumberConversion.Decimal: - writer.WriteStringValue(value == 0 ? "0" : value.ToString(CultureInfo.InvariantCulture)); - break; - case NumberConversion.Raw: - writer.WriteNumberValue(value); - break; - default: - throw new NotSupportedException(); - } + return value; } - public override ulong Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) + + ThrowJsonException(); + return default; + + [DoesNotReturn, StackTraceHidden] + static void ThrowJsonException() => throw new JsonException("hex to long"); + } + + [SkipLocalsInit] + public override void Write( + Utf8JsonWriter writer, + ulong value, + JsonSerializerOptions options) + { + NumberConversion usedConversion = ForcedNumberConversion.GetFinalConversion(); + switch (usedConversion) { - if (reader.TokenType == JsonTokenType.Number) - { - return reader.GetUInt64(); - } - if (reader.TokenType == JsonTokenType.String) - { - if (!reader.HasValueSequence) + case NumberConversion.Hex: + if (value == 0) { - return FromString(reader.ValueSpan); + writer.WriteStringValue("0x0"u8); } else { - return FromString(reader.ValueSequence.ToArray()); + HexWriter.WriteUlongHexRawValue(writer, value); } - } + break; + case NumberConversion.Decimal: + writer.WriteStringValue(value == 0 ? "0" : value.ToString(CultureInfo.InvariantCulture)); + break; + case NumberConversion.Raw: + writer.WriteNumberValue(value); + break; + default: + throw new NotSupportedException(); + } + } - throw new JsonException(); + internal static ulong ReadCore(ref Utf8JsonReader reader) + { + if (reader.TokenType == JsonTokenType.Number) + { + return reader.GetUInt64(); } + if (reader.TokenType == JsonTokenType.String) + { + return !reader.HasValueSequence + ? FromString(reader.ValueSpan) + : FromString(reader.ValueSequence.ToArray()); + } + + ThrowJsonException(); + return default; + + [DoesNotReturn, StackTraceHidden] + static void ThrowJsonException() => 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 fa172ac039c9..a6e8ee2c2ae0 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/ULongRawJsonConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ULongRawJsonConverter.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; using System.Text.Json; using System.Text.Json.Serialization; @@ -15,23 +14,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.Core/Crypto/ValueHash256Converter.cs b/src/Nethermind/Nethermind.Serialization.Json/ValueHash256Converter.cs similarity index 64% rename from src/Nethermind/Nethermind.Core/Crypto/ValueHash256Converter.cs rename to src/Nethermind/Nethermind.Serialization.Json/ValueHash256Converter.cs index 0f4f0c65757e..c38cd342cba6 100644 --- a/src/Nethermind/Nethermind.Core/Crypto/ValueHash256Converter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ValueHash256Converter.cs @@ -1,7 +1,9 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +#nullable enable using System; +using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.Json.Serialization; using Nethermind.Core.Crypto; @@ -10,20 +12,28 @@ namespace Nethermind.Serialization.Json; public class ValueHash256Converter : JsonConverter { + private readonly bool _strictHexFormat; + + public ValueHash256Converter(bool strictHexFormat = false) + { + _strictHexFormat = strictHexFormat; + } + public override ValueHash256 Read( ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - byte[]? bytes = ByteArrayConverter.Convert(ref reader); + byte[]? bytes = ByteArrayConverter.Convert(ref reader, _strictHexFormat); return bytes is null ? null : new ValueHash256(bytes); } + [SkipLocalsInit] public override void Write( Utf8JsonWriter writer, ValueHash256 keccak, JsonSerializerOptions options) { - ByteArrayConverter.Convert(writer, keccak.Bytes, skipLeadingZeros: false); + Hash256Converter.WriteHashHex(writer, in keccak); } } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/AccountDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/AccountDecoder.cs index 2e992b2443d2..ab7651dda751 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/AccountDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/AccountDecoder.cs @@ -7,7 +7,7 @@ namespace Nethermind.Serialization.Rlp { - public class AccountDecoder : IRlpObjectDecoder, IRlpStreamDecoder, IRlpValueDecoder + public sealed class AccountDecoder : RlpValueDecoder, IRlpObjectDecoder { private readonly bool _slimFormat; @@ -54,7 +54,7 @@ public Hash256 DecodeStorageRootOnly(ref Rlp.ValueDecoderContext context) return storageRoot; } - public Account? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override Account? DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { int length = rlpStream.ReadSequenceLength(); if (length == 1) @@ -74,7 +74,7 @@ public Hash256 DecodeStorageRootOnly(ref Rlp.ValueDecoderContext context) return new(nonce, balance, storageRoot, codeHash); } - public void Encode(RlpStream stream, Account? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, Account? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) { @@ -144,7 +144,7 @@ public int GetLength(Account[] accounts) return length; } - public int GetLength(Account? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override int GetLength(Account? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) { @@ -234,7 +234,7 @@ private Hash256 DecodeCodeHash(RlpStream rlpStream) return codeHash; } - public Account? Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override Account? DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { int length = decoderContext.ReadSequenceLength(); if (length == 1) diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/BasicStreamDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/BasicStreamDecoder.cs index 106413dbc446..c3cf4d93f67c 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/BasicStreamDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/BasicStreamDecoder.cs @@ -6,109 +6,109 @@ namespace Nethermind.Serialization.Rlp; // If any of these is triggered in prod, then something went wrong, coz these are fairly slow path. These are only // useful for easy tests. -public class ByteStreamDecoder : IRlpStreamDecoder +public sealed class ByteStreamDecoder : RlpStreamDecoder { - public int GetLength(byte item, RlpBehaviors rlpBehaviors) + public override int GetLength(byte item, RlpBehaviors rlpBehaviors) { return Rlp.LengthOf(item); } - public byte Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override byte DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { return rlpStream.DecodeByte(); } - public void Encode(RlpStream stream, byte item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, byte item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { stream.Encode(item); } } -public class ShortStreamDecoder : IRlpStreamDecoder +public sealed class ShortStreamDecoder : RlpStreamDecoder { - public int GetLength(short item, RlpBehaviors rlpBehaviors) + public override int GetLength(short item, RlpBehaviors rlpBehaviors) { return Rlp.LengthOf(item); } - public short Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override short DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { return (short)rlpStream.DecodeLong(); } - public void Encode(RlpStream stream, short item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, short item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { stream.Encode(item); } } -public class UShortStreamDecoder : IRlpStreamDecoder +public sealed class UShortStreamDecoder : RlpStreamDecoder { - public int GetLength(ushort item, RlpBehaviors rlpBehaviors) + public override int GetLength(ushort item, RlpBehaviors rlpBehaviors) { return Rlp.LengthOf((long)item); } - public ushort Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override ushort DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { return (ushort)rlpStream.DecodeLong(); } - public void Encode(RlpStream stream, ushort item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, ushort item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { stream.Encode(item); } } -public class IntStreamDecoder : IRlpStreamDecoder +public sealed class IntStreamDecoder : RlpStreamDecoder { - public int GetLength(int item, RlpBehaviors rlpBehaviors) + public override int GetLength(int item, RlpBehaviors rlpBehaviors) { return Rlp.LengthOf(item); } - public int Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override int DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { return rlpStream.DecodeInt(); } - public void Encode(RlpStream stream, int item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, int item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { stream.Encode(item); } } -public class UIntStreamDecoder : IRlpStreamDecoder +public sealed class UIntStreamDecoder : RlpStreamDecoder { - public int GetLength(uint item, RlpBehaviors rlpBehaviors) + public override int GetLength(uint item, RlpBehaviors rlpBehaviors) { return Rlp.LengthOf((long)item); } - public uint Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override uint DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { return rlpStream.DecodeUInt(); } - public void Encode(RlpStream stream, uint item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, uint item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { stream.Encode(item); } } -public class ULongStreamDecoder : IRlpStreamDecoder +public sealed class ULongStreamDecoder : RlpStreamDecoder { - public int GetLength(ulong item, RlpBehaviors rlpBehaviors) + public override int GetLength(ulong item, RlpBehaviors rlpBehaviors) { return Rlp.LengthOf(item); } - public ulong Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override ulong DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { return rlpStream.DecodeUInt(); } - public void Encode(RlpStream stream, ulong item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, ulong item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { stream.Encode(item); } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/BlockBodyDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/BlockBodyDecoder.cs index f7fc6028159b..b2287601a824 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/BlockBodyDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/BlockBodyDecoder.cs @@ -6,21 +6,22 @@ namespace Nethermind.Serialization.Rlp; -public class BlockBodyDecoder : IRlpValueDecoder, IRlpStreamDecoder +public sealed class BlockBodyDecoder : RlpValueDecoder { private readonly TxDecoder _txDecoder = TxDecoder.Instance; - private readonly HeaderDecoder _headerDecoder = new(); + private readonly IHeaderDecoder _headerDecoder; private readonly WithdrawalDecoder _withdrawalDecoderDecoder = new(); private static BlockBodyDecoder? _instance = null; public static BlockBodyDecoder Instance => _instance ??= new BlockBodyDecoder(); // Cant set to private because of `Rlp.RegisterDecoder`. - public BlockBodyDecoder() + public BlockBodyDecoder(IHeaderDecoder headerDecoder = null) { + _headerDecoder = headerDecoder ?? new HeaderDecoder(); } - public int GetLength(BlockBody item, RlpBehaviors rlpBehaviors) + public override int GetLength(BlockBody item, RlpBehaviors rlpBehaviors) { return Rlp.LengthOfSequence(GetBodyLength(item)); } @@ -79,7 +80,7 @@ private int GetWithdrawalsLength(Withdrawal[] withdrawals) return sum; } - public BlockBody? Decode(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override BlockBody? DecodeInternal(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { int sequenceLength = ctx.ReadSequenceLength(); int startingPosition = ctx.Position; @@ -93,9 +94,6 @@ private int GetWithdrawalsLength(Withdrawal[] withdrawals) public BlockBody? DecodeUnwrapped(ref Rlp.ValueDecoderContext ctx, int lastPosition) { - - // quite significant allocations (>0.5%) here based on a sample 3M blocks sync - // (just on these delegates) Transaction[] transactions = ctx.DecodeArray(_txDecoder); BlockHeader[] uncles = ctx.DecodeArray(_headerDecoder); Withdrawal[]? withdrawals = null; @@ -108,7 +106,7 @@ private int GetWithdrawalsLength(Withdrawal[] withdrawals) return new BlockBody(transactions, uncles, withdrawals); } - public BlockBody Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override BlockBody DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { Span span = rlpStream.PeekNextItem(); Rlp.ValueDecoderContext ctx = new Rlp.ValueDecoderContext(span); @@ -118,7 +116,7 @@ public BlockBody Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBeha return response; } - public void Encode(RlpStream stream, BlockBody body, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, BlockBody body, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { stream.StartSequence(GetBodyLength(body)); stream.StartSequence(GetTxLength(body.Transactions)); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs index 50704bb6b423..90c559c1ab62 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs @@ -7,14 +7,14 @@ namespace Nethermind.Serialization.Rlp { - public class BlockDecoder(IHeaderDecoder headerDecoder) : IRlpValueDecoder, IRlpStreamDecoder + public sealed class BlockDecoder(IHeaderDecoder headerDecoder) : RlpValueDecoder { private readonly IHeaderDecoder _headerDecoder = headerDecoder ?? throw new ArgumentNullException(nameof(headerDecoder)); - private readonly BlockBodyDecoder _blockBodyDecoder = BlockBodyDecoder.Instance; + private readonly BlockBodyDecoder _blockBodyDecoder = new BlockBodyDecoder(headerDecoder); public BlockDecoder() : this(new HeaderDecoder()) { } - public Block? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override Block? DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (rlpStream.Length == 0) { @@ -40,6 +40,12 @@ public BlockDecoder() : this(new HeaderDecoder()) { } (int txs, int uncles, int? withdrawals) = _blockBodyDecoder.GetBodyComponentLength(item.Body); + byte[][]? encodedTxs = item.EncodedTransactions; + if (encodedTxs is not null) + { + txs = GetPreEncodedTxLength(item.Transactions, encodedTxs); + } + int contentLength = headerLength + Rlp.LengthOfSequence(txs) + @@ -48,7 +54,17 @@ public BlockDecoder() : this(new HeaderDecoder()) { } return (contentLength, txs, uncles, withdrawals); } - public int GetLength(Block? item, RlpBehaviors rlpBehaviors) + private static int GetPreEncodedTxLength(Transaction[] txs, byte[][] encodedTxs) + { + int sum = 0; + for (int i = 0; i < encodedTxs.Length; i++) + { + sum += TxDecoder.GetWrappedTxLength(txs[i].Type, encodedTxs[i].Length); + } + return sum; + } + + public override int GetLength(Block? item, RlpBehaviors rlpBehaviors) { if (item is null) { @@ -58,7 +74,7 @@ public int GetLength(Block? item, RlpBehaviors rlpBehaviors) return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors).Total); } - public Block? Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override Block? DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (decoderContext.IsNextItemNull()) { @@ -69,7 +85,7 @@ public int GetLength(Block? item, RlpBehaviors rlpBehaviors) int sequenceLength = decoderContext.ReadSequenceLength(); int blockCheck = decoderContext.Position + sequenceLength; - BlockHeader header = Rlp.Decode(ref decoderContext); + BlockHeader header = _headerDecoder.Decode(ref decoderContext); BlockBody body = _blockBodyDecoder.DecodeUnwrapped(ref decoderContext, blockCheck); Block block = new(header, body) @@ -92,7 +108,7 @@ public Rlp Encode(Block? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) return new(rlpStream.Data.ToArray()); } - public void Encode(RlpStream stream, Block? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, Block? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) { @@ -102,11 +118,23 @@ public void Encode(RlpStream stream, Block? item, RlpBehaviors rlpBehaviors = Rl (int contentLength, int txsLength, int unclesLength, int? withdrawalsLength) = GetContentLength(item, rlpBehaviors); stream.StartSequence(contentLength); - stream.Encode(item.Header); + _headerDecoder.Encode(stream, item.Header); stream.StartSequence(txsLength); - for (int i = 0; i < item.Transactions.Length; i++) + + byte[][]? encodedTxs = item.EncodedTransactions; + if (encodedTxs is not null) { - stream.Encode(item.Transactions[i]); + for (int i = 0; i < encodedTxs.Length; i++) + { + TxDecoder.WriteWrappedFormat(stream, item.Transactions[i].Type, encodedTxs[i]); + } + } + else + { + for (int i = 0; i < item.Transactions.Length; i++) + { + stream.Encode(item.Transactions[i]); + } } stream.StartSequence(unclesLength); @@ -126,7 +154,7 @@ public void Encode(RlpStream stream, Block? item, RlpBehaviors rlpBehaviors = Rl } } - public static ReceiptRecoveryBlock? DecodeToReceiptRecoveryBlock(MemoryManager? memoryManager, Memory memory, RlpBehaviors rlpBehaviors) + public ReceiptRecoveryBlock? DecodeToReceiptRecoveryBlock(MemoryManager? memoryManager, Memory memory, RlpBehaviors rlpBehaviors) { Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(memory, true); @@ -139,7 +167,7 @@ public void Encode(RlpStream stream, Block? item, RlpBehaviors rlpBehaviors = Rl int sequenceLength = decoderContext.ReadSequenceLength(); int blockCheck = decoderContext.Position + sequenceLength; - BlockHeader header = Rlp.Decode(ref decoderContext); + BlockHeader header = _headerDecoder.Decode(ref decoderContext); int contentLength = decoderContext.ReadSequenceLength(); int transactionCount = decoderContext.PeekNumberOfItemsRemaining(decoderContext.Position + contentLength); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/BlockInfoDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/BlockInfoDecoder.cs index 69ee52db06c1..92cbb67ba098 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/BlockInfoDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/BlockInfoDecoder.cs @@ -7,11 +7,11 @@ namespace Nethermind.Serialization.Rlp { - public class BlockInfoDecoder : IRlpStreamDecoder, IRlpValueDecoder + public sealed class BlockInfoDecoder : RlpValueDecoder { public static BlockInfoDecoder Instance { get; } = new(); - public BlockInfo? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override BlockInfo? DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (rlpStream.IsNextItemNull()) { @@ -52,7 +52,7 @@ public class BlockInfoDecoder : IRlpStreamDecoder, IRlpValueDecoder, IRlpValueDecoder + public sealed class ChainLevelDecoder : RlpValueDecoder { - public ChainLevelInfo? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override ChainLevelInfo? DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (rlpStream.Length == 0) { @@ -50,7 +49,7 @@ public class ChainLevelDecoder : IRlpStreamDecoder, IRlpValueDec return info; } - public void Encode(RlpStream stream, ChainLevelInfo? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, ChainLevelInfo? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) { @@ -78,7 +77,7 @@ static void ThrowHasNull() => throw new InvalidOperationException($"{nameof(BlockInfo)} is null when encoding {nameof(ChainLevelInfo)}"); } - public ChainLevelInfo? Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override ChainLevelInfo? DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (decoderContext.IsNextItemNull()) { @@ -127,7 +126,7 @@ private static int GetContentLength(ChainLevelInfo item, RlpBehaviors rlpBehavio return contentLength; } - public int GetLength(ChainLevelInfo? item, RlpBehaviors rlpBehaviors) + public override int GetLength(ChainLevelInfo? item, RlpBehaviors rlpBehaviors) { if (item is null) { diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/CompactLogEntryDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/CompactLogEntryDecoder.cs index 2ce1c1348c74..c0fe0af457ef 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/CompactLogEntryDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/CompactLogEntryDecoder.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Linq; using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; @@ -27,7 +26,7 @@ public class CompactLogEntryDecoder : IRlpDecoder Address? address = rlpStream.DecodeAddress(); long sequenceLength = rlpStream.ReadSequenceLength(); long untilPosition = rlpStream.Position + sequenceLength; - using ArrayPoolList topics = new((int)(sequenceLength * 2 / Rlp.LengthOfKeccakRlp)); + using ArrayPoolListRef topics = new((int)(sequenceLength * 2 / Rlp.LengthOfKeccakRlp)); while (rlpStream.Position < untilPosition) { topics.Add(rlpStream.DecodeZeroPrefixKeccak()); @@ -53,7 +52,7 @@ public class CompactLogEntryDecoder : IRlpDecoder Address? address = decoderContext.DecodeAddress(); long sequenceLength = decoderContext.ReadSequenceLength(); long untilPosition = decoderContext.Position + sequenceLength; - using ArrayPoolList topics = new((int)(sequenceLength * 2 / Rlp.LengthOfKeccakRlp)); + using ArrayPoolListRef topics = new((int)(sequenceLength * 2 / Rlp.LengthOfKeccakRlp)); while (decoderContext.Position < untilPosition) { topics.Add(decoderContext.DecodeZeroPrefixKeccak()); @@ -95,7 +94,7 @@ public static Hash256[] DecodeTopics(Rlp.ValueDecoderContext valueDecoderContext { long sequenceLength = valueDecoderContext.ReadSequenceLength(); long untilPosition = valueDecoderContext.Position + sequenceLength; - using ArrayPoolList topics = new((int)(sequenceLength * 2 / Rlp.LengthOfKeccakRlp)); + using ArrayPoolListRef topics = new((int)(sequenceLength * 2 / Rlp.LengthOfKeccakRlp)); while (valueDecoderContext.Position < untilPosition) { topics.Add(valueDecoderContext.DecodeZeroPrefixKeccak()); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/CompactReceiptStorageDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/CompactReceiptStorageDecoder.cs index 80ef6e8681ea..6508c53148fd 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/CompactReceiptStorageDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/CompactReceiptStorageDecoder.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Linq; using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; @@ -13,7 +12,7 @@ namespace Nethermind.Serialization.Rlp { [Decoder(RlpDecoderKey.Storage)] - public class CompactReceiptStorageDecoder : IRlpStreamDecoder, IRlpValueDecoder, IRlpObjectDecoder, IReceiptRefDecoder + public sealed class CompactReceiptStorageDecoder : RlpValueDecoder, IRlpObjectDecoder, IReceiptRefDecoder { public static readonly CompactReceiptStorageDecoder Instance = new(); @@ -21,7 +20,7 @@ public CompactReceiptStorageDecoder() { } - public TxReceipt? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override TxReceipt? DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (rlpStream.IsNextItemNull()) { @@ -47,7 +46,7 @@ public CompactReceiptStorageDecoder() int sequenceLength = rlpStream.ReadSequenceLength(); int lastCheck = sequenceLength + rlpStream.Position; - using ArrayPoolList logEntries = new(sequenceLength * 2 / Rlp.LengthOfAddressRlp); + using ArrayPoolListRef logEntries = new(sequenceLength * 2 / Rlp.LengthOfAddressRlp); while (rlpStream.Position < lastCheck) { @@ -67,7 +66,7 @@ public CompactReceiptStorageDecoder() return txReceipt; } - public TxReceipt? Decode(ref Rlp.ValueDecoderContext decoderContext, + protected override TxReceipt? DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (decoderContext.IsNextItemNull()) @@ -96,7 +95,7 @@ public CompactReceiptStorageDecoder() int lastCheck = sequenceLength + decoderContext.Position; // Don't know the size exactly, I'll just assume its just an address and add some margin - using ArrayPoolList logEntries = new(sequenceLength * 2 / Rlp.LengthOfAddressRlp); + using ArrayPoolListRef logEntries = new(sequenceLength * 2 / Rlp.LengthOfAddressRlp); while (decoderContext.Position < lastCheck) { logEntries.Add(CompactLogEntryDecoder.Decode(ref decoderContext, RlpBehaviors.AllowExtraBytes)); @@ -129,7 +128,7 @@ public void DecodeStructRef(scoped ref Rlp.ValueDecoderContext decoderContext, R decoderContext.SkipLength(); - ReadOnlySpan firstItem = decoderContext.DecodeByteArraySpan(); + ReadOnlySpan firstItem = decoderContext.DecodeByteArraySpan(RlpLimit.L32); if (firstItem.Length == 1) { item.StatusCode = firstItem[0]; @@ -171,7 +170,7 @@ public Rlp Encode(TxReceipt item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) return new Rlp(rlpStream.Data.ToArray()); } - public void Encode(RlpStream rlpStream, TxReceipt? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream rlpStream, TxReceipt? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) { @@ -245,7 +244,7 @@ private static int GetLogsLength(TxReceipt item) return logsLength; } - public int GetLength(TxReceipt item, RlpBehaviors rlpBehaviors) + public override int GetLength(TxReceipt item, RlpBehaviors rlpBehaviors) { (int Total, _) = GetContentLength(item, rlpBehaviors); return Rlp.LengthOfSequence(Total); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Eip2930/AccessListDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Eip2930/AccessListDecoder.cs index e61b35690cca..2556b482e275 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/Eip2930/AccessListDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip2930/AccessListDecoder.cs @@ -7,7 +7,7 @@ namespace Nethermind.Serialization.Rlp.Eip2930 { - public class AccessListDecoder : IRlpStreamDecoder, IRlpValueDecoder + public sealed class AccessListDecoder : RlpValueDecoder { private const int IndexLength = 32; @@ -20,7 +20,7 @@ public class AccessListDecoder : IRlpStreamDecoder, IRlpValueDecode /// RLP serializable item and keep it as a compiled call available at runtime. /// It would be slightly slower but still much faster than what we would get from using dynamic serializers. /// - public AccessList? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override AccessList? DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (rlpStream.IsNextItemNull()) { @@ -78,7 +78,7 @@ public class AccessListDecoder : IRlpStreamDecoder, IRlpValueDecode /// Question to Lukasz here -> would it be fine to always use ValueDecoderContext only? /// I believe it cannot be done for the network items decoding and is only relevant for the DB loads. /// - public AccessList? Decode( + protected override AccessList? DecodeInternal( ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { @@ -132,7 +132,7 @@ public class AccessListDecoder : IRlpStreamDecoder, IRlpValueDecode return accessListBuilder.Build(); } - public void Encode(RlpStream stream, AccessList? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, AccessList? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) { @@ -166,7 +166,7 @@ public void Encode(RlpStream stream, AccessList? item, RlpBehaviors rlpBehaviors } } - public int GetLength(AccessList? accessList, RlpBehaviors rlpBehaviors) + public override int GetLength(AccessList? accessList, RlpBehaviors rlpBehaviors) { if (accessList is null) { diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Eip7702/AuthorizationTupleDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7702/AuthorizationTupleDecoder.cs index 9557c6f29354..9ab45209ba1c 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/Eip7702/AuthorizationTupleDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7702/AuthorizationTupleDecoder.cs @@ -9,11 +9,11 @@ namespace Nethermind.Serialization.Rlp; -public class AuthorizationTupleDecoder : IRlpStreamDecoder, IRlpValueDecoder +public sealed class AuthorizationTupleDecoder : RlpValueDecoder { public static readonly AuthorizationTupleDecoder Instance = new(); - public AuthorizationTuple Decode(RlpStream stream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override AuthorizationTuple DecodeInternal(RlpStream stream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { int length = stream.ReadSequenceLength(); int check = length + stream.Position; @@ -37,7 +37,7 @@ public AuthorizationTuple Decode(RlpStream stream, RlpBehaviors rlpBehaviors = R return new AuthorizationTuple(chainId, codeAddress, nonce, yParity, r, s); } - public AuthorizationTuple Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override AuthorizationTuple DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { int length = decoderContext.ReadSequenceLength(); int check = length + decoderContext.Position; @@ -68,7 +68,7 @@ public RlpStream Encode(AuthorizationTuple item, RlpBehaviors rlpBehaviors = Rlp return stream; } - public void Encode(RlpStream stream, AuthorizationTuple item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, AuthorizationTuple item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { int contentLength = GetContentLength(item); stream.StartSequence(contentLength); @@ -89,7 +89,7 @@ public static void EncodeWithoutSignature(RlpStream stream, UInt256 chainId, Add stream.Encode(nonce); } - public int GetLength(AuthorizationTuple item, RlpBehaviors rlpBehaviors) => Rlp.LengthOfSequence(GetContentLength(item)); + public override int GetLength(AuthorizationTuple item, RlpBehaviors rlpBehaviors) => Rlp.LengthOfSequence(GetContentLength(item)); private static int GetContentLength(AuthorizationTuple tuple) => GetContentLengthWithoutSig(tuple.ChainId, tuple.CodeAddress, tuple.Nonce) diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs index e4e824bfddf7..6a7164d46b92 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs @@ -11,11 +11,11 @@ namespace Nethermind.Serialization.Rlp public interface IHeaderDecoder : IBlockHeaderDecoder { } public interface IBlockHeaderDecoder : IRlpValueDecoder, IRlpStreamDecoder where T : BlockHeader { } - public class HeaderDecoder : IHeaderDecoder + public sealed class HeaderDecoder : RlpValueDecoder, IHeaderDecoder { public const int NonceLength = 8; - public BlockHeader? Decode(ref Rlp.ValueDecoderContext decoderContext, + protected override BlockHeader? DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (decoderContext.IsNextItemNull()) @@ -85,7 +85,7 @@ public class HeaderDecoder : IHeaderDecoder return blockHeader; } - public BlockHeader? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override BlockHeader? DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (rlpStream.IsNextItemNull()) { @@ -155,7 +155,7 @@ public class HeaderDecoder : IHeaderDecoder return blockHeader; } - public void Encode(RlpStream rlpStream, BlockHeader? header, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream rlpStream, BlockHeader? header, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (header is null) { @@ -289,7 +289,7 @@ private static int GetContentLength(BlockHeader? item, RlpBehaviors rlpBehaviors return contentLength; } - public int GetLength(BlockHeader? item, RlpBehaviors rlpBehaviors) + public override int GetLength(BlockHeader? item, RlpBehaviors rlpBehaviors) { return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/IRlpDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/IRlpDecoder.cs index 4895b9e46d27..d8c508e93769 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/IRlpDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/IRlpDecoder.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace Nethermind.Serialization.Rlp { @@ -11,7 +13,7 @@ public interface IRlpDecoder public interface IRlpDecoder : IRlpDecoder { - int GetLength(T item, RlpBehaviors rlpBehaviors); + int GetLength(T item, RlpBehaviors rlpBehaviors = RlpBehaviors.None); } public interface IRlpStreamDecoder : IRlpDecoder @@ -38,4 +40,51 @@ public static T Decode(this IRlpValueDecoder decoder, ReadOnlySpan b return decoder.Decode(ref context, rlpBehaviors); } } + + public abstract class RlpStreamDecoder : IRlpStreamDecoder + { + public abstract int GetLength(T item, RlpBehaviors rlpBehaviors = RlpBehaviors.None); + + public T Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + try + { + return DecodeInternal(rlpStream, rlpBehaviors); + } + catch (Exception e) when (e is IndexOutOfRangeException or ArgumentOutOfRangeException) + { + ThrowRlpException(e); + return default; + } + } + + [DoesNotReturn] + [StackTraceHidden] + protected static void ThrowRlpException(Exception exception) + { + throw new RlpException($"Cannot decode stream of {nameof(T)}", exception); + } + + protected abstract T DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None); + + public abstract void Encode(RlpStream stream, T item, RlpBehaviors rlpBehaviors = RlpBehaviors.None); + } + + public abstract class RlpValueDecoder : RlpStreamDecoder, IRlpValueDecoder + { + public T Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + try + { + return DecodeInternal(ref decoderContext, rlpBehaviors); + } + catch (Exception e) when (e is IndexOutOfRangeException or ArgumentOutOfRangeException) + { + ThrowRlpException(e); + return default; + } + } + + protected abstract T DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None); + } } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/KeccakDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/KeccakDecoder.cs index 89dd7e564860..90d78edc2f3b 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/KeccakDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/KeccakDecoder.cs @@ -5,14 +5,19 @@ namespace Nethermind.Serialization.Rlp { - public class KeccakDecoder : IRlpValueDecoder + public sealed class KeccakDecoder : RlpValueDecoder { public static readonly KeccakDecoder Instance = new(); - public Hash256? Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) => decoderContext.DecodeKeccak(); + protected override Hash256? DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) => decoderContext.DecodeKeccak(); - public static Rlp Encode(Hash256 item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) => Rlp.Encode(item); + protected override Hash256? DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) => rlpStream.DecodeKeccak(); - public int GetLength(Hash256 item, RlpBehaviors rlpBehaviors) => Rlp.LengthOf(item); + public override int GetLength(Hash256 item, RlpBehaviors rlpBehaviors) => Rlp.LengthOf(item); + + public override void Encode(RlpStream stream, Hash256 item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + stream.Encode(item); + } } } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/LogEntryDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/LogEntryDecoder.cs index c2bfe06fcd90..966b5f9efdb1 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/LogEntryDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/LogEntryDecoder.cs @@ -1,16 +1,17 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using Nethermind.Core; using Nethermind.Core.Crypto; namespace Nethermind.Serialization.Rlp { - public class LogEntryDecoder : IRlpStreamDecoder, IRlpValueDecoder + public sealed class LogEntryDecoder : RlpValueDecoder { public static LogEntryDecoder Instance { get; } = new(); - public LogEntry? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override LogEntry? DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (rlpStream.IsNextItemNull()) { @@ -32,7 +33,7 @@ public class LogEntryDecoder : IRlpStreamDecoder, IRlpValueDecoder topics = decoderContext.Data.Slice(decoderContext.Position, sequenceLength); decoderContext.SkipItem(); - var data = decoderContext.DecodeByteArraySpan(); + ReadOnlySpan data = decoderContext.DecodeByteArraySpan(); item = new LogEntryStructRef(address, data, topics); } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/NethermindBuffers.cs b/src/Nethermind/Nethermind.Serialization.Rlp/NethermindBuffers.cs index 1b8df5ce7a6d..2f9c0b875574 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/OwnedBlockBodies.cs b/src/Nethermind/Nethermind.Serialization.Rlp/OwnedBlockBodies.cs index 4a96cb2f51cb..5923d48c8552 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/OwnedBlockBodies.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/OwnedBlockBodies.cs @@ -5,8 +5,6 @@ using System.Buffers; using System.Collections; using System.Collections.Generic; -using System.Runtime.InteropServices; - using Nethermind.Core.Crypto; using Nethermind.Serialization.Rlp; diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptArrayStorageDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptArrayStorageDecoder.cs index fa1d5f452774..b044b4b37882 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptArrayStorageDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptArrayStorageDecoder.cs @@ -8,7 +8,7 @@ namespace Nethermind.Serialization.Rlp; [Rlp.SkipGlobalRegistration] -public class ReceiptArrayStorageDecoder(bool compactEncoding = true) : IRlpStreamDecoder +public sealed class ReceiptArrayStorageDecoder(bool compactEncoding = true) : RlpStreamDecoder { public static readonly ReceiptArrayStorageDecoder Instance = new(); @@ -20,7 +20,7 @@ public class ReceiptArrayStorageDecoder(bool compactEncoding = true) : IRlpStrea public const int CompactEncoding = 127; - public int GetLength(TxReceipt[] items, RlpBehaviors rlpBehaviors) + public override int GetLength(TxReceipt[] items, RlpBehaviors rlpBehaviors) { if (items is null || items.Length == 0) { @@ -59,12 +59,12 @@ private int GetContentLength(TxReceipt[] items, RlpBehaviors rlpBehaviors) } } - public TxReceipt[] Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override TxReceipt[] DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (rlpStream.PeekByte() == CompactEncoding) { rlpStream.ReadByte(); - return CompactDecoder.DecodeArray(rlpStream, RlpBehaviors.Storage); + return CompactDecoder.DecodeArray(rlpStream, RlpBehaviors.Storage | RlpBehaviors.AllowExtraBytes); } else { @@ -72,7 +72,7 @@ public TxReceipt[] Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBe } } - public void Encode(RlpStream stream, TxReceipt[] items, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, TxReceipt[] items, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (items is null || items.Length == 0) { @@ -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 c6330d11746e..7106903da147 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptMessageDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptMessageDecoder.cs @@ -10,7 +10,7 @@ namespace Nethermind.Serialization.Rlp { [Rlp.Decoder(RlpDecoderKey.Default)] [Rlp.Decoder(RlpDecoderKey.Trie)] - public class ReceiptMessageDecoder : IRlpStreamDecoder, IRlpValueDecoder + public sealed class ReceiptMessageDecoder : RlpValueDecoder { private readonly bool _skipStateAndStatus; @@ -18,7 +18,7 @@ public ReceiptMessageDecoder(bool skipStateAndStatus = false) { _skipStateAndStatus = skipStateAndStatus; } - public TxReceipt Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override TxReceipt DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { Span span = rlpStream.PeekNextItem(); Rlp.ValueDecoderContext ctx = new Rlp.ValueDecoderContext(span); @@ -28,7 +28,7 @@ public TxReceipt Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBeha return response; } - public TxReceipt Decode(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override TxReceipt DecodeInternal(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (ctx.IsNextItemNull()) { @@ -72,6 +72,11 @@ public TxReceipt Decode(ref Rlp.ValueDecoderContext ctx, RlpBehaviors rlpBehavio } txReceipt.Logs = entries; + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + ctx.Check(lastCheck); + } + return txReceipt; } @@ -115,7 +120,7 @@ private static int GetLogsLength(TxReceipt item) /// /// https://eips.ethereum.org/EIPS/eip-2718 /// - public int GetLength(TxReceipt item, RlpBehaviors rlpBehaviors) + public override int GetLength(TxReceipt item, RlpBehaviors rlpBehaviors) { (int Total, _) = GetContentLength(item, rlpBehaviors); int receiptPayloadLength = Rlp.LengthOfSequence(Total); @@ -142,7 +147,7 @@ public byte[] EncodeNew(TxReceipt? item, RlpBehaviors rlpBehaviors = RlpBehavior return stream.Data.ToArray(); } - public void Encode(RlpStream rlpStream, TxReceipt item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream rlpStream, TxReceipt item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) { diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptStorageDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptStorageDecoder.cs index 655dadabfcf8..d6673d1a089b 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptStorageDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptStorageDecoder.cs @@ -11,7 +11,7 @@ namespace Nethermind.Serialization.Rlp { [Rlp.Decoder(RlpDecoderKey.LegacyStorage)] - public class ReceiptStorageDecoder : IRlpStreamDecoder, IRlpValueDecoder, IRlpObjectDecoder, IReceiptRefDecoder + public sealed class ReceiptStorageDecoder : RlpValueDecoder, IRlpObjectDecoder, IReceiptRefDecoder { private readonly bool _supportTxHash; private const byte MarkTxHashByte = 255; @@ -26,7 +26,7 @@ public ReceiptStorageDecoder() : this(true) { } - public TxReceipt? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override TxReceipt? DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (rlpStream.IsNextItemNull()) { @@ -100,7 +100,7 @@ public ReceiptStorageDecoder() : this(true) return txReceipt; } - public TxReceipt? Decode(ref Rlp.ValueDecoderContext decoderContext, + protected override TxReceipt? DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (decoderContext.IsNextItemNull()) @@ -182,7 +182,7 @@ public Rlp Encode(TxReceipt item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) return new Rlp(rlpStream.Data.ToArray()); } - public void Encode(RlpStream rlpStream, TxReceipt? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream rlpStream, TxReceipt? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) { @@ -319,7 +319,7 @@ private static int GetLogsLength(TxReceipt item) /// /// https://eips.ethereum.org/EIPS/eip-2718 /// - public int GetLength(TxReceipt item, RlpBehaviors rlpBehaviors) + public override int GetLength(TxReceipt item, RlpBehaviors rlpBehaviors) { (int Total, _) = GetContentLength(item, rlpBehaviors); int receiptPayloadLength = Rlp.LengthOfSequence(Total); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs index 9344ce4f6086..c523cc2555e3 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs @@ -18,6 +18,7 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Int256; +using Nethermind.Logging; namespace Nethermind.Serialization.Rlp { @@ -200,39 +201,49 @@ public static T Decode(Span bytes, RlpBehaviors rlpBehaviors = RlpBehav return Decode(ref valueContext, rlpBehaviors); } - public static T[] DecodeArray(RlpStream rlpStream, IRlpStreamDecoder? rlpDecoder, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public static T[] DecodeArray(RlpStream rlpStream, IRlpStreamDecoder? rlpDecoder, RlpBehaviors rlpBehaviors = RlpBehaviors.None, RlpLimit? limit = null) { int checkPosition = rlpStream.ReadSequenceLength() + rlpStream.Position; - T[] result = new T[rlpStream.PeekNumberOfItemsRemaining(checkPosition)]; + int length = rlpStream.PeekNumberOfItemsRemaining(checkPosition); + rlpStream.GuardLimit(length, limit); + T[] result = new T[length]; for (int i = 0; i < result.Length; i++) { result[i] = rlpDecoder.Decode(rlpStream, rlpBehaviors); } + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + rlpStream.Check(checkPosition); + } + return result; } - public static ArrayPoolList DecodeArrayPool(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public static ArrayPoolList DecodeArrayPool(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None, RlpLimit? limit = null) { IRlpStreamDecoder? rlpDecoder = GetStreamDecoder(); - if (rlpDecoder is not null) - { - return DecodeArrayPool(rlpStream, rlpDecoder, rlpBehaviors); - } - - throw new RlpException($"{nameof(Rlp)} does not support decoding {typeof(T).Name}"); + return rlpDecoder is not null + ? DecodeArrayPool(rlpStream, rlpDecoder, rlpBehaviors, limit) + : throw new RlpException($"{nameof(Rlp)} does not support decoding {typeof(T).Name}"); } - public static ArrayPoolList DecodeArrayPool(RlpStream rlpStream, IRlpStreamDecoder? rlpDecoder, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + 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); - ArrayPoolList result = new ArrayPoolList(length); + rlpStream.GuardLimit(length, limit); + ArrayPoolList result = new(length); for (int i = 0; i < length; i++) { result.Add(rlpDecoder.Decode(rlpStream, rlpBehaviors)); } + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + rlpStream.Check(checkPosition); + } + return result; } @@ -317,8 +328,7 @@ public static Rlp Encode(T item, RlpBehaviors behaviors = RlpBehaviors.None) { if (item is Rlp rlp) { - RlpStream stream = new(LengthOfSequence(rlp.Length)); - return new(stream.Data.ToArray()); + return rlp; } IRlpStreamDecoder? rlpStreamDecoder = GetStreamDecoder(); @@ -1018,7 +1028,7 @@ public DecodeKeccakRlpException(in int prefix, in int position, in int dataLengt return null; } - ReadOnlySpan theSpan = DecodeByteArraySpan(); + ReadOnlySpan theSpan = DecodeByteArraySpan(RlpLimit.L32); byte[] keccakByte = new byte[32]; theSpan.CopyTo(keccakByte.AsSpan(32 - theSpan.Length)); return new Hash256(keccakByte); @@ -1087,7 +1097,7 @@ public void DecodeZeroPrefixedKeccakStructRef(out Hash256StructRef keccak, Span< } else { - ReadOnlySpan theSpan = DecodeByteArraySpan(); + ReadOnlySpan theSpan = DecodeByteArraySpan(RlpLimit.L32); if (theSpan.Length < 32) { buffer[..(32 - theSpan.Length)].Clear(); @@ -1132,7 +1142,7 @@ public void DecodeAddressStructRef(out AddressStructRef address) public UInt256 DecodeUInt256(int length = -1) { - ReadOnlySpan byteSpan = DecodeByteArraySpan(); + ReadOnlySpan byteSpan = DecodeByteArraySpan(RlpLimit.L32); if (byteSpan.Length > 32) { RlpHelpers.ThrowUnexpectedIntegerLength(byteSpan.Length); @@ -1156,7 +1166,7 @@ public UInt256 DecodeUInt256(int length = -1) public BigInteger DecodeUBigInt() { - ReadOnlySpan bytes = DecodeByteArraySpan(); + ReadOnlySpan bytes = DecodeByteArraySpan(RlpLimit.L32); if (bytes.Length > 1 && bytes[0] == 0) { RlpHelpers.ThrowNonCanonicalInteger(Position); @@ -1173,18 +1183,18 @@ public BigInteger DecodeUBigInt() if (Data[Position] == 249) { Position += 5; // tks: skip 249 1 2 129 127 and read 256 bytes - bloomBytes = Read(256); + bloomBytes = Read(Bloom.ByteLength); } else { - bloomBytes = DecodeByteArraySpan(); + bloomBytes = DecodeByteArraySpan(RlpLimit.Bloom); if (bloomBytes.Length == 0) { return null; } } - if (bloomBytes.Length != 256) + if (bloomBytes.Length != Bloom.ByteLength) { throw new InvalidOperationException("Incorrect bloom RLP"); } @@ -1201,11 +1211,11 @@ public void DecodeBloomStructRef(out BloomStructRef bloom) if (Data[Position] == 249) { Position += 5; // tks: skip 249 1 2 129 127 and read 256 bytes - bloomBytes = Read(256); + bloomBytes = Read(Bloom.ByteLength); } else { - bloomBytes = DecodeByteArraySpan(); + bloomBytes = DecodeByteArraySpan(RlpLimit.Bloom); if (bloomBytes.Length == 0) { bloom = new BloomStructRef(Bloom.Empty.Bytes); @@ -1213,7 +1223,7 @@ public void DecodeBloomStructRef(out BloomStructRef bloom) } } - if (bloomBytes.Length != 256) + if (bloomBytes.Length != Bloom.ByteLength) { throw new InvalidOperationException("Incorrect bloom RLP"); } @@ -1268,9 +1278,9 @@ public int DecodeInt() return result; } - public byte[] DecodeByteArray() => ByteSpanToArray(DecodeByteArraySpan()); + public byte[] DecodeByteArray(RlpLimit? limit = null) => ByteSpanToArray(DecodeByteArraySpan(limit)); - public ReadOnlySpan DecodeByteArraySpan() + public ReadOnlySpan DecodeByteArraySpan(RlpLimit? limit = null) { int prefix = ReadByte(); ReadOnlySpan span = RlpStream.SingleBytes; @@ -1287,6 +1297,7 @@ public ReadOnlySpan DecodeByteArraySpan() if (prefix <= 183) { int length = prefix - 128; + GuardLimit(length, limit); ReadOnlySpan buffer = Read(length); if (length == 1 && buffer[0] < 128) { @@ -1296,11 +1307,11 @@ public ReadOnlySpan DecodeByteArraySpan() return buffer; } - return DecodeLargerByteArraySpan(prefix); + return DecodeLargerByteArraySpan(prefix, limit); } [MethodImpl(MethodImplOptions.NoInlining)] - private ReadOnlySpan DecodeLargerByteArraySpan(int prefix) + private ReadOnlySpan DecodeLargerByteArraySpan(int prefix, RlpLimit? limit = null) { if (prefix < 192) { @@ -1315,6 +1326,7 @@ private ReadOnlySpan DecodeLargerByteArraySpan(int prefix) { RlpHelpers.ThrowUnexpectedLength(length); } + GuardLimit(length, limit); return Read(length); } @@ -1323,11 +1335,11 @@ private ReadOnlySpan DecodeLargerByteArraySpan(int prefix) return default; } - public Memory DecodeByteArrayMemory() + public Memory DecodeByteArrayMemory(RlpLimit? limit = null) { if (!_sliceMemory) { - return DecodeByteArraySpan().ToArray(); + return DecodeByteArraySpan(limit).ToArray(); } if (Memory is null) @@ -1368,6 +1380,7 @@ public Memory DecodeByteArrayMemory() { RlpHelpers.ThrowUnexpectedLength(length); } + GuardLimit(length, limit); return ReadSlicedMemory(length); } @@ -1381,6 +1394,7 @@ static void ThrowNotMemoryBacked() { throw new RlpException("Rlp not backed by a Memory"); } + } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1439,9 +1453,9 @@ public bool DecodeBool() private void SkipBytes(int length) => Position += length; - public string DecodeString() + public string DecodeString(RlpLimit? limit = null) { - ReadOnlySpan bytes = DecodeByteArraySpan(); + ReadOnlySpan bytes = DecodeByteArraySpan(limit); return Encoding.UTF8.GetString(bytes); } @@ -1531,7 +1545,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++) @@ -1539,6 +1554,8 @@ public byte[][] DecodeByteArrays() result[i] = DecodeByteArray(); } + Check(checkPosition); + return result; } @@ -1567,8 +1584,7 @@ public byte DecodeByte() return default; } - public T[] DecodeArray(IRlpValueDecoder? decoder = null, bool checkPositions = true, - T defaultElement = default) + public T[] DecodeArray(IRlpValueDecoder? decoder = null, bool checkPositions = true, T defaultElement = default, RlpLimit? limit = null) { if (decoder is null) { @@ -1580,10 +1596,11 @@ public T[] DecodeArray(IRlpValueDecoder? decoder = null, bool checkPositio } int positionCheck = ReadSequenceLength() + Position; int count = PeekNumberOfItemsRemaining(checkPositions ? positionCheck : null); + GuardLimit(count, limit); T[] result = new T[count]; for (int i = 0; i < result.Length; i++) { - if (PeekByte() == Rlp.OfEmptySequence[0]) + if (PeekByte() == OfEmptySequence[0]) { result[i] = defaultElement; Position++; @@ -1594,6 +1611,11 @@ public T[] DecodeArray(IRlpValueDecoder? decoder = null, bool checkPositio } } + if (checkPositions) + { + Check(positionCheck); + } + return result; } @@ -1602,6 +1624,10 @@ public T[] DecodeArray(IRlpValueDecoder? decoder = null, bool checkPositio [DoesNotReturn, StackTraceHidden] private readonly void ThrowKeccakDecodeException(int prefix) => throw new DecodeKeccakRlpException(prefix, Position, Data.Length); + + [StackTraceHidden] + public void GuardLimit(int count, RlpLimit? limit = null) => + Rlp.GuardLimit(count, Length - Position, limit); } public override bool Equals(object? other) => Equals(other as Rlp); @@ -1762,15 +1788,7 @@ public static int LengthOf(IReadOnlyList array) return LengthOfByteString(array.Count, array[0]); } - public static int LengthOf(ReadOnlySpan array) - { - if (array.Length == 0) - { - return 1; - } - - return LengthOfByteString(array.Length, array[0]); - } + public static int LengthOf(ReadOnlySpan array) => array.Length == 0 ? 1 : LengthOfByteString(array.Length, array[0]); // Assumes that length is greater then 0 public static int LengthOfByteString(int length, byte firstByte) @@ -1824,9 +1842,7 @@ public static int LengthOf(string value) public static int LengthOf(BlockInfo item) => BlockInfoDecoder.Instance.GetLength(item, RlpBehaviors.None); [AttributeUsage(AttributeTargets.Class)] - public class SkipGlobalRegistration : Attribute - { - } + public class SkipGlobalRegistration : Attribute; /// /// Optional attribute for RLP decoders. @@ -1837,6 +1853,29 @@ public sealed class DecoderAttribute(string key = RlpDecoderKey.Default) : Attri { public string Key { get; } = key; } + + private static ILogger _logger = Static.LogManager.GetClassLogger(); + + [StackTraceHidden] + public static void GuardLimit(int count, int bytesLeft, RlpLimit? limit = null) + { + RlpLimit l = limit ?? RlpLimit.DefaultLimit; + if (count > bytesLeft || count > l.Limit) + { + ThrowCountOverLimit(count, bytesLeft, l); + } + } + + [DoesNotReturn] + [StackTraceHidden] + private static void ThrowCountOverLimit(int count, int bytesLeft, RlpLimit limit) + { + string message = string.IsNullOrEmpty(limit.CollectionExpression) + ? $"Collection count of {count} is over limit {limit.Limit} or {bytesLeft} bytes left" + : $"Collection count {limit.CollectionExpression} of {count} is over limit {limit.Limit} or {bytesLeft} bytes left"; + if (_logger.IsDebug) _logger.Error($"DEBUG/ERROR: {message}; {new StackTrace()}"); + throw new RlpLimitException(message); + } } public readonly struct RlpDecoderKey(Type type, string key = RlpDecoderKey.Default) : IEquatable diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpDecoderExtensions.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpDecoderExtensions.cs index 8c3183b911e4..16202a6baf7d 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/RlpDecoderExtensions.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpDecoderExtensions.cs @@ -6,34 +6,49 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Nethermind.Core.Buffers; +using Nethermind.Core.Collections; namespace Nethermind.Serialization.Rlp { public static class RlpDecoderExtensions { - private readonly static SpanSource[] s_intPreEncodes = CreatePreEncodes(); + private static readonly SpanSource[] s_intPreEncodes = CreatePreEncodes(); - public static T[] DecodeArray(this IRlpStreamDecoder decoder, RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public static T[] DecodeArray(this IRlpStreamDecoder decoder, RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None, RlpLimit? limit = null) { int checkPosition = rlpStream.ReadSequenceLength() + rlpStream.Position; - T[] result = new T[rlpStream.PeekNumberOfItemsRemaining(checkPosition)]; + int length = rlpStream.PeekNumberOfItemsRemaining(checkPosition); + rlpStream.GuardLimit(length, limit); + T[] result = new T[length]; for (int i = 0; i < result.Length; i++) { result[i] = decoder.Decode(rlpStream, rlpBehaviors); } + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + rlpStream.Check(checkPosition); + } + return result; } - public static T[] DecodeArray(this IRlpValueDecoder decoder, ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public static T[] DecodeArray(this IRlpValueDecoder decoder, ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None, RlpLimit? limit = null) { int checkPosition = decoderContext.ReadSequenceLength() + decoderContext.Position; - T[] result = new T[decoderContext.PeekNumberOfItemsRemaining(checkPosition)]; + int length = decoderContext.PeekNumberOfItemsRemaining(checkPosition); + decoderContext.GuardLimit(length, limit); + T[] result = new T[length]; for (int i = 0; i < result.Length; i++) { result[i] = decoder.Decode(ref decoderContext, rlpBehaviors); } + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + decoderContext.Check(checkPosition); + } + return result; } @@ -126,6 +141,27 @@ public static NettyRlpStream EncodeToNewNettyStream(this IRlpStreamDecoder return rlpStream; } + public static NettyRlpStream EncodeToNewNettyStream(this IRlpStreamDecoder decoder, in ArrayPoolListRef items, RlpBehaviors behaviors = RlpBehaviors.None) + { + int totalLength = 0; + for (int i = 0; i < items.Count; i++) + { + totalLength += decoder.GetLength(items[i], behaviors); + } + + int bufferLength = Rlp.LengthOfSequence(totalLength); + + NettyRlpStream rlpStream = new(NethermindBuffers.Default.Buffer(bufferLength)); + rlpStream.StartSequence(totalLength); + + for (int i = 0; i < items.Count; i++) + { + decoder.Encode(rlpStream, items[i], behaviors); + } + + return rlpStream; + } + public static SpanSource EncodeToSpanSource(this IRlpStreamDecoder decoder, T? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None, ICappedArrayPool? bufferPool = null) { diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpLimit.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpLimit.cs new file mode 100644 index 000000000000..2da1c24da2b6 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpLimit.cs @@ -0,0 +1,40 @@ +// 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.Extensions; + +namespace Nethermind.Serialization.Rlp; + +public record struct RlpLimit(int Limit, string TypeName = "", ReadOnlyMemory PropertyName = default) +{ + // 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); + public static readonly RlpLimit L8 = new(8); + public static readonly RlpLimit L32 = new(32); + public static readonly RlpLimit L64 = new(64); + public static readonly RlpLimit L65 = new(65); + private string _collectionExpression; + + public RlpLimit() : this((int)4.MiB()) { } + + public string CollectionExpression + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _collectionExpression ??= GenerateCollectionExpression(); + } + + private string GenerateCollectionExpression() => + PropertyName.IsEmpty + ? TypeName + : $"{TypeName}.{PropertyName}"; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RlpLimit For(int limit, string propertyName = "") => new(limit, typeof(T).Name, propertyName.AsMemory()); + + public override string ToString() => CollectionExpression; +} diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpLimitException.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpLimitException.cs new file mode 100644 index 000000000000..f881d4ccf49e --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpLimitException.cs @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Serialization.Rlp; + +public class RlpLimitException : RlpException +{ + public RlpLimitException(string message, Exception inner) + : base(message, inner) + { + } + + public RlpLimitException(string message) + : base(message) + { + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs index d776121f1dc9..3cda0d7d9f41 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs @@ -4,6 +4,7 @@ using System; using System.Buffers.Binary; using System.Collections.Generic; +using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -21,13 +22,14 @@ public class RlpStream { private static readonly HeaderDecoder _headerDecoder = new(); private static readonly BlockDecoder _blockDecoder = new(); - private static readonly BlockBodyDecoder _blockBodyDecoder = new(); private static readonly BlockInfoDecoder _blockInfoDecoder = new(); private static readonly TxDecoder _txDecoder = TxDecoder.Instance; - private static readonly ReceiptMessageDecoder _receiptDecoder = new(); private static readonly WithdrawalDecoder _withdrawalDecoder = new(); private static readonly LogEntryDecoder _logEntryDecoder = LogEntryDecoder.Instance; + internal static ReadOnlySpan SingleBytes => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127]; + internal static readonly byte[][] SingleByteArrays = [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16], [17], [18], [19], [20], [21], [22], [23], [24], [25], [26], [27], [28], [29], [30], [31], [32], [33], [34], [35], [36], [37], [38], [39], [40], [41], [42], [43], [44], [45], [46], [47], [48], [49], [50], [51], [52], [53], [54], [55], [56], [57], [58], [59], [60], [61], [62], [63], [64], [65], [66], [67], [68], [69], [70], [71], [72], [73], [74], [75], [76], [77], [78], [79], [80], [81], [82], [83], [84], [85], [86], [87], [88], [89], [90], [91], [92], [93], [94], [95], [96], [97], [98], [99], [100], [101], [102], [103], [104], [105], [106], [107], [108], [109], [110], [111], [112], [113], [114], [115], [116], [117], [118], [119], [120], [121], [122], [123], [124], [125], [126], [127]]; + private readonly CappedArray _data; private int _position = 0; @@ -66,7 +68,7 @@ public void EncodeArray(T?[]? items, RlpBehaviors rlpBehaviors = RlpBehaviors StartSequence(contentLength); - foreach (var item in items) + foreach (T? item in items) { decoder.Encode(this, item, rlpBehaviors); } @@ -338,7 +340,11 @@ public void Encode(Bloom? bloom) } } - protected virtual void WriteZero(int length) => Position += 256; + protected virtual void WriteZero(int length) + { + Data.AsSpan(Position, length).Clear(); + Position += length; + } public void Encode(byte value) { @@ -766,7 +772,7 @@ public bool DecodeValueKeccak(out ValueHash256 keccak) return null; } - ReadOnlySpan theSpan = DecodeByteArraySpan(); + ReadOnlySpan theSpan = DecodeByteArraySpan(RlpLimit.L32); byte[] keccakByte = new byte[32]; theSpan.CopyTo(keccakByte.AsSpan(32 - theSpan.Length)); return new Hash256(keccakByte); @@ -804,7 +810,7 @@ public UInt256 DecodeUInt256(int length = -1) return byteValue; } - ReadOnlySpan byteSpan = DecodeByteArraySpan(); + ReadOnlySpan byteSpan = DecodeByteArraySpan(RlpLimit.L32); if (byteSpan.Length > 32) { @@ -828,7 +834,7 @@ public UInt256 DecodeUInt256(int length = -1) public BigInteger DecodeUBigInt() { - ReadOnlySpan bytes = DecodeByteArraySpan(); + ReadOnlySpan bytes = DecodeByteArraySpan(RlpLimit.L32); if (bytes.Length > 1 && bytes[0] == 0) { RlpHelpers.ThrowNonCanonicalInteger(Position); @@ -845,18 +851,18 @@ public BigInteger DecodeUBigInt() if (PeekByte() == 249) { SkipBytes(5); // tks: skip 249 1 2 129 127 and read 256 bytes - bloomBytes = Read(256); + bloomBytes = Read(Bloom.ByteLength); } else { - bloomBytes = DecodeByteArraySpan(); + bloomBytes = DecodeByteArraySpan(RlpLimit.Bloom); if (bloomBytes.Length == 0) { return null; } } - if (bloomBytes.Length != 256) + if (bloomBytes.Length != Bloom.ByteLength) { throw new RlpException("Incorrect bloom RLP"); } @@ -923,11 +929,11 @@ public bool DecodeBool() return default; } - public T[] DecodeArray(Func decodeItem, bool checkPositions = true, - T defaultElement = default) + public T[] DecodeArray(Func decodeItem, bool checkPositions = true, T defaultElement = default, RlpLimit? limit = null) { int positionCheck = ReadSequenceLength() + Position; - int count = PeekNumberOfItemsRemaining(checkPositions ? positionCheck : (int?)null); + int count = PeekNumberOfItemsRemaining(checkPositions ? positionCheck : null); + GuardLimit(count, limit); T[] result = new T[count]; for (int i = 0; i < result.Length; i++) { @@ -942,14 +948,19 @@ public T[] DecodeArray(Func decodeItem, bool checkPositions = t } } + if (checkPositions) + { + Check(positionCheck); + } + return result; } - public ArrayPoolList DecodeArrayPoolList(Func decodeItem, bool checkPositions = true, - T defaultElement = default) + public ArrayPoolList DecodeArrayPoolList(Func decodeItem, bool checkPositions = true, T defaultElement = default, RlpLimit? limit = null) { int positionCheck = ReadSequenceLength() + Position; - int count = PeekNumberOfItemsRemaining(checkPositions ? positionCheck : (int?)null); + int count = PeekNumberOfItemsRemaining(checkPositions ? positionCheck : null); + GuardLimit(count, limit); var result = new ArrayPoolList(count, count); for (int i = 0; i < result.Count; i++) { @@ -964,12 +975,17 @@ public ArrayPoolList DecodeArrayPoolList(Func decodeItem, bo } } + if (checkPositions) + { + Check(positionCheck); + } + return result; } - public string DecodeString() + public string DecodeString(RlpLimit? limit = null) { - ReadOnlySpan bytes = DecodeByteArraySpan(); + ReadOnlySpan bytes = DecodeByteArraySpan(limit); return Encoding.UTF8.GetString(bytes); } @@ -1039,7 +1055,7 @@ public int DecodeInt() public uint DecodeUInt() { - ReadOnlySpan bytes = DecodeByteArraySpan(); + ReadOnlySpan bytes = DecodeByteArraySpan(RlpLimit.L8); if (bytes.Length > 1 && bytes[0] == 0) { RlpHelpers.ThrowNonCanonicalInteger(Position); @@ -1127,7 +1143,7 @@ public ulong DecodeULong() public ulong DecodeUlong() { - ReadOnlySpan bytes = DecodeByteArraySpan(); + ReadOnlySpan bytes = DecodeByteArraySpan(RlpLimit.L8); if (bytes.Length > 1 && bytes[0] == 0) { RlpHelpers.ThrowNonCanonicalInteger(Position); @@ -1135,14 +1151,14 @@ public ulong DecodeUlong() return bytes.Length == 0 ? 0L : bytes.ReadEthUInt64(); } - public byte[] DecodeByteArray() => Rlp.ByteSpanToArray(DecodeByteArraySpan()); + public byte[] DecodeByteArray(RlpLimit? limit = null) => Rlp.ByteSpanToArray(DecodeByteArraySpan(limit)); - public ArrayPoolList DecodeByteArrayPoolList() => Rlp.ByteSpanToArrayPool(DecodeByteArraySpan()); + public ArrayPoolList DecodeByteArrayPoolList(RlpLimit? limit = null) => Rlp.ByteSpanToArrayPool(DecodeByteArraySpan(limit)); - public ReadOnlySpan DecodeByteArraySpan() + public ReadOnlySpan DecodeByteArraySpan(RlpLimit? limit = null) { int prefix = ReadByte(); - ReadOnlySpan span = RlpStream.SingleBytes; + ReadOnlySpan span = SingleBytes; if ((uint)prefix < (uint)span.Length) { return span.Slice(prefix, 1); @@ -1156,6 +1172,7 @@ public ReadOnlySpan DecodeByteArraySpan() if (prefix <= 183) { int length = prefix - 128; + GuardLimit(length, limit); ReadOnlySpan buffer = Read(length); if (buffer.Length == 1 && buffer[0] < 128) { @@ -1165,11 +1182,11 @@ public ReadOnlySpan DecodeByteArraySpan() return buffer; } - return DecodeLargerByteArraySpan(prefix); + return DecodeLargerByteArraySpan(prefix, limit); } [MethodImpl(MethodImplOptions.NoInlining)] - private ReadOnlySpan DecodeLargerByteArraySpan(int prefix) + private ReadOnlySpan DecodeLargerByteArraySpan(int prefix, RlpLimit? limit = null) { if (prefix < 192) { @@ -1184,6 +1201,7 @@ private ReadOnlySpan DecodeLargerByteArraySpan(int prefix) { RlpHelpers.ThrowUnexpectedLength(length); } + GuardLimit(length, limit); return Read(length); } @@ -1206,7 +1224,7 @@ private ReadOnlySpan DecodeLargerByteArraySpan(int prefix) public override string ToString() => $"[{nameof(RlpStream)}|{Position}/{Length}]"; - public byte[][] DecodeByteArrays() + public byte[][] DecodeByteArrays(RlpLimit? limit = null) { int length = ReadSequenceLength(); if (length is 0) @@ -1214,7 +1232,9 @@ public byte[][] DecodeByteArrays() return []; } - int itemsCount = PeekNumberOfItemsRemaining(Position + length); + int checkPosition = Position + length; + int itemsCount = PeekNumberOfItemsRemaining(checkPosition); + GuardLimit(itemsCount, limit); byte[][] result = new byte[itemsCount][]; for (int i = 0; i < itemsCount; i++) @@ -1222,10 +1242,13 @@ public byte[][] DecodeByteArrays() result[i] = DecodeByteArray(); } + Check(checkPosition); + return result; } - internal static ReadOnlySpan SingleBytes => new byte[128] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127 }; - internal static readonly byte[][] SingleByteArrays = new byte[128][] { [0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16], [17], [18], [19], [20], [21], [22], [23], [24], [25], [26], [27], [28], [29], [30], [31], [32], [33], [34], [35], [36], [37], [38], [39], [40], [41], [42], [43], [44], [45], [46], [47], [48], [49], [50], [51], [52], [53], [54], [55], [56], [57], [58], [59], [60], [61], [62], [63], [64], [65], [66], [67], [68], [69], [70], [71], [72], [73], [74], [75], [76], [77], [78], [79], [80], [81], [82], [83], [84], [85], [86], [87], [88], [89], [90], [91], [92], [93], [94], [95], [96], [97], [98], [99], [100], [101], [102], [103], [104], [105], [106], [107], [108], [109], [110], [111], [112], [113], [114], [115], [116], [117], [118], [119], [120], [121], [122], [123], [124], [125], [126], [127] }; + [StackTraceHidden] + public void GuardLimit(int count, RlpLimit? limit = null) => + Rlp.GuardLimit(count, Length - Position, limit); } } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs index 082cbcc3bd92..22d063b13661 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs @@ -24,12 +24,31 @@ static TxDecoder() Instance = new TxDecoder(static () => TxObjectPool.Get()); Rlp.RegisterDecoder(typeof(Transaction), Instance); } + + /// + /// Gets the block-format length of a pre-encoded CL-format transaction. + /// Legacy txs use the same format; typed txs are wrapped in an RLP byte string. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetWrappedTxLength(TxType type, int clEncodedLength) + => type == TxType.Legacy ? clEncodedLength : Rlp.LengthOfSequence(clEncodedLength); + + /// + /// Writes a pre-encoded CL-format transaction in block format. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteWrappedFormat(RlpStream stream, TxType type, byte[] clEncoded) + { + if (type != TxType.Legacy) + stream.StartByteArray(clEncoded.Length, false); + stream.Write(clEncoded); + } } public sealed class SystemTxDecoder : TxDecoder; public sealed class GeneratedTxDecoder : TxDecoder; -public class TxDecoder : IRlpStreamDecoder, IRlpValueDecoder where T : Transaction, new() +public class TxDecoder : RlpValueDecoder where T : Transaction, new() { private readonly ITxDecoder?[] _decoders = new ITxDecoder?[Transaction.MaxTxType + 1]; @@ -45,7 +64,7 @@ protected TxDecoder(Func? transactionFactory = null) public void RegisterDecoder(ITxDecoder decoder) => _decoders[(int)decoder.Type] = decoder; - public T? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override T? DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { static void ThrowIfLegacy(TxType txType1) { @@ -88,7 +107,7 @@ private ITxDecoder GetDecoder(TxType txType) => ? decoder : throw new RlpException($"Unknown transaction type {txType}") { Data = { { "txType", txType } } }; - public T? Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override T? DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { T transaction = null; Decode(ref decoderContext, ref transaction, rlpBehaviors); @@ -131,6 +150,11 @@ public void Decode(ref Rlp.ValueDecoderContext decoderContext, ref T? transactio GetDecoder(txType).Decode(ref Unsafe.As(ref transaction), txSequenceStart, transactionSequence, ref decoderContext, rlpBehaviors); } + public override void Encode(RlpStream stream, T? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + EncodeTx(stream, item, rlpBehaviors, forSigning: false, isEip155Enabled: false, chainId: 0); + } + public Rlp Encode(T item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { RlpStream rlpStream = new(GetLength(item, rlpBehaviors)); @@ -138,11 +162,6 @@ public Rlp Encode(T item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) return new Rlp(rlpStream.Data.ToArray() ?? []); } - public void Encode(RlpStream stream, T? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) - { - EncodeTx(stream, item, rlpBehaviors, forSigning: false, isEip155Enabled: false, chainId: 0); - } - public Rlp EncodeTx(T? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None, bool forSigning = false, bool isEip155Enabled = false, ulong chainId = 0) { RlpStream rlpStream = new(GetLength(item, rlpBehaviors, forSigning, isEip155Enabled, chainId)); @@ -153,7 +172,7 @@ public Rlp EncodeTx(T? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None, bool /// /// https://eips.ethereum.org/EIPS/eip-2718 /// - public int GetLength(T tx, RlpBehaviors rlpBehaviors) => GetLength(tx, rlpBehaviors, forSigning: false, isEip155Enabled: false, chainId: 0); + public override int GetLength(T tx, RlpBehaviors rlpBehaviors) => GetLength(tx, rlpBehaviors, forSigning: false, isEip155Enabled: false, chainId: 0); public void EncodeTx(RlpStream stream, T? item, RlpBehaviors rlpBehaviors, bool forSigning, bool isEip155Enabled, ulong chainId) { diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/BaseTxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/BaseTxDecoder.cs index c6522607a265..d7115f9d72c8 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/BaseTxDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/BaseTxDecoder.cs @@ -14,6 +14,9 @@ public abstract class BaseTxDecoder(TxType txType, Func? transactionFactor private const int MaxDelayedHashTxnSize = 32768; private readonly Func _createTransaction = transactionFactory ?? (static () => new T()); + // 30MB should be good enough for 300MGas block just filled with call data + private static readonly RlpLimit _dataRlpLimit = RlpLimit.For((int)30.MiB(), nameof(Transaction.Data)); + public TxType Type => txType; public virtual Transaction? Decode(Span transactionSequence, RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) @@ -132,7 +135,7 @@ protected virtual void DecodePayload(Transaction transaction, RlpStream rlpStrea transaction.GasLimit = rlpStream.DecodeLong(); transaction.To = rlpStream.DecodeAddress(); transaction.Value = rlpStream.DecodeUInt256(); - transaction.Data = rlpStream.DecodeByteArray(); + transaction.Data = rlpStream.DecodeByteArray(_dataRlpLimit); } protected virtual void DecodeGasPrice(Transaction transaction, RlpStream rlpStream) @@ -147,7 +150,7 @@ protected virtual void DecodePayload(Transaction transaction, ref Rlp.ValueDecod transaction.GasLimit = decoderContext.DecodeLong(); transaction.To = decoderContext.DecodeAddress(); transaction.Value = decoderContext.DecodeUInt256(); - transaction.Data = decoderContext.DecodeByteArrayMemory(); + transaction.Data = decoderContext.DecodeByteArrayMemory(_dataRlpLimit); } protected virtual void DecodeGasPrice(Transaction transaction, ref Rlp.ValueDecoderContext decoderContext) @@ -158,16 +161,16 @@ protected virtual void DecodeGasPrice(Transaction transaction, ref Rlp.ValueDeco protected Signature? DecodeSignature(Transaction transaction, RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { ulong v = rlpStream.DecodeULong(); - ReadOnlySpan rBytes = rlpStream.DecodeByteArraySpan(); - ReadOnlySpan sBytes = rlpStream.DecodeByteArraySpan(); + ReadOnlySpan rBytes = rlpStream.DecodeByteArraySpan(RlpLimit.L32); + ReadOnlySpan sBytes = rlpStream.DecodeByteArraySpan(RlpLimit.L32); return DecodeSignature(v, rBytes, sBytes, transaction.Signature, rlpBehaviors); } protected Signature? DecodeSignature(Transaction transaction, ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { ulong v = decoderContext.DecodeULong(); - ReadOnlySpan rBytes = decoderContext.DecodeByteArraySpan(); - ReadOnlySpan sBytes = decoderContext.DecodeByteArraySpan(); + ReadOnlySpan rBytes = decoderContext.DecodeByteArraySpan(RlpLimit.L32); + ReadOnlySpan sBytes = decoderContext.DecodeByteArraySpan(RlpLimit.L32); return DecodeSignature(v, rBytes, sBytes, transaction.Signature, rlpBehaviors); } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/BlobTxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/BlobTxDecoder.cs index 3f1fc2ea10f9..dd548dd4a4ad 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.Rlp/TxDecoders/SetCodeTxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/SetCodeTxDecoder.cs index 3b121bffcbee..0b6dff7fb322 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/SetCodeTxDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/SetCodeTxDecoder.cs @@ -14,7 +14,7 @@ public sealed class SetCodeTxDecoder(Func? transactionFactory = null) protected override void DecodePayload(Transaction transaction, RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { base.DecodePayload(transaction, rlpStream, rlpBehaviors); - transaction.AuthorizationList = rlpStream.DecodeArray((s) => _authTupleDecoder.Decode(s, rlpBehaviors)); + transaction.AuthorizationList = rlpStream.DecodeArray((s) => _authTupleDecoder.Decode(s, rlpBehaviors)); } protected override void DecodePayload(Transaction transaction, ref Rlp.ValueDecoderContext decoderContext, diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/WithdrawalDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/WithdrawalDecoder.cs index 6ddd0e6a6009..d70984bb7e71 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/WithdrawalDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/WithdrawalDecoder.cs @@ -5,9 +5,9 @@ namespace Nethermind.Serialization.Rlp; -public class WithdrawalDecoder : IRlpStreamDecoder, IRlpValueDecoder +public sealed class WithdrawalDecoder : RlpValueDecoder { - public Withdrawal? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override Withdrawal? DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (rlpStream.IsNextItemNull()) { @@ -27,7 +27,7 @@ public class WithdrawalDecoder : IRlpStreamDecoder, IRlpValueDecoder }; } - public Withdrawal? Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override Withdrawal? DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (decoderContext.IsNextItemNull()) { @@ -47,7 +47,7 @@ public class WithdrawalDecoder : IRlpStreamDecoder, IRlpValueDecoder }; } - public void Encode(RlpStream stream, Withdrawal? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, Withdrawal? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) { @@ -79,5 +79,5 @@ private static int GetContentLength(Withdrawal item) => Rlp.LengthOfAddressRlp + Rlp.LengthOf(item.AmountInGwei); - public int GetLength(Withdrawal item, RlpBehaviors _) => Rlp.LengthOfSequence(GetContentLength(item)); + public override int GetLength(Withdrawal item, RlpBehaviors _) => Rlp.LengthOfSequence(GetContentLength(item)); } diff --git a/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.BasicTypes.cs b/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.BasicTypes.cs index f95471e7864e..41bd474e5c5a 100644 --- a/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.BasicTypes.cs +++ b/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.BasicTypes.cs @@ -15,6 +15,8 @@ namespace Nethermind.Serialization.Ssz; /// public static partial class Ssz { + private const int VarOffsetSize = sizeof(uint); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(Span span, byte[] value, ref int offset) { diff --git a/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.Containers.cs b/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.Containers.cs deleted file mode 100644 index 3055b6038a62..000000000000 --- a/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.Containers.cs +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; - -namespace Nethermind.Serialization.Ssz; - -public partial class Ssz -{ - private const int VarOffsetSize = sizeof(uint); - - private static void DecodeDynamicOffset(ReadOnlySpan span, ref int offset, out int dynamicOffset) - { - dynamicOffset = (int)DecodeUInt(span.Slice(offset, VarOffsetSize)); - offset += sizeof(uint); - } - -} diff --git a/src/Nethermind/Nethermind.Serialization.SszGenerator.Test/SszTypes.cs b/src/Nethermind/Nethermind.Serialization.SszGenerator.Test/SszTypes.cs index f1249c1ae665..759f62fec873 100644 --- a/src/Nethermind/Nethermind.Serialization.SszGenerator.Test/SszTypes.cs +++ b/src/Nethermind/Nethermind.Serialization.SszGenerator.Test/SszTypes.cs @@ -4,7 +4,6 @@ using Nethermind.Merkleization; using Nethermind.Serialization.Ssz; using System.Collections; -using System.ComponentModel; namespace Nethermind.Serialization.SszGenerator.Test { diff --git a/src/Nethermind/Nethermind.Serialization.SszGenerator/AnalyzerReleases.Unshipped.md b/src/Nethermind/Nethermind.Serialization.SszGenerator/AnalyzerReleases.Unshipped.md index af79f7e195f0..43681898caca 100644 --- a/src/Nethermind/Nethermind.Serialization.SszGenerator/AnalyzerReleases.Unshipped.md +++ b/src/Nethermind/Nethermind.Serialization.SszGenerator/AnalyzerReleases.Unshipped.md @@ -1,5 +1,5 @@ ; Unshipped analyzer release -; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md +; https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md ### New Rules diff --git a/src/Nethermind/Nethermind.Serialization.SszGenerator/Nethermind.Serialization.SszGenerator.csproj b/src/Nethermind/Nethermind.Serialization.SszGenerator/Nethermind.Serialization.SszGenerator.csproj index ae6ab740113a..f9b4add506e8 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 1277e0b28055..64b4b9bf7263 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.Test/ShutterApiSimulator.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterApiSimulator.cs index b9899f3637e3..9e36ffa069a8 100644 --- a/src/Nethermind/Nethermind.Shutter.Test/ShutterApiSimulator.cs +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterApiSimulator.cs @@ -15,7 +15,6 @@ using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; -using Nethermind.Evm.TransactionProcessing; using Nethermind.Facade.Find; using Nethermind.KeyStore.Config; using Nethermind.Logging; diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterBlockHandlerTests.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterBlockHandlerTests.cs index 9338b8626726..3267d86d6587 100644 --- a/src/Nethermind/Nethermind.Shutter.Test/ShutterBlockHandlerTests.cs +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterBlockHandlerTests.cs @@ -12,6 +12,7 @@ namespace Nethermind.Shutter.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] class ShutterBlockHandlerTests : BaseEngineModuleTests { [Test] @@ -31,7 +32,7 @@ public void Can_wait_for_valid_block() } [Test] - public void Wait_times_out_at_cutoff() + public async Task Wait_times_out_at_cutoff() { Random rnd = new(ShutterTestsCommon.Seed); Timestamper timestamper = ShutterTestsCommon.InitTimestamper(ShutterTestsCommon.InitialSlotTimestamp, 0); @@ -47,7 +48,8 @@ public void Wait_times_out_at_cutoff() Assert.That(waitTask.IsCompleted, Is.False); timeoutSource.Cancel(); - Assert.That(waitTask.IsCompletedSuccessfully); + Block? result = await waitTask; + Assert.That(result, Is.Null); } [Test] diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterCryptoTests.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterCryptoTests.cs index f22bd96d6aac..0d4917627615 100644 --- a/src/Nethermind/Nethermind.Shutter.Test/ShutterCryptoTests.cs +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterCryptoTests.cs @@ -7,7 +7,6 @@ using Nethermind.Int256; using NUnit.Framework; using Nethermind.Crypto; -using Nethermind.Serialization.Rlp; using Nethermind.Core.Extensions; namespace Nethermind.Shutter.Test; @@ -18,6 +17,7 @@ namespace Nethermind.Shutter.Test; using EncryptedMessage = ShutterCrypto.EncryptedMessage; [TestFixture] +[Parallelizable(ParallelScope.All)] class ShutterCryptoTests { [Test] diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs index fede30d6d11a..c50e687cbce7 100644 --- a/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs @@ -16,6 +16,7 @@ namespace Nethermind.Shutter.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] public class ShutterIntegrationTests : BaseEngineModuleTests { private const int BuildingSlot = (int)ShutterTestsCommon.InitialSlot; @@ -38,7 +39,7 @@ public async Task Can_load_when_previous_block_arrives_late() // keys arrive 5 seconds before slot start // waits for previous block to timeout then loads txs - chain.Api!.AdvanceSlot(20); + chain.Api.AdvanceSlot(20); // no events loaded initially var txs = chain.Api.TxSource.GetTransactions(chain.BlockTree!.Head!.Header, 0, payloadAttributes).ToList(); @@ -46,8 +47,8 @@ public async Task Can_load_when_previous_block_arrives_late() // after timeout they should be loaded using CancellationTokenSource cts = new(); - await chain.Api.TxSource.WaitForTransactions((ulong)BuildingSlot, cts.Token); - txs = chain.Api.TxSource.GetTransactions(chain.BlockTree!.Head!.Header, 0, payloadAttributes).ToList(); + await chain.Api.TxSource.WaitForTransactions(BuildingSlot, cts.Token); + txs = chain.Api.TxSource.GetTransactions(chain.BlockTree.Head!.Header, 0, payloadAttributes).ToList(); Assert.That(txs, Has.Count.EqualTo(20)); // late block arrives, then next block should contain loaded transactions @@ -59,6 +60,7 @@ public async Task Can_load_when_previous_block_arrives_late() [Test] + [Retry(3)] public async Task Can_load_when_block_arrives_before_keys() { Random rnd = new(ShutterTestsCommon.Seed); @@ -71,10 +73,10 @@ public async Task Can_load_when_block_arrives_before_keys() using var chain = (ShutterTestBlockchain)await new ShutterTestBlockchain(rnd, timestamper).Build(ShutterTestsCommon.SpecProvider); IEngineRpcModule rpc = chain.EngineRpcModule; IReadOnlyList executionPayloads = await ProduceBranchV1(rpc, chain, BuildingSlot - 2, CreateParentBlockRequestOnHead(chain.BlockTree), true, null, 5); - ExecutionPayload lastPayload = executionPayloads[executionPayloads.Count - 1]; + ExecutionPayload lastPayload = executionPayloads[^1]; // no events loaded initially - var txs = chain.Api!.TxSource.GetTransactions(chain.BlockTree!.Head!.Header, 0, payloadAttributes).ToList(); + var txs = chain.Api.TxSource.GetTransactions(chain.BlockTree.Head!.Header, 0, payloadAttributes).ToList(); Assert.That(txs, Has.Count.EqualTo(0)); chain.Api.AdvanceSlot(20); @@ -82,7 +84,7 @@ public async Task Can_load_when_block_arrives_before_keys() IReadOnlyList payloads = await ProduceBranchV1(rpc, chain, 1, lastPayload, true, null, 5); lastPayload = payloads[0]; - txs = chain.Api.TxSource.GetTransactions(chain.BlockTree!.Head!.Header, 0, payloadAttributes).ToList(); + txs = chain.Api.TxSource.GetTransactions(chain.BlockTree.Head!.Header, 0, payloadAttributes).ToList(); Assert.That(txs, Has.Count.EqualTo(20)); payloads = await ProduceBranchV1(rpc, chain, 1, lastPayload, true, null, 5); @@ -113,7 +115,9 @@ public async Task Can_increment_metric_on_missed_keys() time += (long)ShutterTestsCommon.SlotLength.TotalSeconds; } - Assert.That(Metrics.ShutterKeysMissed, Is.EqualTo(5)); + // ImproveBlock tasks run in the background and may not have completed yet + // when GetPayload returns (it only waits 50ms), so poll until all increments land. + Assert.That(() => Metrics.ShutterKeysMissed, Is.EqualTo((ulong)5).After(5000, 50)); } } diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterKeyValidatorTests.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterKeyValidatorTests.cs index e7b2f65c8261..b4867c95f2aa 100644 --- a/src/Nethermind/Nethermind.Shutter.Test/ShutterKeyValidatorTests.cs +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterKeyValidatorTests.cs @@ -8,6 +8,7 @@ namespace Nethermind.Shutter.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] class ShutterKeyValidatorTests { [Test] diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterTestsCommon.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterTestsCommon.cs index 546f9b7edf91..7ff7536e1bf6 100644 --- a/src/Nethermind/Nethermind.Shutter.Test/ShutterTestsCommon.cs +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterTestsCommon.cs @@ -10,7 +10,6 @@ using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Crypto; -using Nethermind.Evm.TransactionProcessing; using Nethermind.Facade.Find; using Nethermind.KeyStore.Config; using Nethermind.Logging; @@ -20,6 +19,7 @@ using NSubstitute; namespace Nethermind.Shutter.Test; + class ShutterTestsCommon { public const int Seed = 100; diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterTxFilterTests.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterTxFilterTests.cs index 0ea7c024a907..00374dd78143 100644 --- a/src/Nethermind/Nethermind.Shutter.Test/ShutterTxFilterTests.cs +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterTxFilterTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Shutter.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] class ShutterTxFilterTests { [Test] diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterTxLoaderTests.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterTxLoaderTests.cs index 511299d9177a..dd8ef0ba36a9 100644 --- a/src/Nethermind/Nethermind.Shutter.Test/ShutterTxLoaderTests.cs +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterTxLoaderTests.cs @@ -15,6 +15,7 @@ namespace Nethermind.Shutter.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] class ShutterTxLoaderTests : BaseEngineModuleTests { private class ShutterEventSimulatorHalfInvalid(Random rnd, ulong chainId, ulong threshold, ulong slot, IAbiEncoder abiEncoder, Address sequencerContractAddress) : ShutterEventSimulator(rnd, chainId, threshold, slot, abiEncoder, sequencerContractAddress) diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterValidatorRegistryTests.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterValidatorRegistryTests.cs index a8c134b5d908..383dc215ea85 100644 --- a/src/Nethermind/Nethermind.Shutter.Test/ShutterValidatorRegistryTests.cs +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterValidatorRegistryTests.cs @@ -19,6 +19,7 @@ namespace Nethermind.Shutter.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] class ShutterValidatorRegistryTests { private static readonly byte[] SkBytes = [0x2c, 0xd4, 0xba, 0x40, 0x6b, 0x52, 0x24, 0x59, 0xd5, 0x7a, 0x0b, 0xed, 0x51, 0xa3, 0x97, 0x43, 0x5c, 0x0b, 0xb1, 0x1d, 0xd5, 0xf3, 0xca, 0x11, 0x52, 0xb3, 0x69, 0x4b, 0xb9, 0x1d, 0x7c, 0x22]; diff --git a/src/Nethermind/Nethermind.Shutter/Contracts/ValidatorRegistryContract.cs b/src/Nethermind/Nethermind.Shutter/Contracts/ValidatorRegistryContract.cs index ce8f6297556e..0ffec84aa708 100644 --- a/src/Nethermind/Nethermind.Shutter/Contracts/ValidatorRegistryContract.cs +++ b/src/Nethermind/Nethermind.Shutter/Contracts/ValidatorRegistryContract.cs @@ -13,8 +13,6 @@ using Nethermind.Core.Extensions; using Nethermind.Crypto; using Nethermind.Shutter.Config; -using System.Linq; - using Update = (byte[] Message, byte[] Signature); namespace Nethermind.Shutter.Contracts; diff --git a/src/Nethermind/Nethermind.Shutter/IShutterBlockHandler.cs b/src/Nethermind/Nethermind.Shutter/IShutterBlockHandler.cs index 8db86be349a3..1652f018a5a0 100644 --- a/src/Nethermind/Nethermind.Shutter/IShutterBlockHandler.cs +++ b/src/Nethermind/Nethermind.Shutter/IShutterBlockHandler.cs @@ -7,6 +7,7 @@ using Nethermind.Core; namespace Nethermind.Shutter; + public interface IShutterBlockHandler : IDisposable { Task WaitForBlockInSlot(ulong slot, CancellationToken cancellationToken, Func? initTimeoutSource = null); diff --git a/src/Nethermind/Nethermind.Shutter/IShutterTxSignal.cs b/src/Nethermind/Nethermind.Shutter/IShutterTxSignal.cs index edb5cc510c74..4c31b7459c85 100644 --- a/src/Nethermind/Nethermind.Shutter/IShutterTxSignal.cs +++ b/src/Nethermind/Nethermind.Shutter/IShutterTxSignal.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; namespace Nethermind.Shutter; + public interface IShutterTxSignal { Task WaitForTransactions(ulong slot, CancellationToken cancellationToken); diff --git a/src/Nethermind/Nethermind.Shutter/ShutterAdditionalBlockProductionTxSource.cs b/src/Nethermind/Nethermind.Shutter/ShutterAdditionalBlockProductionTxSource.cs index eab81a7aa1d7..2892dbb9abaf 100644 --- a/src/Nethermind/Nethermind.Shutter/ShutterAdditionalBlockProductionTxSource.cs +++ b/src/Nethermind/Nethermind.Shutter/ShutterAdditionalBlockProductionTxSource.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Nethermind.Consensus.Producers; using Nethermind.Consensus.Transactions; diff --git a/src/Nethermind/Nethermind.Shutter/ShutterApi.cs b/src/Nethermind/Nethermind.Shutter/ShutterApi.cs index 005152f5bfa3..41be56d72f43 100644 --- a/src/Nethermind/Nethermind.Shutter/ShutterApi.cs +++ b/src/Nethermind/Nethermind.Shutter/ShutterApi.cs @@ -13,11 +13,9 @@ using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; using Nethermind.Consensus; -using Nethermind.Consensus.Processing; using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Crypto; -using Nethermind.Evm.TransactionProcessing; using Nethermind.Facade.Find; using Nethermind.KeyStore.Config; using Nethermind.Logging; diff --git a/src/Nethermind/Nethermind.Shutter/ShutterBlockHandler.cs b/src/Nethermind/Nethermind.Shutter/ShutterBlockHandler.cs index 7584da0df921..4556f40f58d0 100644 --- a/src/Nethermind/Nethermind.Shutter/ShutterBlockHandler.cs +++ b/src/Nethermind/Nethermind.Shutter/ShutterBlockHandler.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using Nethermind.Consensus.Processing; using Nethermind.Core; using Nethermind.Evm.TransactionProcessing; using Nethermind.Shutter.Contracts; @@ -89,7 +88,7 @@ public ShutterBlockHandler( if (_logger.IsDebug) _logger.Debug($"Waiting for block in {slot} to get Shutter transactions."); - tcs = new(); + tcs = new(TaskCreationOptions.RunContinuationsAsynchronously); long offset = _time.GetCurrentOffsetMs(slot); long waitTime = (long)_blockWaitCutoff.TotalMilliseconds - offset; @@ -125,31 +124,39 @@ public ShutterBlockHandler( public void Dispose() { _blockTree.NewHeadBlock -= OnNewHeadBlock; - _blockWaitTasks.ForEach(static x => x.Value.ForEach(static waitTask => + lock (_syncObject) { - waitTask.Value.CancellationRegistration.Dispose(); - waitTask.Value.TimeoutCancellationRegistration.Dispose(); - })); + _blockWaitTasks.ForEach(static x => x.Value.ForEach(static waitTask => + { + waitTask.Value.CancellationRegistration.Dispose(); + waitTask.Value.TimeoutCancellationRegistration.Dispose(); + })); + } } private void CancelWaitForBlock(ulong slot, ulong taskId, bool timeout) { - if (_blockWaitTasks.TryGetValue(slot, out Dictionary? slotWaitTasks)) + lock (_syncObject) { - if (slotWaitTasks.TryGetValue(taskId, out BlockWaitTask waitTask)) + if (_blockWaitTasks.TryGetValue(slot, out Dictionary? slotWaitTasks)) { - if (timeout) - { - waitTask.Tcs.TrySetResult(null); - } - else + if (slotWaitTasks.TryGetValue(taskId, out BlockWaitTask waitTask)) { - waitTask.Tcs.SetException(new OperationCanceledException()); + if (timeout) + { + waitTask.Tcs.TrySetResult(null); + } + else + { + waitTask.Tcs.SetException(new OperationCanceledException()); + } + + waitTask.CancellationRegistration.Dispose(); + waitTask.TimeoutCancellationRegistration.Dispose(); } - waitTask.CancellationRegistration.Dispose(); - waitTask.TimeoutCancellationRegistration.Dispose(); + + slotWaitTasks.Remove(taskId); } - slotWaitTasks.Remove(taskId); } } diff --git a/src/Nethermind/Nethermind.Shutter/ShutterBlockImprovementContext.cs b/src/Nethermind/Nethermind.Shutter/ShutterBlockImprovementContext.cs index 09617cc288c1..bc213677c2ee 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 f9f627152f87..8325863d8f94 100644 --- a/src/Nethermind/Nethermind.Shutter/ShutterCrypto.cs +++ b/src/Nethermind/Nethermind.Shutter/ShutterCrypto.cs @@ -30,10 +30,10 @@ public static class ShutterCrypto public readonly ref struct EncryptedMessage { - public readonly byte VersionId { get; init; } - public readonly G2 C1 { get; init; } - public readonly ReadOnlySpan C2 { get; init; } - public readonly ReadOnlySpan C3 { get; init; } + public byte VersionId { get; init; } + public G2 C1 { get; init; } + public ReadOnlySpan C2 { get; init; } + public ReadOnlySpan C3 { get; init; } } public class ShutterCryptoException(string message, Exception? innerException = null) : Exception(message, innerException); @@ -101,7 +101,7 @@ public static EncryptedMessage DecodeEncryptedMessage(ReadOnlySpan bytes) public static void RecoverSigma(out Span sigma, EncryptedMessage encryptedMessage, G1Affine decryptionKey) { - using ArrayPoolList buf = new(GT.Sz, GT.Sz); + using ArrayPoolListRef buf = new(GT.Sz, GT.Sz); GT p = new(buf.AsSpan()); p.MillerLoop(encryptedMessage.C1.ToAffine(), decryptionKey); sigma = Hash2(p); // key @@ -143,7 +143,7 @@ public static void ComputeBlockKeys(Span blockKeys, ReadOnlySpan sig public static void ComputeIdentity(G1 p, scoped ReadOnlySpan bytes) { int len = bytes.Length + 1; - using ArrayPoolList buf = new(len, len); + using ArrayPoolListRef buf = new(len, len); Span preimage = buf.AsSpan(); preimage[0] = 0x1; bytes.CopyTo(preimage[1..]); @@ -152,7 +152,7 @@ public static void ComputeIdentity(G1 p, scoped ReadOnlySpan bytes) public static Span Hash2(GT p) { - using ArrayPoolList buf = new(577, 577); + using ArrayPoolListRef buf = new(577, 577); Span preimage = buf.AsSpan(); preimage[0] = 0x2; p.FinalExp().ToBendian().CopyTo(preimage[1..]); @@ -167,7 +167,7 @@ public static void GTExp(ref GT x, UInt256 exp) } exp -= 1; - using ArrayPoolList buf = new(GT.Sz, GT.Sz); + using ArrayPoolListRef buf = new(GT.Sz, GT.Sz); x.Fp12.CopyTo(buf.AsSpan()); GT a = new(buf.AsSpan()); for (; exp > 0; exp >>= 1) @@ -184,7 +184,7 @@ public static void GTExp(ref GT x, UInt256 exp) public static bool CheckDecryptionKey(G1Affine decryptionKey, G2Affine eonPublicKey, G1Affine identity) { int len = GT.Sz * 2; - using ArrayPoolList buf = new(len, len); + using ArrayPoolListRef buf = new(len, len); GT p1 = new(buf.AsSpan()[..GT.Sz]); p1.MillerLoop(G2Affine.Generator(stackalloc long[G2Affine.Sz]), decryptionKey); GT p2 = new(buf.AsSpan()[GT.Sz..]); @@ -222,7 +222,7 @@ public static bool CheckValidatorRegistrySignatures(BlsSigner.AggregatedPublicKe public static EncryptedMessage Encrypt(ReadOnlySpan msg, G1 identity, G2 eonKey, ReadOnlySpan sigma) { int len = PaddedLength(msg); - using ArrayPoolList buf = new(len, len); + using ArrayPoolListRef buf = new(len, len); ComputeR(sigma, msg, out UInt256 r); ReadOnlySpan msgBlocks = PadAndSplit(buf.AsSpan(), msg); Span c3 = new byte[msgBlocks.Length]; @@ -266,7 +266,7 @@ private static void UnpadAndJoin(ref Span blocks) byte n = blocks[^1]; - if (n == 0 || n > 32) + if (n is 0 or > 32) { throw new ShutterCryptoException("Invalid padding length"); } @@ -276,7 +276,7 @@ private static void UnpadAndJoin(ref Span blocks) private static Span ComputeC2(scoped ReadOnlySpan sigma, UInt256 r, G1 identity, G2 eonKey) { - using ArrayPoolList buf = new(GT.Sz, GT.Sz); + using ArrayPoolListRef buf = new(GT.Sz, GT.Sz); GT p = new(buf.AsSpan()); p.MillerLoop(eonKey, identity); GTExp(ref p, r); @@ -306,7 +306,7 @@ private static Span PadAndSplit(Span paddedBytes, scoped ReadOnlySpa private static void ComputeR(scoped ReadOnlySpan sigma, scoped ReadOnlySpan msg, out UInt256 r) { int len = 32 + msg.Length; - using ArrayPoolList buf = new(len, len); + using ArrayPoolListRef buf = new(len, len); Span preimage = buf.AsSpan(); sigma.CopyTo(preimage); msg.CopyTo(preimage[32..]); @@ -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, @@ -338,7 +338,7 @@ internal static Hash256 GenerateHash(ulong instanceId, ulong eon, ulong slot, ul private static void Hash3(ReadOnlySpan bytes, out UInt256 res) { int len = bytes.Length + 1; - using ArrayPoolList buf = new(len, len); + using ArrayPoolListRef buf = new(len, len); Span preimage = buf.AsSpan(); preimage[0] = 0x3; bytes.CopyTo(preimage[1..]); @@ -349,20 +349,20 @@ private static void Hash3(ReadOnlySpan bytes, out UInt256 res) private static ValueHash256 Hash4(ReadOnlySpan bytes) { int len = bytes.Length + 1; - using ArrayPoolList buf = new(len, len); + using ArrayPoolListRef buf = new(len, len); Span preimage = buf.AsSpan(); preimage[0] = 0x4; bytes.CopyTo(preimage[1..]); return ValueKeccak.Compute(preimage); } - private readonly struct SlotDecryptionIdentites + private readonly struct SlotDecryptionIdentities { - public readonly ulong InstanceID { get; init; } - public readonly ulong Eon { get; init; } - public readonly ulong Slot { get; init; } - public readonly ulong TxPointer { get; init; } - public readonly IEnumerable> IdentityPreimages { get; init; } + public ulong InstanceID { get; init; } + public ulong Eon { get; init; } + public ulong Slot { get; init; } + public ulong TxPointer { get; init; } + public IEnumerable> IdentityPreimages { get; init; } } } diff --git a/src/Nethermind/Nethermind.Shutter/ShutterEon.cs b/src/Nethermind/Nethermind.Shutter/ShutterEon.cs index 20e3f24b8bb8..c41e3f0f037c 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/ShutterPlugin.cs b/src/Nethermind/Nethermind.Shutter/ShutterPlugin.cs index c3286afd98ac..7bf5f1ab5939 100644 --- a/src/Nethermind/Nethermind.Shutter/ShutterPlugin.cs +++ b/src/Nethermind/Nethermind.Shutter/ShutterPlugin.cs @@ -21,7 +21,6 @@ using Nethermind.Consensus.Producers; using Nethermind.Core.Specs; using Nethermind.Crypto; -using Nethermind.Evm.TransactionProcessing; using Nethermind.Facade.Find; using Nethermind.KeyStore.Config; using Nethermind.Network; diff --git a/src/Nethermind/Nethermind.Shutter/ShutterTxFilter.cs b/src/Nethermind/Nethermind.Shutter/ShutterTxFilter.cs index 61ff0456ac57..f07a1ab198c6 100644 --- a/src/Nethermind/Nethermind.Shutter/ShutterTxFilter.cs +++ b/src/Nethermind/Nethermind.Shutter/ShutterTxFilter.cs @@ -5,7 +5,6 @@ using Nethermind.Core.Specs; using Nethermind.Logging; using Nethermind.Consensus.Validators; -using Nethermind.Consensus.Transactions; using Nethermind.TxPool; using Nethermind.Int256; diff --git a/src/Nethermind/Nethermind.Shutter/ShutterTxLoader.cs b/src/Nethermind/Nethermind.Shutter/ShutterTxLoader.cs index 6c6f03813ce7..7ba443fd9dba 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 fb02c03416de..705a9296745b 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 52dfbf69fdd9..e62685271cc2 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.Sockets/IpcSocketMessageStream.cs b/src/Nethermind/Nethermind.Sockets/IpcSocketMessageStream.cs index b6a3314ed076..81fff6c283ed 100644 --- a/src/Nethermind/Nethermind.Sockets/IpcSocketMessageStream.cs +++ b/src/Nethermind/Nethermind.Sockets/IpcSocketMessageStream.cs @@ -7,7 +7,6 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; -using Nethermind.Core.Buffers; namespace Nethermind.Sockets; diff --git a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs index 48c27a90239c..a2d17190d9ea 100644 --- a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs +++ b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using FluentAssertions; using Nethermind.Config; using Nethermind.Consensus.AuRa.Config; using Nethermind.Core; @@ -48,17 +47,16 @@ public void Setup() [NonParallelizable] public void Timestamp_activation_equal_to_genesis_timestamp_loads_correctly(long blockNumber, ulong? timestamp, bool isEip3855Enabled) { - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance); string path = Path.Combine(TestContext.CurrentContext.WorkDirectory, $"../../../../{Assembly.GetExecutingAssembly().GetName().Name}/Specs/Timestamp_activation_equal_to_genesis_timestamp_test.json"); ChainSpec chainSpec = loader.LoadEmbeddedOrFromFile(path); - chainSpec.Parameters.Eip2537Transition.Should().BeNull(); + Assert.That(chainSpec.Parameters.Eip2537Transition, Is.Null); ILogger logger = new(Substitute.ForPartsOf()); 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; @@ -78,9 +76,9 @@ public void Timestamp_activation_equal_to_genesis_timestamp_loads_correctly(long [Test] public void Missing_dependent_property() { - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance); string path = Path.Combine(TestContext.CurrentContext.WorkDirectory, - $"../../../../{Assembly.GetExecutingAssembly().GetName().Name}/Specs/holesky_missing_deposit_contract.json"); + $"../../../../{Assembly.GetExecutingAssembly().GetName().Name}/Specs/hoodi_no_deposit_contract.json"); InvalidDataException? exception = Assert.Throws(() => loader.LoadEmbeddedOrFromFile(path)); using (Assert.EnterMultipleScope()) { @@ -106,11 +104,11 @@ public void Missing_dependent_property() [NonParallelizable] public void Logs_warning_when_timestampActivation_happens_before_blockActivation(long blockNumber, ulong? timestamp, bool isEip3855Enabled, bool isEip3198Enabled, bool receivesWarning) { - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance); string path = Path.Combine(TestContext.CurrentContext.WorkDirectory, $"../../../../{Assembly.GetExecutingAssembly().GetName().Name}/Specs/Logs_warning_when_timestampActivation_happens_before_blockActivation_test.json"); ChainSpec chainSpec = loader.LoadEmbeddedOrFromFile(path); - chainSpec.Parameters.Eip2537Transition.Should().BeNull(); + Assert.That(chainSpec.Parameters.Eip2537Transition, Is.Null); InterfaceLogger iLogger = Substitute.For(); iLogger.IsWarn.Returns(true); ILogger logger = new(iLogger); @@ -183,16 +181,19 @@ public void Sepolia_loads_properly(ForkActivation forkActivation) Assert.That(provider.GenesisSpec.DifficultyBombDelay, Is.EqualTo(long.MaxValue)); Assert.That(provider.ChainId, Is.EqualTo(BlockchainIds.Sepolia)); Assert.That(provider.NetworkId, Is.EqualTo(BlockchainIds.Sepolia)); + + IEnumerable timestamps = GetTransitionTimestamps(chainSpec.Parameters); + foreach (ulong t in timestamps) + { + Assert.That(ValidateSlotByTimestamp(t, SepoliaSpecProvider.BeaconChainGenesisTimestampConst), Is.True); + } } - GetTransitionTimestamps(chainSpec.Parameters).Should().AllSatisfy( - static t => ValidateSlotByTimestamp(t, SepoliaSpecProvider.BeaconChainGenesisTimestampConst).Should().BeTrue()); IReleaseSpec postCancunSpec = provider.GetSpec((2, SepoliaSpecProvider.CancunTimestamp)); - - VerifyCancunSpecificsForMainnetAndHoleskyAndSepolia(postCancunSpec); + VerifyCancunSpecificsForMainnetAndSepolia(postCancunSpec); IReleaseSpec postPragueSpec = provider.GetSpec((2, SepoliaSpecProvider.PragueTimestamp)); - VerifyPragueSpecificsForMainnetHoleskyHoodiAndSepolia(provider.ChainId, postPragueSpec); + VerifyPragueSpecificsForMainnetHoodiAndSepolia(provider.ChainId, postPragueSpec); IReleaseSpec postOsakaSpec = provider.GetSpec((2, SepoliaSpecProvider.OsakaTimestamp)); IReleaseSpec postBPO1Spec = provider.GetSpec((2, SepoliaSpecProvider.BPO1Timestamp)); @@ -200,62 +201,7 @@ public void Sepolia_loads_properly(ForkActivation forkActivation) VerifyOsakaSpecificsForMainnetHoleskyHoodiAndSepolia(provider.ChainId, postOsakaSpec, postBPO1Spec, postBPO2Spec); } - public static IEnumerable HoleskyActivations - { - get - { - yield return new TestCaseData(new ForkActivation(0, HoleskySpecProvider.GenesisTimestamp)) { TestName = "Genesis" }; - yield return new TestCaseData(new ForkActivation(1, HoleskySpecProvider.ShanghaiTimestamp)) { TestName = "Shanghai" }; - yield return new TestCaseData(new ForkActivation(3, HoleskySpecProvider.ShanghaiTimestamp + 24)) { TestName = "Post Shanghai" }; - yield return new TestCaseData(new ForkActivation(4, HoleskySpecProvider.CancunTimestamp - 1)) { TestName = "Before Cancun" }; - yield return new TestCaseData(new ForkActivation(5, HoleskySpecProvider.CancunTimestamp)) { TestName = "Cancun" }; - yield return new TestCaseData(new ForkActivation(6, HoleskySpecProvider.CancunTimestamp + 24)) { TestName = "Post Cancun" }; - yield return new TestCaseData(new ForkActivation(7, HoleskySpecProvider.PragueTimestamp - 1)) { TestName = "Before Prague" }; - yield return new TestCaseData(new ForkActivation(8, HoleskySpecProvider.PragueTimestamp)) { TestName = "Prague" }; - yield return new TestCaseData(new ForkActivation(9, HoleskySpecProvider.OsakaTimestamp - 1)) { TestName = "Before Osaka" }; - yield return new TestCaseData(new ForkActivation(10, HoleskySpecProvider.OsakaTimestamp)) { TestName = "Osaka" }; - yield return new TestCaseData(new ForkActivation(11, HoleskySpecProvider.BPO1Timestamp - 1)) { TestName = "Before BPO1" }; - yield return new TestCaseData(new ForkActivation(12, HoleskySpecProvider.BPO1Timestamp)) { TestName = "BPO1" }; - yield return new TestCaseData(new ForkActivation(13, HoleskySpecProvider.BPO2Timestamp - 1)) { TestName = "Before BPO2" }; - yield return new TestCaseData(new ForkActivation(14, HoleskySpecProvider.BPO2Timestamp)) { TestName = "BPO2" }; - yield return new TestCaseData(new ForkActivation(15, HoleskySpecProvider.BPO2Timestamp + 100000000)) { TestName = "Future BPO2" }; - } - } - - [TestCaseSource(nameof(HoleskyActivations))] - public void Holesky_loads_properly(ForkActivation forkActivation) - { - ChainSpec chainSpec = LoadChainSpecFromChainFolder("holesky"); - ChainSpecBasedSpecProvider provider = new(chainSpec); - ISpecProvider hardCodedSpec = HoleskySpecProvider.Instance; - - CompareSpecs(hardCodedSpec, provider, forkActivation); - using (Assert.EnterMultipleScope()) - { - Assert.That(provider.TerminalTotalDifficulty, Is.EqualTo(hardCodedSpec.TerminalTotalDifficulty)); - Assert.That(provider.GenesisSpec.Eip1559TransitionBlock, Is.Zero); - Assert.That(provider.GenesisSpec.DifficultyBombDelay, Is.Zero); - Assert.That(provider.ChainId, Is.EqualTo(BlockchainIds.Holesky)); - Assert.That(provider.NetworkId, Is.EqualTo(BlockchainIds.Holesky)); - } - - IReleaseSpec postCancunSpec = provider.GetSpec((2, HoleskySpecProvider.CancunTimestamp)); - VerifyCancunSpecificsForMainnetAndHoleskyAndSepolia(postCancunSpec); - - IReleaseSpec postPragueSpec = provider.GetSpec((2, HoleskySpecProvider.PragueTimestamp)); - VerifyPragueSpecificsForMainnetHoleskyHoodiAndSepolia(provider.ChainId, postPragueSpec); - - IReleaseSpec postOsakaSpec = provider.GetSpec((2, HoleskySpecProvider.OsakaTimestamp)); - IReleaseSpec postBPO1Spec = provider.GetSpec((2, HoleskySpecProvider.BPO1Timestamp)); - IReleaseSpec postBPO2Spec = provider.GetSpec((2, HoleskySpecProvider.BPO2Timestamp)); - VerifyOsakaSpecificsForMainnetHoleskyHoodiAndSepolia(provider.ChainId, postOsakaSpec, postBPO1Spec, postBPO2Spec); - - // because genesis time for holesky is set 5 minutes before the launch of the chain. this test fails. - //GetTransitionTimestamps(chainSpec.Parameters).Should().AllSatisfy( - // t => ValidateSlotByTimestamp(t, HoleskySpecProvider.GenesisTimestamp).Should().BeTrue()); - } - - private static void VerifyCancunSpecificsForMainnetAndHoleskyAndSepolia(IReleaseSpec spec) + private static void VerifyCancunSpecificsForMainnetAndSepolia(IReleaseSpec spec) { using (Assert.EnterMultipleScope()) { @@ -266,7 +212,7 @@ private static void VerifyCancunSpecificsForMainnetAndHoleskyAndSepolia(IRelease } } - private static void VerifyPragueSpecificsForMainnetHoleskyHoodiAndSepolia(ulong chainId, IReleaseSpec spec) + private static void VerifyPragueSpecificsForMainnetHoodiAndSepolia(ulong chainId, IReleaseSpec spec) { using (Assert.EnterMultipleScope()) { @@ -292,7 +238,7 @@ private static void VerifyPragueSpecificsForMainnetHoleskyHoodiAndSepolia(ulong expectedDepositContractAddress = Eip6110Constants.SepoliaDepositContractAddress; break; default: - Assert.Fail("Unrecognised chain id when verifying Prague specifics."); + Assert.Fail("Unrecognized chain id when verifying Prague specifics."); return; } @@ -301,7 +247,7 @@ private static void VerifyPragueSpecificsForMainnetHoleskyHoodiAndSepolia(ulong private static void VerifyOsakaSpecificsForMainnetHoleskyHoodiAndSepolia(ulong chainId, IReleaseSpec postOsakaSpec, IReleaseSpec postBPO1Spec, IReleaseSpec postBPO2Spec) { - VerifyPragueSpecificsForMainnetHoleskyHoodiAndSepolia(chainId, postOsakaSpec); + VerifyPragueSpecificsForMainnetHoodiAndSepolia(chainId, postOsakaSpec); using (Assert.EnterMultipleScope()) { Assert.That(postBPO1Spec.BlobBaseFeeUpdateFraction, Is.EqualTo((UInt256)8346193)); @@ -353,10 +299,10 @@ public void Hoodi_loads_properly(ForkActivation forkActivation) } IReleaseSpec postCancunSpec = provider.GetSpec((2, HoodiSpecProvider.CancunTimestamp)); - VerifyCancunSpecificsForMainnetAndHoleskyAndSepolia(postCancunSpec); + VerifyCancunSpecificsForMainnetAndSepolia(postCancunSpec); IReleaseSpec postPragueSpec = provider.GetSpec((2, HoodiSpecProvider.PragueTimestamp)); - VerifyPragueSpecificsForMainnetHoleskyHoodiAndSepolia(provider.ChainId, postPragueSpec); + VerifyPragueSpecificsForMainnetHoodiAndSepolia(provider.ChainId, postPragueSpec); IReleaseSpec postOsakaSpec = provider.GetSpec((2, HoodiSpecProvider.OsakaTimestamp)); IReleaseSpec postBPO1Spec = provider.GetSpec((2, HoodiSpecProvider.BPO1Timestamp)); @@ -375,10 +321,12 @@ public static IEnumerable ChiadoActivations yield return new TestCaseData((ForkActivation)(1, ChiadoSpecProvider.ShanghaiTimestamp)) { TestName = "Shanghai" }; yield return new TestCaseData((ForkActivation)(1, ChiadoSpecProvider.CancunTimestamp - 1)) { TestName = "Before Cancun" }; yield return new TestCaseData((ForkActivation)(1, ChiadoSpecProvider.CancunTimestamp)) { TestName = "Cancun" }; - yield return new TestCaseData((ForkActivation)(1, ChiadoSpecProvider.CancunTimestamp + 100000000)) { TestName = "Future" }; yield return new TestCaseData((ForkActivation)(1, ChiadoSpecProvider.PragueTimestamp - 1)) { TestName = "Before Prague" }; yield return new TestCaseData((ForkActivation)(1, ChiadoSpecProvider.PragueTimestamp)) { TestName = "Prague" }; yield return new TestCaseData((ForkActivation)(1, ChiadoSpecProvider.PragueTimestamp + 100000000)) { TestName = "Future" }; + // yield return new TestCaseData((ForkActivation)(1, ChiadoSpecProvider.OsakaTimestamp - 1)) { TestName = "Before Osaka" }; + // yield return new TestCaseData((ForkActivation)(1, ChiadoSpecProvider.OsakaTimestamp)) { TestName = "Osaka" }; + // yield return new TestCaseData((ForkActivation)(1, ChiadoSpecProvider.OsakaTimestamp + 100000000)) { TestName = "Future" }; } } @@ -404,13 +352,23 @@ public void Chiado_loads_properly(ForkActivation forkActivation) IReleaseSpec? postCancunSpec = provider.GetSpec((1, ChiadoSpecProvider.CancunTimestamp)); IReleaseSpec? prePragueSpec = provider.GetSpec((1, ChiadoSpecProvider.PragueTimestamp - 1)); IReleaseSpec? postPragueSpec = provider.GetSpec((1, ChiadoSpecProvider.PragueTimestamp)); + // IReleaseSpec? postOsakaSpec = provider.GetSpec((1, ChiadoSpecProvider.OsakaTimestamp)); VerifyGnosisShanghaiSpecifics(preShanghaiSpec, postShanghaiSpec); VerifyGnosisCancunSpecifics(postCancunSpec); VerifyGnosisPragueSpecifics(prePragueSpec, postPragueSpec, ChiadoSpecProvider.FeeCollector); - GetTransitionTimestamps(chainSpec.Parameters).Should().AllSatisfy( - static t => ValidateSlotByTimestamp(t, ChiadoSpecProvider.BeaconChainGenesisTimestampConst, GnosisBlockTime).Should().BeTrue()); - Assert.That(postPragueSpec.DepositContractAddress, Is.EqualTo(new Address("0xb97036A26259B7147018913bD58a774cf91acf25"))); + // VerifyGnosisOsakaSpecifics(postOsakaSpec, ChiadoSpecProvider.FeeCollector); + + using (Assert.EnterMultipleScope()) + { + IEnumerable timestamps = GetTransitionTimestamps(chainSpec.Parameters); + foreach (ulong t in timestamps) + { + Assert.That(ValidateSlotByTimestamp(t, ChiadoSpecProvider.BeaconChainGenesisTimestampConst, GnosisBlockTime), Is.True); + } + + Assert.That(postPragueSpec.DepositContractAddress, Is.EqualTo(new Address("0xb97036A26259B7147018913bD58a774cf91acf25"))); + } } public static IEnumerable GnosisActivations @@ -419,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" }; @@ -474,9 +432,28 @@ public void Gnosis_loads_properly(ForkActivation forkActivation) VerifyGnosisShanghaiSpecifics(preShanghaiSpec, postShanghaiSpec); VerifyGnosisCancunSpecifics(postCancunSpec); VerifyGnosisPragueSpecifics(prePragueSpec, postPragueSpec, GnosisSpecProvider.FeeCollector); - Assert.That(postPragueSpec.DepositContractAddress, Is.EqualTo(new Address("0x0B98057eA310F4d31F2a452B414647007d1645d9"))); - GetTransitionTimestamps(chainSpec.Parameters).Should().AllSatisfy( - static t => ValidateSlotByTimestamp(t, GnosisSpecProvider.BeaconChainGenesisTimestampConst, GnosisBlockTime).Should().BeTrue()); + + using (Assert.EnterMultipleScope()) + { + Assert.That(postPragueSpec.DepositContractAddress, Is.EqualTo(new Address("0x0B98057eA310F4d31F2a452B414647007d1645d9"))); + + IEnumerable timestamps = GetTransitionTimestamps(chainSpec.Parameters); + foreach (ulong t in timestamps) + { + Assert.That(ValidateSlotByTimestamp(t, GnosisSpecProvider.BeaconChainGenesisTimestampConst, GnosisBlockTime), Is.True); + } + } + } + + private static void VerifyGnosisOsakaSpecifics(IReleaseSpec spec, Address feeCollector) + { + using (Assert.EnterMultipleScope()) + { + Assert.That(spec.FeeCollector, Is.EqualTo(feeCollector)); + Assert.That(spec.IsEip4844FeeCollectorEnabled, Is.True); + } + + VerifyGnosisCancunSpecifics(spec); } private static void VerifyGnosisPragueSpecifics(IReleaseSpec prePragueSpec, IReleaseSpec postPragueSpec, Address feeCollector) @@ -485,8 +462,8 @@ private static void VerifyGnosisPragueSpecifics(IReleaseSpec prePragueSpec, IRel { Assert.That(prePragueSpec.FeeCollector, Is.EqualTo(feeCollector)); Assert.That(postPragueSpec.FeeCollector, Is.EqualTo(feeCollector)); - Assert.That(prePragueSpec.IsEip4844FeeCollectorEnabled, Is.EqualTo(false)); - Assert.That(postPragueSpec.IsEip4844FeeCollectorEnabled, Is.EqualTo(true)); + Assert.That(prePragueSpec.IsEip4844FeeCollectorEnabled, Is.False); + Assert.That(postPragueSpec.IsEip4844FeeCollectorEnabled, Is.True); Assert.That(postPragueSpec.Eip2935ContractAddress, Is.EqualTo(Eip2935Constants.BlockHashHistoryAddress)); } @@ -507,30 +484,32 @@ private static void VerifyGnosisCancunSpecifics(IReleaseSpec spec) private static void VerifyGnosisShanghaiSpecifics(IReleaseSpec preShanghaiSpec, IReleaseSpec postShanghaiSpec) { - preShanghaiSpec.MaxCodeSize.Should().Be(long.MaxValue); - postShanghaiSpec.MaxCodeSize.Should().Be(CodeSizeConstants.MaxCodeSizeEip170); + using (Assert.EnterMultipleScope()) + { + Assert.That(preShanghaiSpec.MaxCodeSize, Is.EqualTo(long.MaxValue)); + Assert.That(postShanghaiSpec.MaxCodeSize, Is.EqualTo(CodeSizeConstants.MaxCodeSizeEip170)); - preShanghaiSpec.MaxInitCodeSize.Should().Be(-2L); // doesn't have meaningful value before EIP3860 - postShanghaiSpec.MaxInitCodeSize.Should().Be(2 * CodeSizeConstants.MaxCodeSizeEip170); + Assert.That(preShanghaiSpec.MaxInitCodeSize, Is.EqualTo(-2L)); // doesn't have meaningful value before EIP3860 + Assert.That(postShanghaiSpec.MaxInitCodeSize, Is.EqualTo(2 * CodeSizeConstants.MaxCodeSizeEip170)); - preShanghaiSpec.LimitCodeSize.Should().Be(false); - postShanghaiSpec.LimitCodeSize.Should().Be(true); + Assert.That(preShanghaiSpec.LimitCodeSize, Is.False); + Assert.That(postShanghaiSpec.LimitCodeSize, Is.True); - preShanghaiSpec.IsEip170Enabled.Should().Be(false); - postShanghaiSpec.IsEip170Enabled.Should().Be(true); + Assert.That(preShanghaiSpec.IsEip170Enabled, Is.False); + Assert.That(postShanghaiSpec.IsEip170Enabled, Is.True); + } } private static void VerifyGnosisPreShanghaiSpecifics(ISpecProvider specProvider) { - specProvider.GenesisSpec.MaximumUncleCount.Should().Be(0); - specProvider.GetSpec((ForkActivation)(GnosisSpecProvider.ConstantinopoleBlockNumber - 1)).IsEip1283Enabled.Should() - .BeFalse(); - specProvider.GetSpec((ForkActivation)GnosisSpecProvider.ConstantinopoleBlockNumber).IsEip1283Enabled.Should() - .BeTrue(); - specProvider.GetSpec((ForkActivation)(GnosisSpecProvider.ConstantinopoleBlockNumber - 1)).UseConstantinopleNetGasMetering.Should() - .BeFalse(); - specProvider.GetSpec((ForkActivation)GnosisSpecProvider.ConstantinopoleBlockNumber).UseConstantinopleNetGasMetering.Should() - .BeTrue(); + using (Assert.EnterMultipleScope()) + { + Assert.That(specProvider.GenesisSpec.MaximumUncleCount, Is.Zero); + 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); + } } public static IEnumerable MainnetActivations @@ -568,7 +547,13 @@ public static IEnumerable MainnetActivations yield return new TestCaseData(MainnetSpecProvider.CancunActivation) { TestName = "Cancun" }; yield return new TestCaseData(new ForkActivation(MainnetSpecProvider.ParisBlockNumber, MainnetSpecProvider.PragueBlockTimestamp - 1)) { TestName = "Before Prague" }; yield return new TestCaseData(MainnetSpecProvider.PragueActivation) { TestName = "Prague" }; - yield return new TestCaseData(new ForkActivation(MainnetSpecProvider.ParisBlockNumber, MainnetSpecProvider.PragueBlockTimestamp + 100000000)) { TestName = "Future" }; + yield return new TestCaseData(new ForkActivation(MainnetSpecProvider.ParisBlockNumber, MainnetSpecProvider.OsakaBlockTimestamp - 1)) { TestName = "Before Osaka" }; + yield return new TestCaseData(MainnetSpecProvider.OsakaActivation) { TestName = "Osaka" }; + yield return new TestCaseData(new ForkActivation(MainnetSpecProvider.ParisBlockNumber, MainnetSpecProvider.BPO1BlockTimestamp - 1)) { TestName = "Before BPO1" }; + yield return new TestCaseData(MainnetSpecProvider.BPO1Activation) { TestName = "BPO1" }; + yield return new TestCaseData(new ForkActivation(MainnetSpecProvider.ParisBlockNumber, MainnetSpecProvider.BPO2BlockTimestamp - 1)) { TestName = "Before BPO2" }; + yield return new TestCaseData(MainnetSpecProvider.BPO2Activation) { TestName = "BPO2" }; + yield return new TestCaseData(new ForkActivation(MainnetSpecProvider.ParisBlockNumber, MainnetSpecProvider.BPO2BlockTimestamp + 100000000)) { TestName = "Future" }; } } @@ -580,13 +565,12 @@ public void Mainnet_loads_properly(ForkActivation forkActivation) MainnetSpecProvider mainnet = MainnetSpecProvider.Instance; CompareSpecs(mainnet, provider, forkActivation, CompareSpecsOptions.CheckDifficultyBomb); - provider.GetSpec((MainnetSpecProvider.SpuriousDragonBlockNumber, null)).MaxCodeSize.Should().Be(CodeSizeConstants.MaxCodeSizeEip170); - provider.GetSpec((MainnetSpecProvider.SpuriousDragonBlockNumber, null)).MaxInitCodeSize.Should().Be(2 * CodeSizeConstants.MaxCodeSizeEip170); - - provider.GetSpec((ForkActivation)(long.MaxValue - 1)).IsEip2537Enabled.Should().BeFalse(); using (Assert.EnterMultipleScope()) { + Assert.That(provider.GetSpec((MainnetSpecProvider.SpuriousDragonBlockNumber, null)).MaxCodeSize, Is.EqualTo(CodeSizeConstants.MaxCodeSizeEip170)); + Assert.That(provider.GetSpec((MainnetSpecProvider.SpuriousDragonBlockNumber, null)).MaxInitCodeSize, Is.EqualTo(2 * CodeSizeConstants.MaxCodeSizeEip170)); + Assert.That(provider.GetSpec((ForkActivation)(long.MaxValue - 1)).IsEip2537Enabled, Is.False); Assert.That(provider.GenesisSpec.Eip1559TransitionBlock, Is.EqualTo(MainnetSpecProvider.LondonBlockNumber)); Assert.That(provider.GetSpec((ForkActivation)4_369_999).DifficultyBombDelay, Is.EqualTo(0_000_000)); Assert.That(provider.GetSpec((ForkActivation)4_370_000).DifficultyBombDelay, Is.EqualTo(3_000_000)); @@ -606,15 +590,23 @@ public void Mainnet_loads_properly(ForkActivation forkActivation) Assert.That(provider.TerminalTotalDifficulty, Is.EqualTo(MainnetSpecProvider.Instance.TerminalTotalDifficulty)); Assert.That(provider.ChainId, Is.EqualTo(BlockchainIds.Mainnet)); Assert.That(provider.NetworkId, Is.EqualTo(BlockchainIds.Mainnet)); + + IEnumerable timestamps = GetTransitionTimestamps(chainSpec.Parameters); + foreach (ulong t in timestamps) + { + Assert.That(ValidateSlotByTimestamp(t, MainnetSpecProvider.BeaconChainGenesisTimestampConst), Is.True); + } } - GetTransitionTimestamps(chainSpec.Parameters).Should().AllSatisfy( - static t => ValidateSlotByTimestamp(t, MainnetSpecProvider.BeaconChainGenesisTimestampConst).Should().BeTrue()); IReleaseSpec postCancunSpec = provider.GetSpec(MainnetSpecProvider.CancunActivation); IReleaseSpec postPragueSpec = provider.GetSpec(MainnetSpecProvider.PragueActivation); + IReleaseSpec postOsakaSpec = provider.GetSpec(MainnetSpecProvider.OsakaActivation); + IReleaseSpec postBPO1Spec = provider.GetSpec(MainnetSpecProvider.BPO1Activation); + IReleaseSpec postBPO2Spec = provider.GetSpec(MainnetSpecProvider.BPO2Activation); - VerifyCancunSpecificsForMainnetAndHoleskyAndSepolia(postCancunSpec); - VerifyPragueSpecificsForMainnetHoleskyHoodiAndSepolia(provider.ChainId, postPragueSpec); + VerifyCancunSpecificsForMainnetAndSepolia(postCancunSpec); + VerifyPragueSpecificsForMainnetHoodiAndSepolia(provider.ChainId, postPragueSpec); + VerifyOsakaSpecificsForMainnetHoleskyHoodiAndSepolia(provider.ChainId, postOsakaSpec, postBPO1Spec, postBPO2Spec); } [Flags] @@ -668,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); @@ -682,17 +671,22 @@ private static void CompareSpecs(IReleaseSpec expectedSpec, IReleaseSpec actualS private ChainSpec LoadChainSpecFromChainFolder(string chain) { - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance); string path = Path.Combine(TestContext.CurrentContext.WorkDirectory, $"../../../../Chains/{chain}.json"); - var chainSpec = loader.LoadEmbeddedOrFromFile(path); + ChainSpec chainSpec = loader.LoadEmbeddedOrFromFile(path); return chainSpec; } [Test] public void Chain_id_is_set_correctly() { - ChainSpec chainSpec = new() { Parameters = new ChainParameters(), NetworkId = 2, ChainId = 5 }; - chainSpec.EngineChainSpecParametersProvider = TestChainSpecParametersProvider.NethDev; + ChainSpec chainSpec = new() + { + Parameters = new ChainParameters(), + NetworkId = 2, + ChainId = 5, + EngineChainSpecParametersProvider = TestChainSpecParametersProvider.NethDev + }; ChainSpecBasedSpecProvider provider = new(chainSpec); using (Assert.EnterMultipleScope()) @@ -705,10 +699,12 @@ public void Chain_id_is_set_correctly() [Test] public void Dao_block_number_is_set_correctly() { - ChainSpec chainSpec = new(); - chainSpec.EngineChainSpecParametersProvider = TestChainSpecParametersProvider.NethDev; - chainSpec.Parameters = new ChainParameters(); - chainSpec.DaoForkBlockNumber = 23; + ChainSpec chainSpec = new() + { + EngineChainSpecParametersProvider = TestChainSpecParametersProvider.NethDev, + Parameters = new ChainParameters(), + DaoForkBlockNumber = 23 + }; ChainSpecBasedSpecProvider provider = new(chainSpec); Assert.That(provider.DaoBlockNumber, Is.EqualTo(23)); @@ -726,9 +722,9 @@ public void Max_code_transition_loaded_correctly() { MaxCodeSizeTransition = maxCodeTransition, MaxCodeSize = maxCodeSize - } + }, + EngineChainSpecParametersProvider = TestChainSpecParametersProvider.NethDev }; - chainSpec.EngineChainSpecParametersProvider = TestChainSpecParametersProvider.NethDev; ChainSpecBasedSpecProvider provider = new(chainSpec); using (Assert.EnterMultipleScope()) @@ -742,22 +738,27 @@ public void Max_code_transition_loaded_correctly() [Test] public void Eip2200_is_set_correctly_directly() { - ChainSpec chainSpec = new() { Parameters = new ChainParameters { Eip2200Transition = 5 } }; - chainSpec.EngineChainSpecParametersProvider = TestChainSpecParametersProvider.NethDev; + ChainSpec chainSpec = new() + { + Parameters = new ChainParameters { Eip2200Transition = 5 }, + EngineChainSpecParametersProvider = TestChainSpecParametersProvider.NethDev + }; ChainSpecBasedSpecProvider provider = new(chainSpec); - provider.GetSpec((ForkActivation)5).IsEip2200Enabled.Should().BeTrue(); + Assert.That(provider.GetSpec((ForkActivation)5).IsEip2200Enabled, Is.True); } [Test] public void Eip2200_is_set_correctly_indirectly() { - ChainSpec chainSpec = - new() { Parameters = new ChainParameters { Eip1706Transition = 5, Eip1283Transition = 5 } }; - chainSpec.EngineChainSpecParametersProvider = TestChainSpecParametersProvider.NethDev; + ChainSpec chainSpec = new() + { + Parameters = new ChainParameters { Eip1706Transition = 5, Eip1283Transition = 5 }, + EngineChainSpecParametersProvider = TestChainSpecParametersProvider.NethDev + }; ChainSpecBasedSpecProvider provider = new(chainSpec); - provider.GetSpec((ForkActivation)5).IsEip2200Enabled.Should().BeTrue(); + Assert.That(provider.GetSpec((ForkActivation)5).IsEip2200Enabled, Is.True); } [Test] @@ -771,12 +772,12 @@ public void Eip2200_is_set_correctly_indirectly_after_disabling_eip1283_and_reen Eip1283Transition = 1, Eip1283DisableTransition = 4, Eip1283ReenableTransition = 5 - } + }, + EngineChainSpecParametersProvider = TestChainSpecParametersProvider.NethDev }; - chainSpec.EngineChainSpecParametersProvider = TestChainSpecParametersProvider.NethDev; ChainSpecBasedSpecProvider provider = new(chainSpec); - provider.GetSpec((ForkActivation)5).IsEip2200Enabled.Should().BeTrue(); + Assert.That(provider.GetSpec((ForkActivation)5).IsEip2200Enabled, Is.True); } [Test] @@ -789,12 +790,12 @@ public void Eip2200_is_not_set_correctly_indirectly_after_disabling_eip1283() Eip1706Transition = 5, Eip1283Transition = 1, Eip1283DisableTransition = 4 - } + }, + EngineChainSpecParametersProvider = TestChainSpecParametersProvider.NethDev }; - chainSpec.EngineChainSpecParametersProvider = TestChainSpecParametersProvider.NethDev; ChainSpecBasedSpecProvider provider = new(chainSpec); - provider.GetSpec((ForkActivation)5).IsEip2200Enabled.Should().BeFalse(); + Assert.That(provider.GetSpec((ForkActivation)5).IsEip2200Enabled, Is.False); } [Test] @@ -807,21 +808,23 @@ public void Eip150_and_Eip2537_fork_by_block_number() MaxCodeSizeTransition = 10, Eip2537Transition = 20, MaxCodeSize = 1 - } + }, + EngineChainSpecParametersProvider = TestChainSpecParametersProvider.NethDev }; - chainSpec.EngineChainSpecParametersProvider = TestChainSpecParametersProvider.NethDev; ChainSpecBasedSpecProvider provider = new(chainSpec); - provider.GetSpec((ForkActivation)9).IsEip170Enabled.Should().BeFalse(); - provider.GetSpec((ForkActivation)10).IsEip170Enabled.Should().BeTrue(); - provider.GetSpec((ForkActivation)11).IsEip170Enabled.Should().BeTrue(); - provider.GetSpec((ForkActivation)11).MaxCodeSize.Should().Be(1); - provider.GetSpec((ForkActivation)9).MaxCodeSize.Should().Be(long.MaxValue); - - provider.GetSpec((ForkActivation)19).IsEip2537Enabled.Should().BeFalse(); - provider.GetSpec((ForkActivation)20).IsEip2537Enabled.Should().BeTrue(); - provider.GetSpec((ForkActivation)21).IsEip2537Enabled.Should().BeTrue(); + using (Assert.EnterMultipleScope()) + { + Assert.That(provider.GetSpec((ForkActivation)9).IsEip170Enabled, Is.False); + Assert.That(provider.GetSpec((ForkActivation)10).IsEip170Enabled, Is.True); + Assert.That(provider.GetSpec((ForkActivation)11).IsEip170Enabled, Is.True); + Assert.That(provider.GetSpec((ForkActivation)11).MaxCodeSize, Is.EqualTo(1)); + Assert.That(provider.GetSpec((ForkActivation)9).MaxCodeSize, Is.EqualTo(long.MaxValue)); + Assert.That(provider.GetSpec((ForkActivation)19).IsEip2537Enabled, Is.False); + Assert.That(provider.GetSpec((ForkActivation)20).IsEip2537Enabled, Is.True); + Assert.That(provider.GetSpec((ForkActivation)21).IsEip2537Enabled, Is.True); + } } [Test] @@ -834,21 +837,23 @@ public void Eip150_and_Eip2537_fork_by_timestamp() MaxCodeSizeTransitionTimestamp = 10, Eip2537TransitionTimestamp = 20, MaxCodeSize = 1 - } + }, + EngineChainSpecParametersProvider = TestChainSpecParametersProvider.NethDev }; - chainSpec.EngineChainSpecParametersProvider = TestChainSpecParametersProvider.NethDev; ChainSpecBasedSpecProvider provider = new(chainSpec); - provider.GetSpec((100, 9)).IsEip170Enabled.Should().BeFalse(); - provider.GetSpec((100, 10)).IsEip170Enabled.Should().BeTrue(); - provider.GetSpec((100, 11)).IsEip170Enabled.Should().BeTrue(); - provider.GetSpec((100, 11)).MaxCodeSize.Should().Be(1); - provider.GetSpec((100, 9)).MaxCodeSize.Should().Be(long.MaxValue); - - provider.GetSpec((100, 19)).IsEip2537Enabled.Should().BeFalse(); - provider.GetSpec((100, 20)).IsEip2537Enabled.Should().BeTrue(); - provider.GetSpec((100, 21)).IsEip2537Enabled.Should().BeTrue(); + using (Assert.EnterMultipleScope()) + { + Assert.That(provider.GetSpec((100, 9)).IsEip170Enabled, Is.False); + Assert.That(provider.GetSpec((100, 10)).IsEip170Enabled, Is.True); + Assert.That(provider.GetSpec((100, 11)).IsEip170Enabled, Is.True); + Assert.That(provider.GetSpec((100, 11)).MaxCodeSize, Is.EqualTo(1)); + Assert.That(provider.GetSpec((100, 9)).MaxCodeSize, Is.EqualTo(long.MaxValue)); + Assert.That(provider.GetSpec((100, 19)).IsEip2537Enabled, Is.False); + Assert.That(provider.GetSpec((100, 20)).IsEip2537Enabled, Is.True); + Assert.That(provider.GetSpec((100, 21)).IsEip2537Enabled, Is.True); + } } [TestCaseSource(nameof(BlockNumbersAndTimestampsNearForkActivations))] @@ -969,7 +974,7 @@ static TestCaseData MakeTestCase(string testName, int eip4844Timestamp, int eip7 } private static IEnumerable GetTransitionTimestamps(ChainParameters parameters) => parameters.GetType() - .Properties() + .GetProperties() .Where(p => p.Name.EndsWith("TransitionTimestamp", StringComparison.Ordinal)) .Select(p => (ulong?)p.GetValue(parameters)) .Where(t => t is not null) diff --git a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecLoaderTests.cs b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecLoaderTests.cs index 7021af806999..62e79d257299 100644 --- a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecLoaderTests.cs +++ b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecLoaderTests.cs @@ -18,7 +18,7 @@ public class ChainSpecLoaderTests { private static ChainSpec LoadChainSpec(string path) { - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance); var chainSpec = loader.LoadEmbeddedOrFromFile(path); return chainSpec; } @@ -94,32 +94,6 @@ public void Can_load_sepolia() chainSpec.ShanghaiTimestamp.Should().Be(1677557088); } - [Test] - public void Can_load_holesky() - { - string path = Path.Combine(TestContext.CurrentContext.WorkDirectory, "../../../../", "Chains/holesky.json"); - ChainSpec chainSpec = LoadChainSpec(path); - - Assert.That(chainSpec.NetworkId, Is.EqualTo(17000), $"{nameof(chainSpec.NetworkId)}"); - Assert.That(chainSpec.Name, Is.EqualTo("Holesky Testnet"), $"{nameof(chainSpec.Name)}"); - Assert.That(chainSpec.DataDir, Is.EqualTo("holesky"), $"{nameof(chainSpec.DataDir)}"); - Assert.That(chainSpec.SealEngineType, Is.EqualTo(SealEngineType.Ethash), "engine"); - - chainSpec.DaoForkBlockNumber.Should().Be(null); - chainSpec.TangerineWhistleBlockNumber.Should().Be(0); - chainSpec.SpuriousDragonBlockNumber.Should().Be(0); - chainSpec.ByzantiumBlockNumber.Should().Be(0); - chainSpec.ConstantinopleBlockNumber.Should().Be(0); - chainSpec.ConstantinopleFixBlockNumber.Should().Be(0); - chainSpec.IstanbulBlockNumber.Should().Be(0); - chainSpec.BerlinBlockNumber.Should().Be(0); - chainSpec.LondonBlockNumber.Should().Be(0); - chainSpec.ShanghaiTimestamp.Should().Be(HoleskySpecProvider.ShanghaiTimestamp); - chainSpec.ShanghaiTimestamp.Should().Be(HoleskySpecProvider.Instance.TimestampFork); - // chainSpec.CancunTimestamp.Should().Be(HoleskySpecProvider.CancunTimestamp); - } - - [Test] public void Can_load_hoodi() { diff --git a/src/Nethermind/Nethermind.Specs.Test/Nethermind.Specs.Test.csproj b/src/Nethermind/Nethermind.Specs.Test/Nethermind.Specs.Test.csproj index 5763bf5296e1..d6fc33aed5d9 100644 --- a/src/Nethermind/Nethermind.Specs.Test/Nethermind.Specs.Test.csproj +++ b/src/Nethermind/Nethermind.Specs.Test/Nethermind.Specs.Test.csproj @@ -1,4 +1,4 @@ - + @@ -12,6 +12,7 @@ + @@ -21,10 +22,9 @@ PreserveNewest - - - - + + PreserveNewest + PreserveNewest diff --git a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs index fbac042868e5..b70d3abed3c0 100644 --- a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs @@ -16,189 +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 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; - } - } - - public ulong TargetBlobCount => spec.TargetBlobCount; - public ulong MaxBlobCount => spec.MaxBlobCount; - public ulong MaxBlobsPerTx => spec.MaxBlobsPerTx; - public UInt256 BlobBaseFeeUpdateFraction => spec.BlobBaseFeeUpdateFraction; - 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; FrozenSet IReleaseSpec.Precompiles => spec.Precompiles; } } diff --git a/src/Nethermind/Nethermind.Specs.Test/OverridableSpecProvider.cs b/src/Nethermind/Nethermind.Specs.Test/OverridableSpecProvider.cs index 4a5699e4c18e..14abbe558432 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.Test/Specs/holesky_missing_deposit_contract.json b/src/Nethermind/Nethermind.Specs.Test/Specs/holesky_missing_deposit_contract.json deleted file mode 100644 index a494c60f15ed..000000000000 --- a/src/Nethermind/Nethermind.Specs.Test/Specs/holesky_missing_deposit_contract.json +++ /dev/null @@ -1,1077 +0,0 @@ -{ - "name": "Holesky Testnet", - "dataDir": "holesky", - "engine": { - "Ethash": {} - }, - "params": { - "accountStartNonce": "0x0", - "chainID": "0x4268", - "networkID": "0x4268", - "eip140Transition": "0x0", - "eip145Transition": "0x0", - "eip150Transition": "0x0", - "eip155Transition": "0x0", - "eip160Transition": "0x0", - "eip161abcTransition": "0x0", - "eip161dTransition": "0x0", - "eip211Transition": "0x0", - "eip214Transition": "0x0", - "eip658Transition": "0x0", - "eip1014Transition": "0x0", - "eip1052Transition": "0x0", - "eip1283Transition": "0x0", - "eip1283DisableTransition": "0x0", - "eip152Transition": "0x0", - "eip1108Transition": "0x0", - "eip1344Transition": "0x0", - "eip1884Transition": "0x0", - "eip2028Transition": "0x0", - "eip2200Transition": "0x0", - "eip2565Transition": "0x0", - "eip2929Transition": "0x0", - "eip2930Transition": "0x0", - "eip1559Transition": "0x0", - "eip3198Transition": "0x0", - "eip3529Transition": "0x0", - "eip3541Transition": "0x0", - "beaconChainGenesisTimestamp": "0x65156994", - "eip3651TransitionTimestamp": "0x6516eac0", - "eip3855TransitionTimestamp": "0x6516eac0", - "eip3860TransitionTimestamp": "0x6516eac0", - "eip4895TransitionTimestamp": "0x6516eac0", - "eip1153TransitionTimestamp": "0x65C36AC0", - "eip4788TransitionTimestamp": "0x65C36AC0", - "eip4844TransitionTimestamp": "0x65C36AC0", - "eip5656TransitionTimestamp": "0x65C36AC0", - "eip6780TransitionTimestamp": "0x65C36AC0", - "eip2537TransitionTimestamp": "0x67BCEAC0", - "eip2935TransitionTimestamp": "0x67BCEAC0", - "eip6110TransitionTimestamp": "0x67BCEAC0", - "eip7002TransitionTimestamp": "0x67BCEAC0", - "eip7251TransitionTimestamp": "0x67BCEAC0", - "eip7623TransitionTimestamp": "0x67BCEAC0", - "eip7702TransitionTimestamp": "0x67BCEAC0", - "terminalTotalDifficulty": "0x0", - "gasLimitBoundDivisor": "0x400", - "maxCodeSize": "0x6000", - "maxCodeSizeTransition": "0x0", - "maximumExtraDataSize": "0xffff", - "minGasLimit": "0x1388", - "registrar": "0x0000000000000000000000000000000000000000", - "MergeForkIdTransition": "0x0", - "blobSchedule": [ - { - "timestamp": "0x67BCEAC0", - "target": 6, - "max": 9, - "baseFeeUpdateFraction": "0x4c6964" - } - ] - }, - "genesis": { - "seal": { - "ethereum": { - "nonce": "0x1234", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" - } - }, - "difficulty": "0x01", - "author": "0x0000000000000000000000000000000000000000", - "extraData": "", - "gasLimit": "0x17D7840", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "timestamp": "0x65156994" - }, - "nodes": [ - "enode://ac906289e4b7f12df423d654c5a962b6ebe5b3a74cc9e06292a85221f9a64a6f1cfdd6b714ed6dacef51578f92b34c60ee91e9ede9c7f8fadc4d347326d95e2b@146.190.13.128:30303", - "enode://a3435a0155a3e837c02f5e7f5662a2f1fbc25b48e4dc232016e1c51b544cb5b4510ef633ea3278c0e970fa8ad8141e2d4d0f9f95456c537ff05fdf9b31c15072@178.128.136.233:30303" - ], - "accounts": { - "0x0000000000000000000000000000000000000000": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000001": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000002": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000003": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000004": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000005": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000006": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000007": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000008": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000009": { - "balance": "1" - }, - "0x000000000000000000000000000000000000000a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000000c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000000d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000000e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000000f": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000010": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000011": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000012": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000013": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000014": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000015": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000016": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000017": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000018": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000019": { - "balance": "1" - }, - "0x000000000000000000000000000000000000001a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000001b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000001c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000001d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000001e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000001f": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000020": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000021": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000022": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000023": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000024": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000025": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000026": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000027": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000028": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000029": { - "balance": "1" - }, - "0x000000000000000000000000000000000000002a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000002b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000002c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000002d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000002e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000002f": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000030": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000031": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000032": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000033": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000034": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000035": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000036": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000037": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000038": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000039": { - "balance": "1" - }, - "0x000000000000000000000000000000000000003a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000003b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000003c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000003d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000003e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000003f": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000040": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000041": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000042": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000043": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000044": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000045": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000046": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000047": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000048": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000049": { - "balance": "1" - }, - "0x000000000000000000000000000000000000004a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000004b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000004c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000004d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000004e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000004f": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000050": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000051": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000052": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000053": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000054": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000055": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000056": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000057": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000058": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000059": { - "balance": "1" - }, - "0x000000000000000000000000000000000000005a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000005b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000005c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000005d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000005e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000005f": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000060": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000061": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000062": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000063": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000064": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000065": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000066": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000067": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000068": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000069": { - "balance": "1" - }, - "0x000000000000000000000000000000000000006a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000006b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000006c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000006d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000006e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000006f": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000070": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000071": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000072": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000073": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000074": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000075": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000076": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000077": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000078": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000079": { - "balance": "1" - }, - "0x000000000000000000000000000000000000007a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000007b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000007c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000007d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000007e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000007f": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000080": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000081": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000082": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000083": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000084": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000085": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000086": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000087": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000088": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000089": { - "balance": "1" - }, - "0x000000000000000000000000000000000000008a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000008b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000008c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000008d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000008e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000008f": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000090": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000091": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000092": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000093": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000094": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000095": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000096": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000097": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000098": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000099": { - "balance": "1" - }, - "0x000000000000000000000000000000000000009a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000009b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000009c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000009d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000009e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000009f": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a0": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a1": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a2": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a3": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a4": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a5": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a6": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a7": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a8": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a9": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000aa": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ab": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ac": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ad": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ae": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000af": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b0": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b1": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b2": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b3": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b4": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b5": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b6": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b7": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b8": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b9": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ba": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000bb": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000bc": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000bd": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000be": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000bf": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c0": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c1": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c2": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c3": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c4": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c5": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c6": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c7": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c8": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c9": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ca": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000cb": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000cc": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000cd": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ce": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000cf": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d0": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d1": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d2": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d3": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d4": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d5": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d6": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d7": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d8": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d9": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000da": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000db": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000dc": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000dd": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000de": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000df": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e0": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e1": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e2": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e3": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e4": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e5": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e6": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e7": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e8": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e9": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ea": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000eb": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ec": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ed": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ee": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ef": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f0": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f1": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f2": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f3": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f4": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f5": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f6": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f7": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f8": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f9": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000fa": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000fb": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000fc": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000fd": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000fe": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ff": { - "balance": "1" - }, - "0x4242424242424242424242424242424242424242": { - "balance": "0", - "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a26469706673582212201dd26f37a621703009abf16e77e69c93dc50c79db7f6cc37543e3e0e3decdc9764736f6c634300060b0033", - "storage": { - "0x0000000000000000000000000000000000000000000000000000000000000022": "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", - "0x0000000000000000000000000000000000000000000000000000000000000023": "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", - "0x0000000000000000000000000000000000000000000000000000000000000024": "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", - "0x0000000000000000000000000000000000000000000000000000000000000025": "0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c", - "0x0000000000000000000000000000000000000000000000000000000000000026": "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", - "0x0000000000000000000000000000000000000000000000000000000000000027": "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", - "0x0000000000000000000000000000000000000000000000000000000000000028": "0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c", - "0x0000000000000000000000000000000000000000000000000000000000000029": "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", - "0x000000000000000000000000000000000000000000000000000000000000002a": "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", - "0x000000000000000000000000000000000000000000000000000000000000002b": "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", - "0x000000000000000000000000000000000000000000000000000000000000002c": "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", - "0x000000000000000000000000000000000000000000000000000000000000002d": "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", - "0x000000000000000000000000000000000000000000000000000000000000002e": "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", - "0x000000000000000000000000000000000000000000000000000000000000002f": "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", - "0x0000000000000000000000000000000000000000000000000000000000000030": "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", - "0x0000000000000000000000000000000000000000000000000000000000000031": "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", - "0x0000000000000000000000000000000000000000000000000000000000000032": "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", - "0x0000000000000000000000000000000000000000000000000000000000000033": "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", - "0x0000000000000000000000000000000000000000000000000000000000000034": "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", - "0x0000000000000000000000000000000000000000000000000000000000000035": "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", - "0x0000000000000000000000000000000000000000000000000000000000000036": "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", - "0x0000000000000000000000000000000000000000000000000000000000000037": "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", - "0x0000000000000000000000000000000000000000000000000000000000000038": "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", - "0x0000000000000000000000000000000000000000000000000000000000000039": "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", - "0x000000000000000000000000000000000000000000000000000000000000003a": "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", - "0x000000000000000000000000000000000000000000000000000000000000003b": "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", - "0x000000000000000000000000000000000000000000000000000000000000003c": "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", - "0x000000000000000000000000000000000000000000000000000000000000003d": "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", - "0x000000000000000000000000000000000000000000000000000000000000003e": "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", - "0x000000000000000000000000000000000000000000000000000000000000003f": "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", - "0x0000000000000000000000000000000000000000000000000000000000000040": "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7" - } - }, - "0x0000006916a87b82333f4245046623b23794C65C": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0x0be949928Ff199c9EBA9E110db210AA5C94EFAd0": { - "balance": "0x7c13bc4b2c133c56000000" - }, - "0x0C100000006d7b5e23a1eAEE637f28cA32Cd5b31": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0x0C35317B7a96C454E2CB3d1A255D775Ab112cCc8": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x0d731cfabC5574329823F26d488416451d2ea376": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x0e79065B5F11b5BD1e62B935A600976ffF3754B9": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x105083929bF9bb22C26cB1777Ec92661170D4285": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x10F5d45854e038071485AC9e402308cF80D2d2fE": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0x1268AD189526AC0b386faF06eFfC46779c340eE6": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x12Cba59f5A74DB81a12ff63C349Bd82CBF6007C2": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x1446D7f6dF00380F246d8211dE7f0FaBC4Fd248C": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x164e38a375247A784A81d420201AA8fe4E513921": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x1B7aA44088a0eA95bdc65fef6E5071E946Bf7d8f": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0x222222222222cF64a76AE3d36859958c864fDA2c": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x2f14582947E292a2eCd20C430B46f2d27CFE213c": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0x2f2c75B5Dd5D246194812b00eEb3B09c2c66e2eE": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0x341c40b94bf2afbfa42573cb78f16ee15a056238": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x34f845773D4364999f2fbC7AA26ABDeE902cBb46": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x3C75594181e03E8ECD8468A0037F058a9dAfad79": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x462396E69dBfa455F405f4DD82F3014Af8003B72": { - "balance": "0xa56fa5b99019a5c8000000" - }, - "0x49Df3CCa2670eB0D591146B16359fe336e476F29": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x4D0b04b405c6b62C7cFC3aE54759747e2C0b4662": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x4D496CcC28058B1D74B7a19541663E21154f9c84": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0x509a7667aC8D0320e36172c192506a6188aA84f6": { - "balance": "0x7c13bc4b2c133c56000000" - }, - "0x5180db0237291A6449DdA9ed33aD90a38787621c": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x52730f347dEf6BA09adfF62EaC60D5fEe8205BC4": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x5EAC0fBd3dfef8aE3efa3c5dc1aa193bc6033dFd": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x6a7aA9b882d50Bb7bc5Da1a244719C99f12F06a3": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0x6Cc9397c3B38739daCbfaA68EaD5F5D77Ba5F455": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0x762cA62ca2549ad806763B3Aa1eA317c429bDBDa": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x778F5F13C4Be78A3a4d7141BCB26999702f407CF": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0x875D25Ee4bC604C71BaF6236a8488F22399BED4b": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x8dF7878d3571BEF5e5a744F96287C8D20386d75A": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0x9E415A096fF77650dc925dEA546585B4adB322B6": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xA0766B65A4f7B1da79a1AF79aC695456eFa28644": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xA29B144A449E414A472c60C7AAf1aaFfE329021D": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xa55395566b0b54395B3246f96A0bDc4b8a483df9": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xAC9ba72fb61aA7c31A95df0A8b6ebA6f41EF875e": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xB0498C15879db2eE5471d4926c5fAA25C9a09683": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xB19Fb4c1f280327e60Ed37b1Dc6EE77533539314": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0xC21cB9C99C316d1863142F7dD86dd5496D81A8D6": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xc473d412dc52e349862209924c8981b2ee420768": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xC48E23C5F6e1eA0BaEf6530734edC3968f79Af2e": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0xc6e2459991BfE27cca6d86722F35da23A1E4Cb97": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0xD3994e4d3202dD23c8497d7F75bF1647d1DA1bb1": { - "balance": "0x19D971E4FE8401E74000000" - }, - "0xDCA6e9B48Ea86AeBFDf9929949124042296b6e34": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xe0a2Bd4258D2768837BAa26A28fE71Dc079f84c7": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0xEA28d002042fd9898D0Db016be9758eeAFE35C1E": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xEfA7454f1116807975A4750B46695E967850de5D": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xFBFd6Fa9F73Ac6A058E01259034C28001BEf8247": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0xe0991E844041bE6F11B99da5b114b6bCf84EBd57": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x15E719b6AcAf1E4411Bf0f9576CB1D0dB161DdFc": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x346D827a75F98F0A7a324Ff80b7C3F90252E8baC": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x73b2e0E54510239E22cC936F0b4a6dE1acf0AbdE": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0xBb977B2EE8a111D788B3477D242078d0B837E72b": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x834Dbf5A03e29c25bc55459cCe9c021EeBE676Ad": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xD1F77E4C1C45186e8653C489F90e008a73597296": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xb04aeF2a3d2D86B01006cCD4339A2e943d9c6480": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xC9CA2bA9A27De1Db589d8c33Ab8EDFa2111b31fb": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x4BC656B34De23896fa6069C9862F355b740401aF": { - "balance": "0x084595161401484a000000" - } - } -} diff --git a/src/Nethermind/Chains/holesky.json b/src/Nethermind/Nethermind.Specs.Test/Specs/hoodi_no_deposit_contract.json similarity index 81% rename from src/Nethermind/Chains/holesky.json rename to src/Nethermind/Nethermind.Specs.Test/Specs/hoodi_no_deposit_contract.json index 10364a90ced1..52a476237965 100644 --- a/src/Nethermind/Chains/holesky.json +++ b/src/Nethermind/Nethermind.Specs.Test/Specs/hoodi_no_deposit_contract.json @@ -1,23 +1,31 @@ { - "name": "Holesky Testnet", - "dataDir": "holesky", + "name": "Hoodi Testnet", + "dataDir": "hoodi", "engine": { "Ethash": {} }, "params": { + "gasLimitBoundDivisor": "0x400", + "registrar": "0x0000000000000000000000000000000000000000", "accountStartNonce": "0x0", - "chainID": "0x4268", - "networkID": "0x4268", - "eip140Transition": "0x0", - "eip145Transition": "0x0", + "maximumExtraDataSize": "0xffff", + "minGasLimit": "0x1388", + "chainID": "0x88bb0", + "networkID": "0x88bb0", + "MergeForkIdTransition": "0x0", + "maxCodeSize": "0x6000", + "maxCodeSizeTransition": "0x0", "eip150Transition": "0x0", - "eip155Transition": "0x0", + "eip158Transition": "0x0", "eip160Transition": "0x0", "eip161abcTransition": "0x0", "eip161dTransition": "0x0", + "eip155Transition": "0x0", + "eip140Transition": "0x0", "eip211Transition": "0x0", "eip214Transition": "0x0", "eip658Transition": "0x0", + "eip145Transition": "0x0", "eip1014Transition": "0x0", "eip1052Transition": "0x0", "eip1283Transition": "0x0", @@ -35,63 +43,55 @@ "eip3198Transition": "0x0", "eip3529Transition": "0x0", "eip3541Transition": "0x0", - "beaconChainGenesisTimestamp": "0x65156994", - "eip3651TransitionTimestamp": "0x6516eac0", - "eip3855TransitionTimestamp": "0x6516eac0", - "eip3860TransitionTimestamp": "0x6516eac0", - "eip4895TransitionTimestamp": "0x6516eac0", - "eip1153TransitionTimestamp": "0x65C36AC0", - "eip4788TransitionTimestamp": "0x65C36AC0", - "eip4844TransitionTimestamp": "0x65C36AC0", - "eip5656TransitionTimestamp": "0x65C36AC0", - "eip6780TransitionTimestamp": "0x65C36AC0", - "eip2537TransitionTimestamp": "0x67BCEAC0", - "eip2935TransitionTimestamp": "0x67BCEAC0", - "eip6110TransitionTimestamp": "0x67BCEAC0", - "eip7002TransitionTimestamp": "0x67BCEAC0", - "eip7251TransitionTimestamp": "0x67BCEAC0", - "eip7623TransitionTimestamp": "0x67BCEAC0", - "eip7702TransitionTimestamp": "0x67BCEAC0", - "eip7594TransitionTimestamp": "0x68dceac0", - "eip7823TransitionTimestamp": "0x68dceac0", - "eip7825TransitionTimestamp": "0x68dceac0", - "eip7883TransitionTimestamp": "0x68dceac0", - "eip7918TransitionTimestamp": "0x68dceac0", - "eip7934TransitionTimestamp": "0x68dceac0", - "eip7939TransitionTimestamp": "0x68dceac0", - "eip7951TransitionTimestamp": "0x68dceac0", - "depositContractAddress": "0x4242424242424242424242424242424242424242", + "mergeForkIdTransition": "0x0", "terminalTotalDifficulty": "0x0", - "gasLimitBoundDivisor": "0x400", - "maxCodeSize": "0x6000", - "maxCodeSizeTransition": "0x0", - "maximumExtraDataSize": "0xffff", - "minGasLimit": "0x1388", - "registrar": "0x0000000000000000000000000000000000000000", - "MergeForkIdTransition": "0x0", + "eip4895TransitionTimestamp": "0x0", + "eip3855TransitionTimestamp": "0x0", + "eip3651TransitionTimestamp": "0x0", + "eip3860TransitionTimestamp": "0x0", + "eip4844TransitionTimestamp": "0x0", + "eip4788TransitionTimestamp": "0x0", + "eip1153TransitionTimestamp": "0x0", + "eip5656TransitionTimestamp": "0x0", + "eip6780TransitionTimestamp": "0x0", "blobSchedule": [ { "name": "prague", - "timestamp": "0x67bceac0", + "timestamp": "0x67e41118", "target": 6, "max": 9, "baseFeeUpdateFraction": "0x4c6964" }, { "name": "bpo1", - "timestamp": "0x68e46ac0", + "timestamp": "0x690b9118", "target": 10, "max": 15, "baseFeeUpdateFraction": "0x7f5a51" }, { "name": "bpo2", - "timestamp": "0x68ed6ac0", + "timestamp": "0x69149118", "target": 14, "max": 21, "baseFeeUpdateFraction": "0xb24b3f" } - ] + ], + "eip2537TransitionTimestamp": "0x67e41118", + "eip2935TransitionTimestamp": "0x67e41118", + "eip6110TransitionTimestamp": "0x67e41118", + "eip7002TransitionTimestamp": "0x67e41118", + "eip7251TransitionTimestamp": "0x67e41118", + "eip7702TransitionTimestamp": "0x67e41118", + "eip7623TransitionTimestamp": "0x67e41118", + "eip7594TransitionTimestamp": "0x69011118", + "eip7823TransitionTimestamp": "0x69011118", + "eip7825TransitionTimestamp": "0x69011118", + "eip7883TransitionTimestamp": "0x69011118", + "eip7918TransitionTimestamp": "0x69011118", + "eip7934TransitionTimestamp": "0x69011118", + "eip7939TransitionTimestamp": "0x69011118", + "eip7951TransitionTimestamp": "0x69011118" }, "genesis": { "seal": { @@ -102,15 +102,11 @@ }, "difficulty": "0x01", "author": "0x0000000000000000000000000000000000000000", - "extraData": "", - "gasLimit": "0x17D7840", + "timestamp": "0x67d80ec0", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "timestamp": "0x65156994" + "extraData": "", + "gasLimit": "0x2255100" }, - "nodes": [ - "enode://ac906289e4b7f12df423d654c5a962b6ebe5b3a74cc9e06292a85221f9a64a6f1cfdd6b714ed6dacef51578f92b34c60ee91e9ede9c7f8fadc4d347326d95e2b@146.190.13.128:30303", - "enode://a3435a0155a3e837c02f5e7f5662a2f1fbc25b48e4dc232016e1c51b544cb5b4510ef633ea3278c0e970fa8ad8141e2d4d0f9f95456c537ff05fdf9b31c15072@178.128.136.233:30303" - ], "accounts": { "0x0000000000000000000000000000000000000000": { "balance": "1" @@ -880,9 +876,9 @@ "0x00000000000000000000000000000000000000ff": { "balance": "1" }, - "0x4242424242424242424242424242424242424242": { + "0x00000000219ab540356cBB839Cbe05303d7705Fa": { "balance": "0", - "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a26469706673582212201dd26f37a621703009abf16e77e69c93dc50c79db7f6cc37543e3e0e3decdc9764736f6c634300060b0033", + "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a2646970667358221220dceca8706b29e917dacf25fceef95acac8d90d765ac926663ce4096195952b6164736f6c634300060b0033", "storage": { "0x0000000000000000000000000000000000000000000000000000000000000022": "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", "0x0000000000000000000000000000000000000000000000000000000000000023": "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", @@ -917,185 +913,258 @@ "0x0000000000000000000000000000000000000000000000000000000000000040": "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7" } }, + "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02": { + "balance": "0", + "nonce": "1", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500" + }, + "0x0000F90827F1C53a10cb7A02335B175320002935": { + "balance": "0", + "nonce": "1", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500" + }, + "0x00000961Ef480Eb55e80D19ad83579A64c007002": { + "balance": "0", + "nonce": "1", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460cb5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101f457600182026001905f5b5f82111560685781019083028483029004916001019190604d565b909390049250505036603814608857366101f457346101f4575f5260205ff35b34106101f457600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260385f601437604c5fa0600101600355005b6003546002548082038060101160df575060105b5f5b8181146101835782810160030260040181604c02815460601b8152601401816001015481526020019060020154807fffffffffffffffffffffffffffffffff00000000000000000000000000000000168252906010019060401c908160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c81600101535360010160e1565b910180921461019557906002556101a0565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14156101cd57505f5b6001546002828201116101e25750505f6101e8565b01600290035b5f555f600155604c025ff35b5f5ffd", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + } + }, + "0x0000BBdDc7CE488642fb579F8B00f3a590007251": { + "balance": "0", + "nonce": "1", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + } + }, + "0x9A27D0c715D3f2Af2fAc39a41C49ed35004a3Bcf": { + "balance": "500000000000000000000000000" + }, + "0x610866c6089768dA95524bcc4cE7dB61eDa3931c": { + "balance": "200000000000000000000000000" + }, + "0x462396E69dBfa455F405f4DD82F3014Af8003B72": { + "balance": "200000000000000000000000000" + }, + "0x1B7aA44088a0eA95bdc65fef6E5071E946Bf7d8f": { + "balance": "100000000000000000000000000" + }, + "0x4D496CcC28058B1D74B7a19541663E21154f9c84": { + "balance": "100000000000000000000000000" + }, + "0x6Cc9397c3B38739daCbfaA68EaD5F5D77Ba5F455": { + "balance": "100000000000000000000000000" + }, + "0xfc7AF49b80ACF041744366B02272019723C94F9c": { + "balance": "100000000000000000000000000" + }, + "0x0C100000006d7b5e23a1eAEE637f28cA32Cd5b31": { + "balance": "100000000000000000000000000" + }, + "0x2f14582947E292a2eCd20C430B46f2d27CFE213c": { + "balance": "100000000000000000000000000" + }, + "0x73b2e0E54510239E22cC936F0b4a6dE1acf0AbdE": { + "balance": "100000000000000000000000000" + }, + "0x778F5F13C4Be78A3a4d7141BCB26999702f407CF": { + "balance": "100000000000000000000000000" + }, + "0xc6e2459991BfE27cca6d86722F35da23A1E4Cb97": { + "balance": "100000000000000000000000000" + }, + "0x03B1F066125897834e46aBd7DC6415D8759F8372": { + "balance": "100000000000000000000000000" + }, + "0x10F5d45854e038071485AC9e402308cF80D2d2fE": { + "balance": "100000000000000000000000000" + }, + "0x2f2c75B5Dd5D246194812b00eEb3B09c2c66e2eE": { + "balance": "100000000000000000000000000" + }, "0x0000006916a87b82333f4245046623b23794C65C": { - "balance": "0x52b7d2dcc80cd2e4000000" + "balance": "100000000000000000000000000" + }, + "0xB19Fb4c1f280327e60Ed37b1Dc6EE77533539314": { + "balance": "100000000000000000000000000" + }, + "0x5F10DA9B68F06C76f3c624F8b2eE3b2A2698351b": { + "balance": "100000000000000000000000000" + }, + "0x509a7667aC8D0320e36172c192506a6188aA84f6": { + "balance": "100000000000000000000000000" + }, + "0x6a7aA9b882d50Bb7bc5Da1a244719C99f12F06a3": { + "balance": "100000000000000000000000000" + }, + "0xFBFd6Fa9F73Ac6A058E01259034C28001BEf8247": { + "balance": "100000000000000000000000000" }, "0x0be949928Ff199c9EBA9E110db210AA5C94EFAd0": { - "balance": "0x7c13bc4b2c133c56000000" + "balance": "10000000000000000000000000" }, - "0x0C100000006d7b5e23a1eAEE637f28cA32Cd5b31": { - "balance": "0x52b7d2dcc80cd2e4000000" + "0xDA29BB71669f46F2a779b4b62f03644A84eE3479": { + "balance": "10000000000000000000000000" + }, + "0xDbf640DeA047FA7ee746D01a31782386f827CFC1": { + "balance": "10000000000000000000000000" + }, + "0x9029c772dde847622df1553ed9d9bdb7812e4f93": { + "balance": "1000000000000000000000000" + }, + "0xC564AF154621Ee8D0589758d535511aEc8f67b40": { + "balance": "10000000000000000000000000" + }, + "0x8dF7878d3571BEF5e5a744F96287C8D20386d75A": { + "balance": "1000000000000000000000000" }, "0x0C35317B7a96C454E2CB3d1A255D775Ab112cCc8": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0x0d731cfabC5574329823F26d488416451d2ea376": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x0e79065B5F11b5BD1e62B935A600976ffF3754B9": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0x105083929bF9bb22C26cB1777Ec92661170D4285": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x10F5d45854e038071485AC9e402308cF80D2d2fE": { - "balance": "0x52b7d2dcc80cd2e4000000" + "balance": "1000000000000000000000000" }, "0x1268AD189526AC0b386faF06eFfC46779c340eE6": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0x12Cba59f5A74DB81a12ff63C349Bd82CBF6007C2": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x1446D7f6dF00380F246d8211dE7f0FaBC4Fd248C": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0x164e38a375247A784A81d420201AA8fe4E513921": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x1B7aA44088a0eA95bdc65fef6E5071E946Bf7d8f": { - "balance": "0x52b7d2dcc80cd2e4000000" + "balance": "1000000000000000000000000" }, "0x222222222222cF64a76AE3d36859958c864fDA2c": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x2f14582947E292a2eCd20C430B46f2d27CFE213c": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0x2f2c75B5Dd5D246194812b00eEb3B09c2c66e2eE": { - "balance": "0x52b7d2dcc80cd2e4000000" + "balance": "1000000000000000000000000" }, "0x341c40b94bf2afbfa42573cb78f16ee15a056238": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0x34f845773D4364999f2fbC7AA26ABDeE902cBb46": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0x3C75594181e03E8ECD8468A0037F058a9dAfad79": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x462396E69dBfa455F405f4DD82F3014Af8003B72": { - "balance": "0xa56fa5b99019a5c8000000" + "balance": "1000000000000000000000000" }, "0x49Df3CCa2670eB0D591146B16359fe336e476F29": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0x4D0b04b405c6b62C7cFC3aE54759747e2C0b4662": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x4D496CcC28058B1D74B7a19541663E21154f9c84": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0x509a7667aC8D0320e36172c192506a6188aA84f6": { - "balance": "0x7c13bc4b2c133c56000000" + "balance": "1000000000000000000000000" }, "0x5180db0237291A6449DdA9ed33aD90a38787621c": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0x52730f347dEf6BA09adfF62EaC60D5fEe8205BC4": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0x5EAC0fBd3dfef8aE3efa3c5dc1aa193bc6033dFd": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x6a7aA9b882d50Bb7bc5Da1a244719C99f12F06a3": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0x6Cc9397c3B38739daCbfaA68EaD5F5D77Ba5F455": { - "balance": "0x52b7d2dcc80cd2e4000000" + "balance": "1000000000000000000000000" }, "0x762cA62ca2549ad806763B3Aa1eA317c429bDBDa": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x778F5F13C4Be78A3a4d7141BCB26999702f407CF": { - "balance": "0x52b7d2dcc80cd2e4000000" + "balance": "1000000000000000000000000" }, "0x875D25Ee4bC604C71BaF6236a8488F22399BED4b": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x8dF7878d3571BEF5e5a744F96287C8D20386d75A": { - "balance": "0x52b7d2dcc80cd2e4000000" + "balance": "1000000000000000000000000" }, "0x9E415A096fF77650dc925dEA546585B4adB322B6": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0xA0766B65A4f7B1da79a1AF79aC695456eFa28644": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0xA29B144A449E414A472c60C7AAf1aaFfE329021D": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0xa55395566b0b54395B3246f96A0bDc4b8a483df9": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0xAC9ba72fb61aA7c31A95df0A8b6ebA6f41EF875e": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0xB0498C15879db2eE5471d4926c5fAA25C9a09683": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xB19Fb4c1f280327e60Ed37b1Dc6EE77533539314": { - "balance": "0x52b7d2dcc80cd2e4000000" + "balance": "1000000000000000000000000" }, "0xC21cB9C99C316d1863142F7dD86dd5496D81A8D6": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0xc473d412dc52e349862209924c8981b2ee420768": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xC48E23C5F6e1eA0BaEf6530734edC3968f79Af2e": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0xc6e2459991BfE27cca6d86722F35da23A1E4Cb97": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0xD3994e4d3202dD23c8497d7F75bF1647d1DA1bb1": { - "balance": "0x19D971E4FE8401E74000000" + "balance": "1000000000000000000000000" }, "0xDCA6e9B48Ea86AeBFDf9929949124042296b6e34": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xe0a2Bd4258D2768837BAa26A28fE71Dc079f84c7": { - "balance": "0x52b7d2dcc80cd2e4000000" + "balance": "1000000000000000000000000" }, "0xEA28d002042fd9898D0Db016be9758eeAFE35C1E": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0xEfA7454f1116807975A4750B46695E967850de5D": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xFBFd6Fa9F73Ac6A058E01259034C28001BEf8247": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0xe0991E844041bE6F11B99da5b114b6bCf84EBd57": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0x15E719b6AcAf1E4411Bf0f9576CB1D0dB161DdFc": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0x346D827a75F98F0A7a324Ff80b7C3F90252E8baC": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x73b2e0E54510239E22cC936F0b4a6dE1acf0AbdE": { - "balance": "0x52b7d2dcc80cd2e4000000" - }, - "0xBb977B2EE8a111D788B3477D242078d0B837E72b": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0x834Dbf5A03e29c25bc55459cCe9c021EeBE676Ad": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0xD1F77E4C1C45186e8653C489F90e008a73597296": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0xb04aeF2a3d2D86B01006cCD4339A2e943d9c6480": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, "0xC9CA2bA9A27De1Db589d8c33Ab8EDFa2111b31fb": { - "balance": "0xd3c21bcecceda1000000" + "balance": "1000000000000000000000000" }, - "0x4BC656B34De23896fa6069C9862F355b740401aF": { - "balance": "0x084595161401484a000000" + "0xC48E23C5F6e1eA0BaEf6530734edC3968f79Af2e": { + "balance": "1000000000000000000000000" + }, + "0xeb742E43956E0ea89a2dB6bf6237C79EE4645981": { + "balance": "1000000000000000000000000" + }, + "0x26ecc0885bAB76EB602888dF8415bdf32d0F62bD": { + "balance": "1000000000000000000000000" + }, + "0xe4955e107EA530B4C2db8A323DF0A143D8A82B9C": { + "balance": "1000000000000000000000000" + }, + "0x45DcA0746fc56b400B291f5D9657a79464E8F152": { + "balance": "1000000000000000000000000" + }, + "0x404Ffd9dD65428e096d9a7B837924263Ef40FB31": { + "balance": "1000000000000000000000000" + }, + "0x11ecb03299080fc8d5c1b2fe0c0cd34890d5e1b5": { + "balance": "1000000000000000000000000" + }, + "0xD4BB555d3B0D7fF17c606161B44E372689C14F4B": { + "balance": "1000000000000000000000000" + }, + "0x9B383f8e4Cd5d3DD5F9006B6A508960A1e730375": { + "balance": "1000000000000000000000000" + }, + "0x22819830D4fe783d658841939Ad6D75b00A2B78c": { + "balance": "1000000000000000000000000" + }, + "0x0e79065B5F11b5BD1e62B935A600976ffF3754B9": { + "balance": "1000000000000000000000000" + }, + "0x767f7576944D321374921DF138589a30e3C5030d": { + "balance": "1000000000000000000000000" + }, + "0x9B491F043189af6d31d3D4b1A7cA1cF72f40C52a": { + "balance": "1000000000000000000000000" } - } + }, + "nodes": [ + "enode://2112dd3839dd752813d4df7f40936f06829fc54c0e051a93967c26e5f5d27d99d886b57b4ffcc3c475e930ec9e79c56ef1dbb7d86ca5ee83a9d2ccf36e5c240c@134.209.138.84:30303", + "enode://60203fcb3524e07c5df60a14ae1c9c5b24023ea5d47463dfae051d2c9f3219f309657537576090ca0ae641f73d419f53d8e8000d7a464319d4784acd7d2abc41@209.38.124.160:30303", + "enode://8ae4a48101b2299597341263da0deb47cc38aa4d3ef4b7430b897d49bfa10eb1ccfe1655679b1ed46928ef177fbf21b86837bd724400196c508427a6f41602cd@134.199.184.23:30303" + ] } diff --git a/src/Nethermind/Nethermind.Specs.Test/TestSpecHelper.cs b/src/Nethermind/Nethermind.Specs.Test/TestSpecHelper.cs index 81fc0c5e4d58..88330ad5965a 100644 --- a/src/Nethermind/Nethermind.Specs.Test/TestSpecHelper.cs +++ b/src/Nethermind/Nethermind.Specs.Test/TestSpecHelper.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Logging; using Nethermind.Serialization.Json; using Nethermind.Specs.ChainSpecStyle; using Nethermind.Specs.ChainSpecStyle.Json; @@ -22,7 +23,7 @@ public static (ChainSpecBasedSpecProvider, ChainSpec) LoadChainSpec(ChainSpecJso CustomEngineData = new Dictionary { { "NethDev", serializer.Deserialize("{}") } } }; - ChainSpecLoader loader = new(serializer); + ChainSpecLoader loader = new(serializer, NullLogManager.Instance); MemoryStream data = new(Encoding.UTF8.GetBytes(serializer.Serialize(spec))); ChainSpec chainSpec = loader.Load(data); diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs index ff14862f117f..a1a02a58ea9a 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs @@ -129,6 +129,7 @@ public class ChainParameters public Address Eip7251ContractAddress { get; set; } public ulong? Eip2935TransitionTimestamp { get; set; } public Address Eip2935ContractAddress { get; set; } + public long Eip2935RingBufferSize { get; set; } = Eip2935Constants.RingBufferSize; public ulong? Eip7951TransitionTimestamp { get; set; } public ulong? Rip7212TransitionTimestamp { get; set; } public ulong? Eip7692TransitionTimestamp { get; set; } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs index 94e47e9ab6fc..b46187d7263c 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs @@ -1,11 +1,11 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Collections.Generic; -using System.Diagnostics; using Nethermind.Config; using Nethermind.Core; using Nethermind.Int256; +using System.Collections.Generic; +using System.Diagnostics; namespace Nethermind.Specs.ChainSpecStyle { @@ -27,7 +27,7 @@ public class ChainSpec public ulong ChainId { get; set; } - public NetworkNode[] Bootnodes { get; set; } + public NetworkNode[] Bootnodes { get; set; } = []; public bool GenesisStateUnavailable { get; set; } public Block Genesis { get; set; } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs index 0b4fdc3c8fa4..9ab4f76e4081 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs @@ -257,6 +257,7 @@ protected virtual ReleaseSpec CreateReleaseSpec(ChainSpec chainSpec, long releas releaseSpec.IsEip2935Enabled = (chainSpec.Parameters.Eip2935TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; releaseSpec.IsEofEnabled = (chainSpec.Parameters.Eip7692TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; releaseSpec.Eip2935ContractAddress = chainSpec.Parameters.Eip2935ContractAddress; + releaseSpec.Eip2935RingBufferSize = chainSpec.Parameters.Eip2935RingBufferSize; releaseSpec.IsEip7702Enabled = (chainSpec.Parameters.Eip7702TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; releaseSpec.IsEip7823Enabled = (chainSpec.Parameters.Eip7823TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecFileLoader.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecFileLoader.cs index 4a4e8844d608..7f4e0db86545 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecFileLoader.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecFileLoader.cs @@ -18,15 +18,15 @@ public class ChainSpecFileLoader private readonly Dictionary _chainSpecLoaders; private readonly ILogger _logger; - public ChainSpecFileLoader(IJsonSerializer serializer, ILogger logger) + public ChainSpecFileLoader(IJsonSerializer serializer, ILogManager logManager) { - var jsonLoader = new ChainSpecLoader(serializer); + var jsonLoader = new ChainSpecLoader(serializer, logManager); _chainSpecLoaders = new Dictionary { { ".json", jsonLoader }, { ".zst", new ZstdChainSpecLoader(jsonLoader) } }; - _logger = logger; + _logger = logManager.GetClassLogger(); } public ChainSpec LoadEmbeddedOrFromFile(string fileName) diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs index 7374ae2341d4..aaeef7ec1912 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System; @@ -14,6 +14,7 @@ using Nethermind.Core.Exceptions; using Nethermind.Core.ExecutionRequest; using Nethermind.Int256; +using Nethermind.Logging; using Nethermind.Serialization.Json; using Nethermind.Specs.ChainSpecStyle.Json; @@ -22,8 +23,10 @@ namespace Nethermind.Specs.ChainSpecStyle; /// /// This class can load a Parity-style chain spec file and build a out of it. /// -public class ChainSpecLoader(IJsonSerializer serializer) : IChainSpecLoader +public class ChainSpecLoader(IJsonSerializer serializer, ILogManager logManager) : IChainSpecLoader { + private readonly ILogger _logger = logManager.GetClassLogger(); + public ChainSpec Load(Stream streamData) { try @@ -159,6 +162,7 @@ bool GetForInnerPathExistence(KeyValuePair o) => Eip4788ContractAddress = chainSpecJson.Params.Eip4788ContractAddress ?? Eip4788Constants.BeaconRootsAddress, Eip2935TransitionTimestamp = chainSpecJson.Params.Eip2935TransitionTimestamp, Eip2935ContractAddress = chainSpecJson.Params.Eip2935ContractAddress ?? Eip2935Constants.BlockHashHistoryAddress, + Eip2935RingBufferSize = chainSpecJson.Params.Eip2935RingBufferSize ?? Eip2935Constants.RingBufferSize, TransactionPermissionContract = chainSpecJson.Params.TransactionPermissionContract, TransactionPermissionContractTransition = chainSpecJson.Params.TransactionPermissionContractTransition, ValidateChainIdTransition = chainSpecJson.Params.ValidateChainIdTransition, @@ -433,18 +437,6 @@ private static void LoadAllocations(ChainSpecJson chainSpecJson, ChainSpec chain } } - private static void LoadBootnodes(ChainSpecJson chainSpecJson, ChainSpec chainSpec) - { - if (chainSpecJson.Nodes is null) - { - chainSpec.Bootnodes = []; - return; - } - - chainSpec.Bootnodes = new NetworkNode[chainSpecJson.Nodes.Length]; - for (int i = 0; i < chainSpecJson.Nodes.Length; i++) - { - chainSpec.Bootnodes[i] = new NetworkNode(chainSpecJson.Nodes[i]); - } - } + private void LoadBootnodes(ChainSpecJson chainSpecJson, ChainSpec chainSpec) + => chainSpec.Bootnodes = NetworkNode.ParseNodes(chainSpecJson.Nodes, _logger); } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecJson.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecJson.cs index 78c30c02153b..f9229d4f3385 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecJson.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecJson.cs @@ -19,7 +19,7 @@ public class ChainSpecJson public ChainSpecParamsJson Params { get; set; } [JsonPropertyName("genesis")] public ChainSpecGenesisJson Genesis { get; set; } - public string[] Nodes { get; set; } + public string[]? Nodes { get; set; } [JsonPropertyName("accounts")] public Dictionary Accounts { get; set; } public Dictionary? CodeHashes { get; set; } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs index 6a80e4a902e1..4611a4f6f7eb 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs @@ -148,6 +148,7 @@ public class ChainSpecParamsJson public Address Eip4788ContractAddress { get; set; } public ulong? Eip2935TransitionTimestamp { get; set; } public Address Eip2935ContractAddress { get; set; } + public long? Eip2935RingBufferSize { get; set; } public UInt256? Eip4844BlobGasPriceUpdateFraction { get; set; } public UInt256? Eip4844MinBlobGasPrice { get; set; } public ulong? Eip4844FeeCollectorTransitionTimestamp { get; set; } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/SpecProviderBase.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/SpecProviderBase.cs index 8d826557df95..4a3ef4b16b12 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/ChainSpecStyle/ZstdChainSpecLoader.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ZstdChainSpecLoader.cs index e8072737ad6e..a2a161802a2c 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ZstdChainSpecLoader.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ZstdChainSpecLoader.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.IO; -using System.Reflection; using Nethermind.Config; using ZstdSharp; diff --git a/src/Nethermind/Nethermind.Specs/ChiadoSpecProvider.cs b/src/Nethermind/Nethermind.Specs/ChiadoSpecProvider.cs index 956a2b286acf..2d7bc1291f89 100644 --- a/src/Nethermind/Nethermind.Specs/ChiadoSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/ChiadoSpecProvider.cs @@ -14,20 +14,22 @@ public class ChiadoSpecProvider : ISpecProvider public const ulong BeaconChainGenesisTimestampConst = 0x6343ee4c; public const ulong ShanghaiTimestamp = 0x646e0e4c; public const ulong CancunTimestamp = 0x65ba8e4c; - public const ulong PragueTimestamp = 0x67C96E4C; + public const ulong PragueTimestamp = 0x67c96e4c; + public const ulong OsakaTimestamp = ulong.MaxValue - 1; public static readonly Address FeeCollector = new("0x1559000000000000000000000000000000000000"); private ChiadoSpecProvider() { } - IReleaseSpec ISpecProvider.GetSpecInternal(ForkActivation forkActivation) => forkActivation.BlockNumber switch + public IReleaseSpec GetSpec(ForkActivation forkActivation) => forkActivation.BlockNumber switch { _ => forkActivation.Timestamp switch { null or < ShanghaiTimestamp => GenesisSpec, < CancunTimestamp => ShanghaiGnosis.Instance, < PragueTimestamp => CancunGnosis.Instance, - _ => PragueGnosis.Instance + < OsakaTimestamp => PragueGnosis.Instance, + _ => OsakaGnosis.Instance } }; @@ -42,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 3850d3dd360c..a9408a3d61c6 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 a41a41dbd868..f37711dfd260 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 b922cf5bb339..c6804bf92a76 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 975b10dab09a..f172aba8b0e4 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/GnosisForks/12_LondonGnosis.cs b/src/Nethermind/Nethermind.Specs/GnosisForks/12_LondonGnosis.cs index 35567bc23c63..d85978032c5c 100644 --- a/src/Nethermind/Nethermind.Specs/GnosisForks/12_LondonGnosis.cs +++ b/src/Nethermind/Nethermind.Specs/GnosisForks/12_LondonGnosis.cs @@ -12,14 +12,10 @@ public class LondonGnosis : London private static IReleaseSpec? _instance; private LondonGnosis() - { - ToGnosisFork(this); - } + => ToGnosisFork(this); public static void ToGnosisFork(London spec) - { - spec.FeeCollector = GnosisSpecProvider.FeeCollector; - } + => spec.FeeCollector = GnosisSpecProvider.FeeCollector; public new static IReleaseSpec Instance => LazyInitializer.EnsureInitialized(ref _instance, static () => new LondonGnosis()); } diff --git a/src/Nethermind/Nethermind.Specs/GnosisForks/16_ShanghaiGnosis.cs b/src/Nethermind/Nethermind.Specs/GnosisForks/16_ShanghaiGnosis.cs index a4fe0c24efd2..fe1102f05fef 100644 --- a/src/Nethermind/Nethermind.Specs/GnosisForks/16_ShanghaiGnosis.cs +++ b/src/Nethermind/Nethermind.Specs/GnosisForks/16_ShanghaiGnosis.cs @@ -12,14 +12,10 @@ public class ShanghaiGnosis : Shanghai private static IReleaseSpec? _instance; private ShanghaiGnosis() - { - ToGnosisFork(this); - } + => ToGnosisFork(this); public static void ToGnosisFork(Shanghai spec) - { - LondonGnosis.ToGnosisFork(spec); - } + => LondonGnosis.ToGnosisFork(spec); public new static IReleaseSpec Instance => LazyInitializer.EnsureInitialized(ref _instance, static () => new ShanghaiGnosis()); } diff --git a/src/Nethermind/Nethermind.Specs/GnosisForks/17_CancunGnosis.cs b/src/Nethermind/Nethermind.Specs/GnosisForks/17_CancunGnosis.cs index f471b01e337d..6a41b3a49d2e 100644 --- a/src/Nethermind/Nethermind.Specs/GnosisForks/17_CancunGnosis.cs +++ b/src/Nethermind/Nethermind.Specs/GnosisForks/17_CancunGnosis.cs @@ -12,9 +12,7 @@ public class CancunGnosis : Cancun private static IReleaseSpec? _instance; private CancunGnosis() - { - ToGnosisFork(this); - } + => ToGnosisFork(this); public static void ToGnosisFork(Cancun spec) { diff --git a/src/Nethermind/Nethermind.Specs/GnosisForks/18_PragueGnosis.cs b/src/Nethermind/Nethermind.Specs/GnosisForks/18_PragueGnosis.cs index 06bc1ee2d3fa..496b0d1ea9c8 100644 --- a/src/Nethermind/Nethermind.Specs/GnosisForks/18_PragueGnosis.cs +++ b/src/Nethermind/Nethermind.Specs/GnosisForks/18_PragueGnosis.cs @@ -12,9 +12,7 @@ public class PragueGnosis : Prague private static IReleaseSpec _instance; private PragueGnosis() - { - ToGnosisFork(this); - } + => ToGnosisFork(this); public static void ToGnosisFork(Prague spec) { diff --git a/src/Nethermind/Nethermind.Specs/GnosisForks/19_OsakaGnosis.cs b/src/Nethermind/Nethermind.Specs/GnosisForks/19_OsakaGnosis.cs new file mode 100644 index 000000000000..74d08535e7a7 --- /dev/null +++ b/src/Nethermind/Nethermind.Specs/GnosisForks/19_OsakaGnosis.cs @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading; +using Nethermind.Core.Specs; +using Nethermind.Specs.Forks; + +namespace Nethermind.Specs.GnosisForks; + +public class OsakaGnosis : Osaka +{ + private static IReleaseSpec _instance; + + private OsakaGnosis() + => ToGnosisFork(this); + + public static void ToGnosisFork(Osaka spec) + => PragueGnosis.ToGnosisFork(spec); + + public new static IReleaseSpec Instance => LazyInitializer.EnsureInitialized(ref _instance, static () => new OsakaGnosis()); +} diff --git a/src/Nethermind/Nethermind.Specs/GnosisSpecProvider.cs b/src/Nethermind/Nethermind.Specs/GnosisSpecProvider.cs index 479c78030977..821df7a8d0f0 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/HoleskySpecProvider.cs b/src/Nethermind/Nethermind.Specs/HoleskySpecProvider.cs deleted file mode 100644 index 26c889c4690d..000000000000 --- a/src/Nethermind/Nethermind.Specs/HoleskySpecProvider.cs +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Threading; -using Nethermind.Core; -using Nethermind.Core.Specs; -using Nethermind.Int256; -using Nethermind.Specs.Forks; - -namespace Nethermind.Specs; - -public class HoleskySpecProvider : ISpecProvider -{ - public const ulong GenesisTimestamp = 0x65156994; - public const ulong ShanghaiTimestamp = 0x6516eac0; - public const ulong CancunTimestamp = 0x65C36AC0; - public const ulong PragueTimestamp = 0x67BCEAC0; - public const ulong OsakaTimestamp = 0x68dceac0; - public const ulong BPO1Timestamp = 0x68e46ac0; - public const ulong BPO2Timestamp = 0x68ed6ac0; - - private static IReleaseSpec? _prague; - - private static IReleaseSpec Prague => LazyInitializer.EnsureInitialized(ref _prague, - static () => new Prague { DepositContractAddress = Eip6110Constants.HoleskyDepositContractAddress }); - - private HoleskySpecProvider() { } - - IReleaseSpec ISpecProvider.GetSpecInternal(ForkActivation forkActivation) - { - return forkActivation.Timestamp switch - { - null or < ShanghaiTimestamp => GenesisSpec, - < CancunTimestamp => Shanghai.Instance, - < PragueTimestamp => Cancun.Instance, - < OsakaTimestamp => Prague, - < BPO1Timestamp => Osaka.Instance, - < BPO2Timestamp => BPO1.Instance, - _ => BPO2.Instance - }; - } - - public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalDifficulty = null) - { - if (blockNumber is not null) - MergeBlockNumber = (ForkActivation)blockNumber; - if (terminalTotalDifficulty is not null) - TerminalTotalDifficulty = terminalTotalDifficulty; - } - - public ulong NetworkId => BlockchainIds.Holesky; - public ulong ChainId => NetworkId; - public long? DaoBlockNumber => null; - public ulong? BeaconChainGenesisTimestamp => GenesisTimestamp; - public ForkActivation? MergeBlockNumber { get; private set; } = (0, GenesisTimestamp); - public ulong TimestampFork => ShanghaiTimestamp; - public UInt256? TerminalTotalDifficulty { get; private set; } = 0; - public IReleaseSpec GenesisSpec { get; } = London.Instance; - public ForkActivation[] TransitionActivations { get; } = - [ - (1, ShanghaiTimestamp), - (2, CancunTimestamp), - (3, PragueTimestamp), - (4, OsakaTimestamp), - (5, BPO1Timestamp), - (6, BPO2Timestamp), - ]; - - public static readonly HoleskySpecProvider Instance = new(); -} diff --git a/src/Nethermind/Nethermind.Specs/HoodiSpecProvider.cs b/src/Nethermind/Nethermind.Specs/HoodiSpecProvider.cs index e9e328fe113a..bb02ff2e72fc 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 7352ae53fc09..948e8ebd8693 100644 --- a/src/Nethermind/Nethermind.Specs/MainnetSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/MainnetSpecProvider.cs @@ -27,9 +27,11 @@ public class MainnetSpecProvider : ISpecProvider public const ulong ShanghaiBlockTimestamp = 0x64373057; public const ulong CancunBlockTimestamp = 0x65F1B057; public const ulong PragueBlockTimestamp = 0x681b3057; - public const ulong OsakaBlockTimestamp = ulong.MaxValue - 1; + public const ulong OsakaBlockTimestamp = 0x6930b057; + 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, @@ -49,7 +51,9 @@ IReleaseSpec ISpecProvider.GetSpecInternal(ForkActivation forkActivation) => { Timestamp: < CancunBlockTimestamp } => Shanghai.Instance, { Timestamp: < PragueBlockTimestamp } => Cancun.Instance, { Timestamp: < OsakaBlockTimestamp } => Prague.Instance, - _ => Osaka.Instance + { Timestamp: < BPO1BlockTimestamp } => Osaka.Instance, + { Timestamp: < BPO2BlockTimestamp } => BPO1.Instance, + _ => BPO2.Instance }; public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalDifficulty = null) @@ -61,18 +65,21 @@ 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); public static ForkActivation PragueActivation { get; } = (ParisBlockNumber + 3, PragueBlockTimestamp); public static ForkActivation OsakaActivation { get; } = (ParisBlockNumber + 4, OsakaBlockTimestamp); + public static ForkActivation BPO1Activation { get; } = (ParisBlockNumber + 5, BPO1BlockTimestamp); + public static ForkActivation BPO2Activation { get; } = (ParisBlockNumber + 6, BPO2BlockTimestamp); public ForkActivation[] TransitionActivations { get; } = { (ForkActivation)HomesteadBlockNumber, @@ -91,6 +98,8 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD CancunActivation, PragueActivation, OsakaActivation, + BPO1Activation, + BPO2Activation, }; public static MainnetSpecProvider Instance { get; } = new(); diff --git a/src/Nethermind/Nethermind.Specs/MordenSpecProvider.cs b/src/Nethermind/Nethermind.Specs/MordenSpecProvider.cs index a75fe3fe8d1d..aa572aad1544 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 89a6c258ead0..b3556fa01aac 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 ea60eb262bf4..dad707e7f7f1 100644 --- a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs @@ -4,201 +4,165 @@ 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 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; } - - private FrozenSet? _precompiles; - FrozenSet IReleaseSpec.Precompiles => _precompiles ??= BuildPrecompilesCache(); - - 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(); } + + // 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 3eb52bde3e00..c84e0fe7c79b 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 ded0e575e205..75fb5a087fde 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 054be202f35f..2e8e199c9130 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.Runner.Test/BlockchainTestStreamingTracerTests.cs b/src/Nethermind/Nethermind.State.Test.Runner.Test/BlockchainTestStreamingTracerTests.cs new file mode 100644 index 000000000000..ef903a4858b7 --- /dev/null +++ b/src/Nethermind/Nethermind.State.Test.Runner.Test/BlockchainTestStreamingTracerTests.cs @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.IO; +using System.Linq; +using System.Text; +using Nethermind.Blockchain.Tracing.GethStyle; +using Nethermind.Core; +using Nethermind.Core.Test.Builders; +using Nethermind.Test.Runner; +using NUnit.Framework; + +namespace Nethermind.State.Test.Runner.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class BlockchainTestStreamingTracerTests +{ + [Test] + public void Tracer_writes_to_provided_output() + { + // Arrange + using var output = new MemoryStream(); + var options = new GethTraceOptions(); + var tracer = new BlockchainTestStreamingTracer(options, output); + + Block block = Build.A.Block.WithNumber(1).TestObject; + Transaction tx = Build.A.Transaction.WithValue(1).TestObject; + + // Act + tracer.StartNewBlockTrace(block); + tracer.StartNewTxTrace(tx); + tracer.EndTxTrace(); + tracer.EndBlockTrace(); + + // Assert + var result = Encoding.UTF8.GetString(output.ToArray()); + Assert.That(result, Does.Contain("\"output\"")); + Assert.That(result, Does.Contain("\"gasUsed\"")); + } + + [Test] + public void Tracer_handles_multiple_transactions() + { + // Arrange + using var output = new MemoryStream(); + var options = new GethTraceOptions(); + var tracer = new BlockchainTestStreamingTracer(options, output); + + Block block = Build.A.Block.WithNumber(1).TestObject; + Transaction tx1 = Build.A.Transaction.WithValue(1).WithNonce(0).TestObject; + Transaction tx2 = Build.A.Transaction.WithValue(2).WithNonce(1).TestObject; + + // Act + tracer.StartNewBlockTrace(block); + + tracer.StartNewTxTrace(tx1); + tracer.EndTxTrace(); + + tracer.StartNewTxTrace(tx2); + tracer.EndTxTrace(); + + tracer.EndBlockTrace(); + + // Assert + var result = Encoding.UTF8.GetString(output.ToArray()); + var lines = result.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); + + // Should have at least 2 summary lines (one per transaction) + Assert.That(lines.Count(l => l.Contains("\"gasUsed\"")), Is.EqualTo(2), "Should have 2 transaction summary lines"); + } + + [Test] + public void Tracer_handles_multiple_blocks() + { + // Arrange + using var output = new MemoryStream(); + var options = new GethTraceOptions(); + var tracer = new BlockchainTestStreamingTracer(options, output); + + Block block1 = Build.A.Block.WithNumber(1).TestObject; + Block block2 = Build.A.Block.WithNumber(2).TestObject; + Transaction tx1 = Build.A.Transaction.WithValue(1).TestObject; + Transaction tx2 = Build.A.Transaction.WithValue(2).TestObject; + + // Act + tracer.StartNewBlockTrace(block1); + tracer.StartNewTxTrace(tx1); + tracer.EndTxTrace(); + tracer.EndBlockTrace(); + + tracer.StartNewBlockTrace(block2); + tracer.StartNewTxTrace(tx2); + tracer.EndTxTrace(); + tracer.EndBlockTrace(); + + // Assert + var result = Encoding.UTF8.GetString(output.ToArray()); + var lines = result.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); + + // Should have 2 summary lines (one per transaction across both blocks) + Assert.That(lines.Count(l => l.Contains("\"gasUsed\"")), Is.EqualTo(2), "Should have 2 transaction summary lines across both blocks"); + } + + [Test] + public void Tracer_disposes_cleanly() + { + // Arrange + using var output = new MemoryStream(); + var options = new GethTraceOptions(); + var tracer = new BlockchainTestStreamingTracer(options, output); + + // Act & Assert - should not throw + Assert.DoesNotThrow(() => tracer.Dispose()); + Assert.DoesNotThrow(() => tracer.Dispose()); // Double dispose should be safe + } +} diff --git a/src/Nethermind/Nethermind.State.Test/PatriciaTreeBulkSetterTests.cs b/src/Nethermind/Nethermind.State.Test/PatriciaTreeBulkSetterTests.cs index 80b7ce668f35..ca103f1a10f0 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( @@ -195,6 +213,20 @@ public static IEnumerable BulkSetTestGen() yield return new TestCaseData(GenRandomOfLength(100), eraseList).SetName("delete"); + yield return new TestCaseData( + new List<(Hash256 key, byte[] value)>() + { + (new Hash256("aaaa000000000000000000000000000000000000000000000000000000000000"), [1]), + (new Hash256("aaaabbbb00000000000000000000000000000000000000000000000000000000"), [1]), + (new Hash256("aaaacccc00000000000000000000000000000000000000000000000000000000"), [1]), + }, + new List<(Hash256 key, byte[] value)>() + { + (new Hash256("aaaabbbb00000000000000000000000000000000000000000000000000000000"), [2]), + (new Hash256("aaaacccc00000000000000000000000000000000000000000000000000000000"), [1]), + } + ).SetName("extension head"); + } static byte[] MakeRandomValue(Random rng, bool canBeNull = true) @@ -241,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; @@ -252,7 +284,7 @@ public void BulkSet(List<(Hash256 key, byte[] value)> existingItems, List<(Hash2 pTree.Commit(); - using ArrayPoolList entries = new ArrayPoolList(items.Count); + using ArrayPoolListRef entries = new(items.Count); foreach (var valueTuple in items) { entries.Add(new PatriciaTree.BulkSetEntry(valueTuple.key, valueTuple.value)); @@ -285,6 +317,35 @@ public void BulkSet(List<(Hash256 key, byte[] value)> existingItems, List<(Hash2 newWriteCount.Should().BeLessOrEqualTo(baselineWriteCount); } + [TestCaseSource(nameof(BulkSetTestGen))] + public void BulkSetRootHashUpdated(List<(Hash256 key, byte[] value)> existingItems, List<(Hash256 key, byte[] value)> items) + { + const bool recordDump = true; + (Hash256 root, TimeSpan baselineTime, long baselineWriteCount, string originalDump) = CalculateBaseline(existingItems, items, recordDump); + + TestMemDb db = new TestMemDb(); + IScopedTrieStore trieStore = new StrictRawScopedTrieStore(new RawScopedTrieStore(db)); + PatriciaTree pTree = new PatriciaTree(trieStore, LimboLogs.Instance); + pTree.RootHash = Keccak.EmptyTreeHash; + + foreach (var existingItem in existingItems) + { + pTree.Set(existingItem.key.Bytes, existingItem.value); + } + + pTree.UpdateRootHash(); + + using ArrayPoolListRef entries = new(items.Count); + foreach (var valueTuple in items) + { + entries.Add(new PatriciaTree.BulkSetEntry(valueTuple.key, valueTuple.value)); + } + + pTree.BulkSet(entries); + pTree.UpdateRootHash(); + pTree.RootHash.Should().Be(root); + } + [TestCaseSource(nameof(BulkSetTestGen))] public void BulkSetPreSorted(List<(Hash256 key, byte[] value)> existingItems, List<(Hash256 key, byte[] value)> items) { @@ -295,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; @@ -307,7 +368,7 @@ public void BulkSetPreSorted(List<(Hash256 key, byte[] value)> existingItems, Li pTree.Commit(); - using ArrayPoolList entries = new ArrayPoolList(items.Count); + using ArrayPoolListRef entries = new(items.Count); foreach (var valueTuple in items) { entries.Add(new PatriciaTree.BulkSetEntry(valueTuple.key, valueTuple.value)); @@ -353,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; @@ -368,7 +429,7 @@ public void BulkSetOneByOne(List<(Hash256 key, byte[] value)> existingItems, Lis long sw = Stopwatch.GetTimestamp(); foreach (var valueTuple in items) { - using ArrayPoolList entries = new ArrayPoolList(items.Count); + using ArrayPoolListRef entries = new(items.Count); entries.Add(new PatriciaTree.BulkSetEntry(valueTuple.key, valueTuple.value)); pTree.BulkSet(entries, PatriciaTree.Flags.None); } @@ -405,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; @@ -441,21 +502,30 @@ 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; Random rng = new Random(0); - using ArrayPoolList entries = new ArrayPoolList(3); + using ArrayPoolListRef entries = new(3); entries.Add(new PatriciaTree.BulkSetEntry(new ValueHash256("8818888888888888888888888888888888888888888888888888888888888888"), MakeRandomValue(rng))); entries.Add(new PatriciaTree.BulkSetEntry(new ValueHash256("8828888888888888888888888888888888888888888888888888888888888888"), MakeRandomValue(rng))); entries.Add(new PatriciaTree.BulkSetEntry(new ValueHash256("8848888888888888888888888888888888888888888888888888888888888888"), MakeRandomValue(rng))); entries.Add(new PatriciaTree.BulkSetEntry(new ValueHash256("8848888888888888888888888888888888888888888888888888888888888888"), MakeRandomValue(rng))); entries.Add(new PatriciaTree.BulkSetEntry(new ValueHash256("8858888888888888888888888888888888888888888888888888888888888888"), MakeRandomValue(rng))); - var act = () => pTree.BulkSet(entries); - act.Should().Throw(); + bool thrown = false; + try + { + pTree.BulkSet(entries); + } + catch (InvalidOperationException) + { + thrown = true; + } + + thrown.Should().BeTrue(); } public static IEnumerable BucketSortTestCase() @@ -624,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); } @@ -647,7 +717,7 @@ public void HexarySearch(int nibIndex, List paths, List())); } - items.AsSpan().Sort((a, b) => a.GetPathNibbble(nibIndex).CompareTo(b.GetPathNibbble(nibIndex))); + items.AsSpan().Sort((a, b) => a.GetPathNibble(nibIndex).CompareTo(b.GetPathNibble(nibIndex))); Span result = stackalloc int[TrieNode.BranchesCount]; int resultMask = PatriciaTree.HexarySearchAlreadySortedSmall(items.AsSpan(), nibIndex, result); @@ -663,4 +733,28 @@ 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); + } } diff --git a/src/Nethermind/Nethermind.State.Test/PatriciaTreeTests.cs b/src/Nethermind/Nethermind.State.Test/PatriciaTreeTests.cs index 90d13d6b496c..d68b9300fed7 100644 --- a/src/Nethermind/Nethermind.State.Test/PatriciaTreeTests.cs +++ b/src/Nethermind/Nethermind.State.Test/PatriciaTreeTests.cs @@ -3,7 +3,6 @@ using FluentAssertions; using Nethermind.Core; -using Nethermind.Core.Buffers; using Nethermind.Core.Crypto; using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; diff --git a/src/Nethermind/Nethermind.State.Test/Proofs/AccountProofCollectorTests.cs b/src/Nethermind/Nethermind.State.Test/Proofs/AccountProofCollectorTests.cs index 16d54fd6d7f0..0f029804f994 100644 --- a/src/Nethermind/Nethermind.State.Test/Proofs/AccountProofCollectorTests.cs +++ b/src/Nethermind/Nethermind.State.Test/Proofs/AccountProofCollectorTests.cs @@ -307,8 +307,8 @@ public void Storage_proofs_have_keys_set() AccountProofCollector accountProofCollector = new(TestItem.AddressA, new[] { Bytes.FromHexString("0x0000000000000000000000000000000000000000000000000000000000000000"), Bytes.FromHexString("0x0000000000000000000000000000000000000000000000000000000000000001") }); tree.Accept(accountProofCollector, tree.RootHash); AccountProof proof = accountProofCollector.BuildResult(); - Assert.That(proof.StorageProofs[0].Key.ToHexString(true), Is.EqualTo("0x0000000000000000000000000000000000000000000000000000000000000000")); - Assert.That(proof.StorageProofs[1].Key.ToHexString(true), Is.EqualTo("0x0000000000000000000000000000000000000000000000000000000000000001")); + Assert.That(proof.StorageProofs![0].Key!, Is.EqualTo("0x0")); + Assert.That(proof.StorageProofs![1].Key!, Is.EqualTo("0x1")); } [Test] @@ -750,7 +750,7 @@ public void Chaotic_test() { byte[] indexBytes = new byte[32]; addressesWithStorage[i].StorageCells[j].Index.ToBigEndian(indexBytes.AsSpan()); - accountProof.StorageProofs[j].Key.ToHexString().Should().Be(indexBytes.ToHexString(), $"{i} {j}"); + accountProof.StorageProofs[j].Key!.Should().Be(indexBytes.ToHexString(true, true), $"{i} {j}"); TrieNode node = new(NodeType.Unknown, accountProof.StorageProofs[j].Proof.Last()); node.ResolveNode(null, TreePath.Empty); diff --git a/src/Nethermind/Nethermind.State.Test/Repositories/ChainLevelInfoRepositoryTests.cs b/src/Nethermind/Nethermind.State.Test/Repositories/ChainLevelInfoRepositoryTests.cs index 23ad781b9528..4ed2515fa5ba 100644 --- a/src/Nethermind/Nethermind.State.Test/Repositories/ChainLevelInfoRepositoryTests.cs +++ b/src/Nethermind/Nethermind.State.Test/Repositories/ChainLevelInfoRepositoryTests.cs @@ -27,7 +27,7 @@ public void TestMultiGet() repository.PersistLevel(10, level10); } - using IOwnedReadOnlyList levels = repository.MultiLoadLevel([1, 10]); + using IOwnedReadOnlyList levels = repository.MultiLoadLevel(new ArrayPoolListRef(2, 1, 10)); levels[0].Should().BeEquivalentTo(level1); levels[1].Should().BeEquivalentTo(level10); } diff --git a/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs b/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs index 243bb99cd4c6..27f361829033 100644 --- a/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs +++ b/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs @@ -10,13 +10,11 @@ using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.Core.Test.Builders; -using Nethermind.Db; using Nethermind.Int256; using Nethermind.Blockchain.Tracing.ParityStyle; using Nethermind.Logging; using Nethermind.Evm.State; using Nethermind.State; -using Nethermind.Trie; using NUnit.Framework; namespace Nethermind.Store.Test; diff --git a/src/Nethermind/Nethermind.State.Test/StateReaderTests.cs b/src/Nethermind/Nethermind.State.Test/StateReaderTests.cs index 0eac67506d33..0d707b62b230 100644 --- a/src/Nethermind/Nethermind.State.Test/StateReaderTests.cs +++ b/src/Nethermind/Nethermind.State.Test/StateReaderTests.cs @@ -18,7 +18,6 @@ using Nethermind.Evm.State; using Nethermind.State; using Nethermind.Trie; -using Nethermind.Trie.Pruning; using NSubstitute; using NUnit.Framework; @@ -249,7 +248,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 +381,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 +398,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 e5a15566f4b5..a0e3055f7859 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 713304b13a86..77f917a60080 100644 --- a/src/Nethermind/Nethermind.State.Test/StatsCollectorTests.cs +++ b/src/Nethermind/Nethermind.State.Test/StatsCollectorTests.cs @@ -13,7 +13,6 @@ using Nethermind.Evm.State; using Nethermind.State; using Nethermind.Trie; -using Nethermind.Trie.Pruning; using NUnit.Framework; namespace Nethermind.Store.Test @@ -28,9 +27,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 +49,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 +66,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 57b3e78e3945..3ec0ba3c2cce 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; @@ -15,7 +16,6 @@ using Nethermind.Evm.State; using Nethermind.Int256; using Nethermind.State; -using NSubstitute; using NUnit.Framework; namespace Nethermind.Store.Test; @@ -433,7 +433,7 @@ public void Selfdestruct_clears_cache() WorldState provider = BuildStorageProvider(ctx); StorageCell accessedStorageCell = new StorageCell(TestItem.AddressA, 1); StorageCell nonAccessedStorageCell = new StorageCell(TestItem.AddressA, 2); - preBlockCaches.StorageCache[accessedStorageCell] = [1, 2, 3]; + preBlockCaches.StorageCache.Set(accessedStorageCell, [1, 2, 3]); provider.Get(accessedStorageCell); provider.Commit(Paris.Instance); provider.ClearStorage(TestItem.AddressA); @@ -441,13 +441,168 @@ 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() { PreBlockCaches preBlockCaches = new PreBlockCaches(); Context ctx = new(preBlockCaches); StorageCell accessedStorageCell = new StorageCell(TestItem.AddressA, 1); - preBlockCaches.StorageCache[accessedStorageCell] = [1, 2, 3]; + preBlockCaches.StorageCache.Set(accessedStorageCell, [1, 2, 3]); WorldState provider = BuildStorageProvider(ctx); provider.Get(accessedStorageCell).ToArray().Should().BeEquivalentTo([1, 2, 3]); @@ -456,11 +611,31 @@ public void Selfdestruct_persist_between_commit() provider.Get(accessedStorageCell).ToArray().Should().BeEquivalentTo(StorageTree.ZeroBytes); } + [Test] + public void Eip161_empty_account_with_storage_does_not_throw_on_commit() + { + IWorldState worldState = new WorldState( + new TrieStoreScopeProvider(TestTrieStoreFactory.Build(new MemDb(), LimboLogs.Instance), new MemDb(), LimboLogs.Instance), LogManager); + + using var disposable = worldState.BeginScope(IWorldState.PreGenesis); + + // Create an empty account (balance=0, nonce=0, no code) and set storage on it. + // EIP-161 (via SpuriousDragon+) deletes empty accounts during commit, but the + // storage flush has already produced a non-empty storage root. The commit must + // handle this gracefully by skipping the storage root update for deleted accounts. + worldState.CreateAccount(TestItem.AddressA, 0); + worldState.Set(new StorageCell(TestItem.AddressA, 1), [1, 2, 3]); + worldState.Commit(SpuriousDragon.Instance); + + worldState.AccountExists(TestItem.AddressA).Should().BeFalse(); + } + [TestCase(2)] [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 +669,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 +701,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 +737,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 000000000000..6338d4340452 --- /dev/null +++ b/src/Nethermind/Nethermind.State.Test/TrieStoreScopeProviderTests.cs @@ -0,0 +1,121 @@ +// 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); + } + + [Test] + public void Test_NullAccountWithNonEmptyStorageDoesNotThrow() + { + TestMemDb kv = new TestMemDb(); + IWorldStateScopeProvider scopeProvider = new TrieStoreScopeProvider(new TestRawTrieStore(kv), new MemDb(), LimboLogs.Instance); + + using var scope = scopeProvider.BeginScope(null); + + // Simulates the EIP-161 scenario: storage is flushed for an account that was + // then deleted (set to null) during state commit. The write batch Dispose should + // skip the storage root update for the deleted account instead of throwing. + using (var writeBatch = scope.StartWriteBatch(1)) + { + using (var storageSet = writeBatch.CreateStorageWriteBatch(TestItem.AddressA, 1)) + { + storageSet.Set(1, [1, 2, 3]); + } + + writeBatch.Set(TestItem.AddressA, null); + } + } +} diff --git a/src/Nethermind/Nethermind.State.Test/WorldStateManagerTests.cs b/src/Nethermind/Nethermind.State.Test/WorldStateManagerTests.cs index 63ba31d6fedd..42d6780c5d7f 100644 --- a/src/Nethermind/Nethermind.State.Test/WorldStateManagerTests.cs +++ b/src/Nethermind/Nethermind.State.Test/WorldStateManagerTests.cs @@ -1,11 +1,12 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Autofac; using FluentAssertions; 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; @@ -27,7 +28,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); @@ -38,7 +39,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); @@ -54,7 +55,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); @@ -76,18 +77,23 @@ public void ShouldNotSupportHashLookupOnHalfpath(INodeStorage.KeyScheme keySchem public void ShouldAnnounceReorgOnDispose() { int lastBlock = 256; - int reorgDepth = 128; // Default reorg depth with snap serving IBlockTree blockTree = Substitute.For(); IConfigProvider configProvider = new ConfigProvider(); + int reorgDepth = configProvider.GetConfig().SnapServingMaxDepth; + IFinalizedStateProvider manualFinalizedStateProvider = Substitute.For(); + manualFinalizedStateProvider.FinalizedBlockNumber.Returns(lastBlock - reorgDepth); + manualFinalizedStateProvider.GetFinalizedStateRootAt(lastBlock - reorgDepth) + .Returns(new Hash256("0xec6063a04d48f4b2258f36efaef76a23ba61875f5303fcf8ede2f5d160def35d")); { using IContainer ctx = new ContainerBuilder() .AddModule(new TestNethermindModule(configProvider)) + .AddSingleton(manualFinalizedStateProvider) .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 e7921cd95139..2259ad134a9e 100644 --- a/src/Nethermind/Nethermind.State/BlockingVerifyTrie.cs +++ b/src/Nethermind/Nethermind.State/BlockingVerifyTrie.cs @@ -5,10 +5,7 @@ using System.Threading; using Autofac.Features.AttributeFilters; using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; using Nethermind.Db; -using Nethermind.Evm.State; using Nethermind.Logging; using Nethermind.Trie; using Nethermind.Trie.Pruning; @@ -47,8 +44,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 c1f1f59d0cdc..000000000000 --- 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 78c6097e4130..000000000000 --- a/src/Nethermind/Nethermind.State/Healing/HealingWorldState.cs +++ /dev/null @@ -1,21 +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; - -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 000000000000..a3054977c602 --- /dev/null +++ b/src/Nethermind/Nethermind.State/Healing/HealingWorldStateScopeProvider.cs @@ -0,0 +1,26 @@ +// 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 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/Healing/ITrieNodeRecovery.cs b/src/Nethermind/Nethermind.State/Healing/ITrieNodeRecovery.cs index 4696e2c42737..e6265f409982 100644 --- a/src/Nethermind/Nethermind.State/Healing/ITrieNodeRecovery.cs +++ b/src/Nethermind/Nethermind.State/Healing/ITrieNodeRecovery.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Nethermind.Core.Collections; diff --git a/src/Nethermind/Nethermind.State/IStateReader.cs b/src/Nethermind/Nethermind.State/IStateReader.cs index a2d44d20eb66..f52f5f9b5047 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 a4ad6c9dacf6..6c56f8ca6dd8 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 e46021c9b953..409dbb20d235 100644 --- a/src/Nethermind/Nethermind.State/OverridableEnv/DisposableScopeOverridableEnv.cs +++ b/src/Nethermind/Nethermind.State/OverridableEnv/DisposableScopeOverridableEnv.cs @@ -4,14 +4,13 @@ using System; using System.Collections.Generic; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Evm; namespace Nethermind.State.OverridableEnv; /// /// A utility that provide `IOverridableEnv` -/// Dont forget do dispose it! +/// Don't forget to dispose it! /// /// /// @@ -21,7 +20,7 @@ public class DisposableScopeOverridableEnv( T resolvedComponents ) : IOverridableEnv { - public Scope BuildAndOverride(BlockHeader header, Dictionary? stateOverride = null) + public Scope BuildAndOverride(BlockHeader? header, Dictionary? stateOverride = null) { IDisposable disposable = overridableEnv.BuildAndOverride(header, stateOverride); return new Scope(resolvedComponents, disposable); diff --git a/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs b/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs index 8e58838a252c..7b1a1da5dd5a 100644 --- a/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs @@ -9,46 +9,78 @@ using Nethermind.Core.Specs; using Nethermind.Evm; using Nethermind.Evm.CodeAnalysis; +using Nethermind.Evm.State; namespace Nethermind.State.OverridableEnv; -public class OverridableCodeInfoRepository(ICodeInfoRepository codeInfoRepository) : IOverridableCodeInfoRepository +public class OverridableCodeInfoRepository(ICodeInfoRepository codeInfoRepository, IWorldState worldState) : IOverridableCodeInfoRepository { - private readonly Dictionary _codeOverwrites = new(); + private readonly Dictionary _codeOverrides = new(); + private readonly Dictionary _precompileOverrides = new(); - public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) + public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) { delegationAddress = null; - return _codeOverwrites.TryGetValue(codeSource, out ICodeInfo result) - ? result - : codeInfoRepository.GetCachedCodeInfo(codeSource, followDelegation, vmSpec, out delegationAddress); + if (_precompileOverrides.TryGetValue(codeSource, out var precompile)) return precompile.codeInfo; + + if (_codeOverrides.TryGetValue(codeSource, out var result)) + { + return !result.codeInfo.IsEmpty && + ICodeInfoRepository.TryGetDelegatedAddress(result.codeInfo.CodeSpan, out delegationAddress) && + followDelegation + ? GetCachedCodeInfo(delegationAddress, false, vmSpec, out Address? _) + : result.codeInfo; + } + + return codeInfoRepository.GetCachedCodeInfo(codeSource, followDelegation, vmSpec, out delegationAddress); } public void InsertCode(ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec) => codeInfoRepository.InsertCode(code, codeOwner, spec); - public void SetCodeOverwrite( + public void SetCodeOverride( IReleaseSpec vmSpec, Address key, - ICodeInfo value, - Address? redirectAddress = null) + CodeInfo value) { - if (redirectAddress is not null) - { - _codeOverwrites[redirectAddress] = this.GetCachedCodeInfo(key, vmSpec); - } + _codeOverrides[key] = (value, ValueKeccak.Compute(value.Code.Span)); + } - _codeOverwrites[key] = value; + public void MovePrecompile(IReleaseSpec vmSpec, Address precompileAddr, Address targetAddr) + { + _precompileOverrides[targetAddr] = (this.GetCachedCodeInfo(precompileAddr, vmSpec), precompileAddr); + _codeOverrides[precompileAddr] = (new CodeInfo(worldState.GetCode(precompileAddr)), worldState.GetCodeHash(precompileAddr)); } public void SetDelegation(Address codeSource, Address authority, IReleaseSpec spec) => codeInfoRepository.SetDelegation(codeSource, authority, spec); - public bool TryGetDelegation(Address address, IReleaseSpec vmSpec, [NotNullWhen(true)] out Address? delegatedAddress) => - codeInfoRepository.TryGetDelegation(address, vmSpec, out delegatedAddress); + public bool TryGetDelegation(Address address, IReleaseSpec vmSpec, + [NotNullWhen(true)] out Address? delegatedAddress) + { + delegatedAddress = null; + return _codeOverrides.TryGetValue(address, out var result) + ? ICodeInfoRepository.TryGetDelegatedAddress(result.codeInfo.CodeSpan, out delegatedAddress) + : codeInfoRepository.TryGetDelegation(address, vmSpec, out delegatedAddress); + } + + + public ValueHash256 GetExecutableCodeHash(Address address, IReleaseSpec spec) => _codeOverrides.TryGetValue(address, out var result) + ? result.codeHash + : codeInfoRepository.GetExecutableCodeHash(address, spec); - public ValueHash256 GetExecutableCodeHash(Address address, IReleaseSpec spec) => - codeInfoRepository.GetExecutableCodeHash(address, spec); + public void ResetOverrides() + { + _precompileOverrides.Clear(); + _codeOverrides.Clear(); + } - public void ResetOverrides() => _codeOverwrites.Clear(); + public void ResetPrecompileOverrides() + { + foreach (var (_, precompileInfo) in _precompileOverrides) + { + _codeOverrides.Remove(precompileInfo.initialAddr); + } + _precompileOverrides.Clear(); + } } diff --git a/src/Nethermind/Nethermind.State/OverridableEnv/OverridableEnvFactory.cs b/src/Nethermind/Nethermind.State/OverridableEnv/OverridableEnvFactory.cs index 9cd841602bf8..7e377f6926c5 100644 --- a/src/Nethermind/Nethermind.State/OverridableEnv/OverridableEnvFactory.cs +++ b/src/Nethermind/Nethermind.State/OverridableEnv/OverridableEnvFactory.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using Autofac; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Evm; using Nethermind.Evm.State; @@ -18,7 +17,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,22 +32,30 @@ ISpecProvider specProvider { private IDisposable? _worldScopeCloser; private readonly IOverridableCodeInfoRepository _codeInfoRepository = childLifetimeScope.Resolve(); + private readonly IWorldState _worldState = childLifetimeScope.Resolve(); - public IDisposable BuildAndOverride(BlockHeader header, Dictionary? stateOverride) + 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); - IDisposable scope = new Scope(this); + _worldScopeCloser = _worldState.BeginScope(header); - if (stateOverride is not null) + try { - overridableScope.WorldState.ApplyStateOverrides(_codeInfoRepository, stateOverride, specProvider.GetSpec(header), header.Number); - header.StateRoot = overridableScope.WorldState.StateRoot; - } + if (stateOverride is not null && header is not null) + { + _worldState.ApplyStateOverrides(_codeInfoRepository, stateOverride, specProvider.GetSpec(header), header.Number); + header.StateRoot = _worldState.StateRoot; + } - return scope; + return new Scope(this); + } + catch + { + Reset(); + throw; + } } private class Scope(OverridableEnv env) : IDisposable @@ -65,11 +72,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 b64ff4a156a2..46412024a938 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 57c3253791ea..c98613e93035 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; @@ -119,6 +120,7 @@ public void Restore(int snapshot) if (stack.Count == 0) { _intraBlockCache.Remove(change.StorageCell); + stack.Return(); } } @@ -140,42 +142,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 +156,6 @@ public void Commit(IStorageTracer tracer, bool commitRoots = true) { CommitCore(tracer); } - - if (commitRoots) - { - CommitStorageRoots(); - } - } - - protected virtual void CommitStorageRoots() - { - // Commit storage roots } /// @@ -202,22 +163,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 +228,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 +271,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 725a4b465f4a..85bf38a4e6ca 100644 --- a/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs +++ b/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs @@ -1,25 +1,25 @@ -// 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.Tasks; +using System.Threading; 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.Trie; -using Nethermind.Trie.Pruning; +using Nethermind.Int256; namespace Nethermind.State; @@ -31,10 +31,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 +42,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 +64,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 +108,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 +139,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 +157,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 +169,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 +197,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 +234,7 @@ protected override void CommitCore(IStorageTracer tracer) } } - protected override void CommitStorageRoots() + internal void FlushToTree(IWorldStateScopeProvider.IWorldStateWriteBatch writeBatch) { if (_toUpdateRoots.Count == 0) { @@ -271,19 +264,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 +292,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 +309,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 +324,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 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) - { - 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 +340,6 @@ public void WarmUp(in StorageCell storageCell, bool isEmpty) { if (isEmpty) { - if (_preBlockCache is not null) - { - _preBlockCache[storageCell] = []; - } } else { @@ -411,9 +360,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 +382,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 +443,109 @@ 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 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 +558,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 ArrayPoolList 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 (var 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/PreBlockCaches.cs b/src/Nethermind/Nethermind.State/PreBlockCaches.cs index 3da7b0036bc4..e7f8bf290735 100644 --- a/src/Nethermind/Nethermind.State/PreBlockCaches.cs +++ b/src/Nethermind/Nethermind.State/PreBlockCaches.cs @@ -6,7 +6,6 @@ using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Core.Collections; -using Nethermind.Evm.State; using Nethermind.Trie; using CollectionExtensions = Nethermind.Core.Collections.CollectionExtensions; @@ -20,26 +19,25 @@ public class PreBlockCaches private readonly Func[] _clearCaches; - private readonly ConcurrentDictionary _storageCache = new(LockPartitions, InitialCapacity); - private readonly ConcurrentDictionary _stateCache = new(LockPartitions, InitialCapacity); - private readonly ConcurrentDictionary _rlpCache = new(LockPartitions, InitialCapacity); - private readonly ConcurrentDictionary _precompileCache = new(LockPartitions, InitialCapacity); + private readonly SeqlockCache _storageCache = new(); + private readonly SeqlockCache _stateCache = new(); + private readonly SeqlockCache _rlpCache = new(); + private readonly ConcurrentDictionary> _precompileCache = new(LockPartitions, InitialCapacity); public PreBlockCaches() { _clearCaches = [ - () => _storageCache.NoResizeClear() ? CacheType.Storage : CacheType.None, - () => _stateCache.NoResizeClear() ? CacheType.State : CacheType.None, - () => _rlpCache.NoResizeClear() ? CacheType.Rlp : CacheType.None, - () => _precompileCache.NoResizeClear() ? CacheType.Precompile : CacheType.None + () => { _storageCache.Clear(); return CacheType.None; }, + () => { _stateCache.Clear(); return CacheType.None; }, + () => { _precompileCache.NoResizeClear(); return CacheType.None; } ]; } - public ConcurrentDictionary StorageCache => _storageCache; - public ConcurrentDictionary StateCache => _stateCache; - public ConcurrentDictionary RlpCache => _rlpCache; - public ConcurrentDictionary PrecompileCache => _precompileCache; + public SeqlockCache StorageCache => _storageCache; + public SeqlockCache StateCache => _stateCache; + public SeqlockCache RlpCache => _rlpCache; + public ConcurrentDictionary> PrecompileCache => _precompileCache; public CacheType ClearCaches() { diff --git a/src/Nethermind/Nethermind.State/PrewarmerScopeProvider.cs b/src/Nethermind/Nethermind.State/PrewarmerScopeProvider.cs new file mode 100644 index 000000000000..af258806395f --- /dev/null +++ b/src/Nethermind/Nethermind.State/PrewarmerScopeProvider.cs @@ -0,0 +1,267 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Diagnostics; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Core.Metric; +using Nethermind.Db; +using Nethermind.Evm.State; +using Nethermind.Int256; + +namespace Nethermind.State; + +internal class PrewarmerGetTimeLabels(bool isPrewarmer) +{ + public static PrewarmerGetTimeLabels Prewarmer { get; } = new(true); + public static PrewarmerGetTimeLabels NonPrewarmer { get; } = new(false); + + public PrewarmerGetTimeLabel Commit { get; } = new("commit", isPrewarmer); + public PrewarmerGetTimeLabel UpdateRootHash { get; } = new("update_root_hash", isPrewarmer); + public PrewarmerGetTimeLabel AddressHit { get; } = new("address_hit", isPrewarmer); + public PrewarmerGetTimeLabel AddressMiss { get; } = new("address_miss", isPrewarmer); + public PrewarmerGetTimeLabel SlotGetHit { get; } = new("slot_get_hit", isPrewarmer); + public PrewarmerGetTimeLabel SlotGetMiss { get; } = new("slot_get_miss", isPrewarmer); + public PrewarmerGetTimeLabel WriteBatchLifetime { get; } = new("write_batch_lifetime", isPrewarmer); +} + +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 + { + private readonly IWorldStateScopeProvider.IScope baseScope; + private readonly SeqlockCache preBlockCache; + private readonly SeqlockCache storageCache; + private readonly bool populatePreBlockCache; + private readonly SeqlockCache.ValueFactory _getFromBaseTree; + private readonly IMetricObserver _metricObserver = Metrics.PrewarmerGetTime; + private readonly bool _measureMetric = Metrics.DetailedMetricsEnabled; + private readonly PrewarmerGetTimeLabels _labels; + + public ScopeWrapper(IWorldStateScopeProvider.IScope baseScope, PreBlockCaches preBlockCaches, bool populatePreBlockCache) + { + this.baseScope = baseScope; + preBlockCache = preBlockCaches.StateCache; + storageCache = preBlockCaches.StorageCache; + this.populatePreBlockCache = populatePreBlockCache; + _labels = populatePreBlockCache ? PrewarmerGetTimeLabels.Prewarmer : PrewarmerGetTimeLabels.NonPrewarmer; + _getFromBaseTree = GetFromBaseTree; + } + + public void Dispose() => baseScope.Dispose(); + + public IWorldStateScopeProvider.ICodeDb CodeDb => baseScope.CodeDb; + + public IWorldStateScopeProvider.IStorageTree CreateStorageTree(Address address) + { + return new StorageTreeWrapper( + baseScope.CreateStorageTree(address), + storageCache, + address, + populatePreBlockCache); + } + + public IWorldStateScopeProvider.IWorldStateWriteBatch StartWriteBatch(int estimatedAccountNum) + { + if (!_measureMetric) + { + return baseScope.StartWriteBatch(estimatedAccountNum); + } + + long sw = Stopwatch.GetTimestamp(); + return new WriteBatchLifetimeMeasurer( + baseScope.StartWriteBatch(estimatedAccountNum), + _metricObserver, + sw, + populatePreBlockCache); + } + + public void Commit(long blockNumber) + { + if (!_measureMetric) + { + baseScope.Commit(blockNumber); + return; + } + + long sw = Stopwatch.GetTimestamp(); + baseScope.Commit(blockNumber); + _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.Commit); + } + + public Hash256 RootHash => baseScope.RootHash; + + public void UpdateRootHash() + { + if (!_measureMetric) + { + baseScope.UpdateRootHash(); + return; + } + + long sw = Stopwatch.GetTimestamp(); + baseScope.UpdateRootHash(); + _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.UpdateRootHash); + } + + public Account? Get(Address address) + { + AddressAsKey addressAsKey = address; + long sw = _measureMetric ? Stopwatch.GetTimestamp() : 0; + if (populatePreBlockCache) + { + long priorReads = Metrics.ThreadLocalStateTreeReads; + Account? account = preBlockCache.GetOrAdd(in addressAsKey, _getFromBaseTree); + + if (Metrics.ThreadLocalStateTreeReads == priorReads) + { + if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.AddressHit); + Metrics.IncrementStateTreeCacheHits(); + } + else + { + if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.AddressMiss); + } + return account; + } + else + { + if (preBlockCache.TryGetValue(in addressAsKey, out Account? account)) + { + if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.AddressHit); + baseScope.HintGet(address, account); + Metrics.IncrementStateTreeCacheHits(); + } + else + { + account = GetFromBaseTree(in addressAsKey); + if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.AddressMiss); + } + return account; + } + } + + public void HintGet(Address address, Account? account) => baseScope.HintGet(address, account); + + private Account? GetFromBaseTree(in AddressAsKey address) + { + return baseScope.Get(address); + } + } + + private sealed class StorageTreeWrapper : IWorldStateScopeProvider.IStorageTree + { + private readonly IWorldStateScopeProvider.IStorageTree baseStorageTree; + private readonly SeqlockCache preBlockCache; + private readonly Address address; + private readonly bool populatePreBlockCache; + private readonly SeqlockCache.ValueFactory _loadFromTreeStorage; + private readonly IMetricObserver _metricObserver = Db.Metrics.PrewarmerGetTime; + private readonly bool _measureMetric = Db.Metrics.DetailedMetricsEnabled; + private readonly PrewarmerGetTimeLabels _labels; + + public StorageTreeWrapper( + IWorldStateScopeProvider.IStorageTree baseStorageTree, + SeqlockCache preBlockCache, + Address address, + bool populatePreBlockCache) + { + this.baseStorageTree = baseStorageTree; + this.preBlockCache = preBlockCache; + this.address = address; + this.populatePreBlockCache = populatePreBlockCache; + _labels = populatePreBlockCache ? PrewarmerGetTimeLabels.Prewarmer : PrewarmerGetTimeLabels.NonPrewarmer; + _loadFromTreeStorage = LoadFromTreeStorage; + } + + 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 + long sw = _measureMetric ? Stopwatch.GetTimestamp() : 0; + if (populatePreBlockCache) + { + long priorReads = Db.Metrics.ThreadLocalStorageTreeReads; + + byte[] value = preBlockCache.GetOrAdd(in storageCell, _loadFromTreeStorage); + + if (Db.Metrics.ThreadLocalStorageTreeReads == priorReads) + { + if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.SlotGetHit); + // Read from Concurrent Cache + Db.Metrics.IncrementStorageTreeCache(); + } + else + { + if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.SlotGetMiss); + } + return value; + } + else + { + if (preBlockCache.TryGetValue(in storageCell, out byte[] value)) + { + if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.SlotGetHit); + baseStorageTree.HintGet(in index, value); + Db.Metrics.IncrementStorageTreeCache(); + } + else + { + value = LoadFromTreeStorage(in storageCell); + if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.SlotGetMiss); + } + return value; + } + } + + public void HintGet(in UInt256 index, byte[]? value) => baseStorageTree.HintGet(in index, value); + + private byte[] LoadFromTreeStorage(in 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); + } + + private class WriteBatchLifetimeMeasurer(IWorldStateScopeProvider.IWorldStateWriteBatch baseWriteBatch, IMetricObserver metricObserver, long startTime, bool populatePreBlockCache) : IWorldStateScopeProvider.IWorldStateWriteBatch + { + private readonly PrewarmerGetTimeLabels _labels = populatePreBlockCache ? PrewarmerGetTimeLabels.Prewarmer : PrewarmerGetTimeLabels.NonPrewarmer; + + public void Dispose() + { + baseWriteBatch.Dispose(); + metricObserver.Observe(Stopwatch.GetTimestamp() - startTime, _labels.WriteBatchLifetime); + } + + public event EventHandler? OnAccountUpdated + { + add => baseWriteBatch.OnAccountUpdated += value; + remove => baseWriteBatch.OnAccountUpdated -= value; + } + + public void Set(Address key, Account? account) => baseWriteBatch.Set(key, account); + + public IWorldStateScopeProvider.IStorageWriteBatch CreateStorageWriteBatch(Address key, int estimatedEntries) => baseWriteBatch.CreateStorageWriteBatch(key, estimatedEntries); + } +} diff --git a/src/Nethermind/Nethermind.State/Proofs/AccountProofCollector.cs b/src/Nethermind/Nethermind.State/Proofs/AccountProofCollector.cs index 2a6e21558038..c1f57a417e56 100644 --- a/src/Nethermind/Nethermind.State/Proofs/AccountProofCollector.cs +++ b/src/Nethermind/Nethermind.State/Proofs/AccountProofCollector.cs @@ -82,7 +82,7 @@ private AccountProofCollector(ReadOnlySpan hashedAddress, IEnumerable 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/Proofs/PatriciaTrieT.cs b/src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs index e54f7fb46bbb..ce45389b1fc3 100644 --- a/src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs +++ b/src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs @@ -17,12 +17,13 @@ namespace Nethermind.State.Trie; /// The type of the elements in the collection used to build the trie. public abstract class PatriciaTrie : PatriciaTree { + protected const int MinItemsForParallelRootHash = 64; /// The collection to build the trie of. /// /// true to maintain an in-memory database for proof computation; /// otherwise, false. /// - protected PatriciaTrie(ReadOnlySpan list, bool canBuildProof, ICappedArrayPool? bufferPool = null) + protected PatriciaTrie(ReadOnlySpan list, bool canBuildProof, ICappedArrayPool? bufferPool = null, bool canBeParallel = true) : base(canBuildProof ? new MemDb() : NullDb.Instance, EmptyTreeHash, false, NullLogManager.Instance, bufferPool: bufferPool) { CanBuildProof = canBuildProof; @@ -31,7 +32,8 @@ protected PatriciaTrie(ReadOnlySpan list, bool canBuildProof, ICappedArrayPoo { // ReSharper disable once VirtualMemberCallInConstructor Initialize(list); - UpdateRootHash(); + // Parallel root hashing adds scheduling overhead for small tries. + UpdateRootHash(canBeParallel); } } diff --git a/src/Nethermind/Nethermind.State/Proofs/ProofCollector.cs b/src/Nethermind/Nethermind.State/Proofs/ProofCollector.cs index bbb190e97dbb..e4250f00da4b 100644 --- a/src/Nethermind/Nethermind.State/Proofs/ProofCollector.cs +++ b/src/Nethermind/Nethermind.State/Proofs/ProofCollector.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using Nethermind.Core; using Nethermind.Core.Crypto; diff --git a/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs b/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs index a84177868b06..f3391d31649b 100644 --- a/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs +++ b/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs @@ -20,8 +20,8 @@ public sealed class ReceiptTrie : PatriciaTrie private readonly IRlpStreamDecoder _decoder; /// /// The transaction receipts to build the trie of. - public ReceiptTrie(IReceiptSpec spec, ReadOnlySpan receipts, IRlpStreamDecoder trieDecoder, ICappedArrayPool bufferPool, bool canBuildProof = false) - : base(null, canBuildProof, bufferPool: bufferPool) + public ReceiptTrie(IReceiptSpec spec, ReadOnlySpan receipts, IRlpStreamDecoder trieDecoder, ICappedArrayPool bufferPool, bool canBuildProof = false, bool canBeParallel = true) + : base(null, canBuildProof, bufferPool: bufferPool, canBeParallel) { ArgumentNullException.ThrowIfNull(spec); ArgumentNullException.ThrowIfNull(trieDecoder); @@ -30,7 +30,7 @@ public ReceiptTrie(IReceiptSpec spec, ReadOnlySpan receipts, IRlpStre if (receipts.Length > 0) { Initialize(receipts, spec); - UpdateRootHash(); + UpdateRootHash(canBeParallel); } } @@ -53,14 +53,16 @@ private void Initialize(ReadOnlySpan receipts, IReceiptSpec spec) public static byte[][] CalculateReceiptProofs(IReleaseSpec spec, ReadOnlySpan receipts, int index, IRlpStreamDecoder decoder) { - using TrackingCappedArrayPool cappedArrayPool = new(receipts.Length * 4); - return new ReceiptTrie(spec, receipts, decoder, cappedArrayPool, canBuildProof: true).BuildProof(index); + bool canBeParallel = receipts.Length > MinItemsForParallelRootHash; + using TrackingCappedArrayPool cappedArrayPool = new(receipts.Length * 4, canBeParallel: canBeParallel); + return new ReceiptTrie(spec, receipts, decoder, cappedArrayPool, canBuildProof: true, canBeParallel: canBeParallel).BuildProof(index); } public static Hash256 CalculateRoot(IReceiptSpec receiptSpec, ReadOnlySpan txReceipts, IRlpStreamDecoder decoder) { - using TrackingCappedArrayPool cappedArrayPool = new(txReceipts.Length * 4); - Hash256 receiptsRoot = new ReceiptTrie(receiptSpec, txReceipts, decoder, bufferPool: cappedArrayPool).RootHash; + bool canBeParallel = txReceipts.Length > MinItemsForParallelRootHash; + using TrackingCappedArrayPool cappedArrayPool = new(txReceipts.Length * 4, canBeParallel: canBeParallel); + Hash256 receiptsRoot = new ReceiptTrie(receiptSpec, txReceipts, decoder, bufferPool: cappedArrayPool, canBeParallel: canBeParallel).RootHash; return receiptsRoot; } } diff --git a/src/Nethermind/Nethermind.State/Proofs/StorageProof.cs b/src/Nethermind/Nethermind.State/Proofs/StorageProof.cs index ca7ead61a899..d450dd280905 100644 --- a/src/Nethermind/Nethermind.State/Proofs/StorageProof.cs +++ b/src/Nethermind/Nethermind.State/Proofs/StorageProof.cs @@ -12,7 +12,7 @@ namespace Nethermind.State.Proofs; /// public class StorageProof { - public byte[]? Key { get; set; } + public string? Key { get; set; } public byte[][]? Proof { get; set; } [JsonConverter(typeof(ProofStorageValueConverter))] diff --git a/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs b/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs index 7e5c6b5415a7..49aeaac04930 100644 --- a/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs +++ b/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs @@ -22,8 +22,8 @@ public sealed class TxTrie : PatriciaTrie /// /// The transactions to build the trie of. - public TxTrie(ReadOnlySpan transactions, bool canBuildProof = false, ICappedArrayPool? bufferPool = null) - : base(transactions, canBuildProof, bufferPool: bufferPool) { } + public TxTrie(ReadOnlySpan transactions, bool canBuildProof = false, ICappedArrayPool? bufferPool = null, bool canBeParallel = true) + : base(transactions, canBuildProof, bufferPool: bufferPool, canBeParallel: canBeParallel) { } protected override void Initialize(ReadOnlySpan list) { @@ -57,20 +57,41 @@ static SpanSource CopyExistingRlp(ReadOnlySpan rlp, ICappedArrayPool? buff } } + private void InitializeFromEncodedTransactions(ReadOnlySpan list) + { + for (int key = 0; key < list.Length; key++) + { + SpanSource keyBuffer = key.EncodeToSpanSource(_bufferPool); + Set(keyBuffer.Span, list[key]); + } + } + [DoesNotReturn, StackTraceHidden] private static void ThrowSpanSourceNotCappedArray() => throw new InvalidOperationException("Encode to SpanSource failed to get a CappedArray."); public static byte[][] CalculateProof(ReadOnlySpan transactions, int index) { - using TrackingCappedArrayPool cappedArray = new(transactions.Length * 4); - byte[][] rootHash = new TxTrie(transactions, canBuildProof: true, bufferPool: cappedArray).BuildProof(index); + bool canBeParallel = transactions.Length > MinItemsForParallelRootHash; + using TrackingCappedArrayPool cappedArray = new(transactions.Length * 4, canBeParallel: canBeParallel); + byte[][] rootHash = new TxTrie(transactions, canBuildProof: true, bufferPool: cappedArray, canBeParallel: canBeParallel).BuildProof(index); return rootHash; } public static Hash256 CalculateRoot(ReadOnlySpan transactions) { - using TrackingCappedArrayPool cappedArray = new(transactions.Length * 4); - Hash256 rootHash = new TxTrie(transactions, canBuildProof: false, bufferPool: cappedArray).RootHash; + bool canBeParallel = transactions.Length > MinItemsForParallelRootHash; + using TrackingCappedArrayPool cappedArray = new(transactions.Length * 4, canBeParallel: canBeParallel); + Hash256 rootHash = new TxTrie(transactions, canBuildProof: false, bufferPool: cappedArray, canBeParallel: canBeParallel).RootHash; return rootHash; } + + public static Hash256 CalculateRoot(ReadOnlySpan encodedTransactions) + { + bool canBeParallel = encodedTransactions.Length > MinItemsForParallelRootHash; + using TrackingCappedArrayPool cappedArray = new(encodedTransactions.Length * 4, canBeParallel: canBeParallel); + TxTrie txTrie = new(ReadOnlySpan.Empty, canBuildProof: false, bufferPool: cappedArray, canBeParallel: canBeParallel); + txTrie.InitializeFromEncodedTransactions(encodedTransactions); + txTrie.UpdateRootHash(canBeParallel); + return txTrie.RootHash; + } } diff --git a/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs b/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs index 10efa64e86a4..d63dc61471b0 100644 --- a/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs +++ b/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs @@ -19,7 +19,7 @@ public sealed class WithdrawalTrie : PatriciaTrie /// /// The withdrawals to build the trie of. public WithdrawalTrie(ReadOnlySpan withdrawals, bool canBuildProof = false) - : base(withdrawals, canBuildProof) { } + : base(withdrawals, canBuildProof, canBeParallel: false) { } public static Hash256? CalculateRoot(ReadOnlySpan withdrawals) => new WithdrawalTrie(withdrawals).RootHash; diff --git a/src/Nethermind/Nethermind.State/Repositories/ChainLevelInfoRepository.cs b/src/Nethermind/Nethermind.State/Repositories/ChainLevelInfoRepository.cs index 51ac2184c3c3..1fc4b124b1a6 100644 --- a/src/Nethermind/Nethermind.State/Repositories/ChainLevelInfoRepository.cs +++ b/src/Nethermind/Nethermind.State/Repositories/ChainLevelInfoRepository.cs @@ -14,7 +14,7 @@ namespace Nethermind.State.Repositories { - public class ChainLevelInfoRepository : IChainLevelInfoRepository + public class ChainLevelInfoRepository([KeyFilter(DbNames.BlockInfos)] IDb blockInfoDb) : IChainLevelInfoRepository { private const int CacheSize = 64; @@ -22,12 +22,7 @@ public class ChainLevelInfoRepository : IChainLevelInfoRepository private readonly ClockCache _blockInfoCache = new(CacheSize); private readonly IRlpValueDecoder _decoder = Rlp.GetValueDecoder(); - private readonly IDb _blockInfoDb; - - public ChainLevelInfoRepository([KeyFilter(DbNames.BlockInfos)] IDb blockInfoDb) - { - _blockInfoDb = blockInfoDb ?? throw new ArgumentNullException(nameof(blockInfoDb)); - } + private readonly IDb _blockInfoDb = blockInfoDb ?? throw new ArgumentNullException(nameof(blockInfoDb)); public void Delete(long number, BatchWrite? batch = null) { @@ -79,7 +74,7 @@ void LocalPersistLevel() public ChainLevelInfo? LoadLevel(long number) => _blockInfoDb.Get(number, Rlp.GetStreamDecoder(), _blockInfoCache); - public IOwnedReadOnlyList MultiLoadLevel(IReadOnlyList blockNumbers) + public IOwnedReadOnlyList MultiLoadLevel(in ArrayPoolListRef blockNumbers) { byte[][] keys = new byte[blockNumbers.Count][]; for (var i = 0; i < blockNumbers.Count; i++) @@ -89,10 +84,10 @@ void LocalPersistLevel() KeyValuePair[] data = _blockInfoDb[keys]; - return data.Select((kv) => + return data.Select(kv => { if (kv.Value == null || kv.Value.Length == 0) return null; - var rlpValueContext = kv.Value.AsRlpValueContext(); + Rlp.ValueDecoderContext rlpValueContext = kv.Value.AsRlpValueContext(); return _decoder.Decode(ref rlpValueContext, RlpBehaviors.AllowExtraBytes); }) .ToPooledList(data.Length); diff --git a/src/Nethermind/Nethermind.State/Repositories/IChainLevelInfoRepository.cs b/src/Nethermind/Nethermind.State/Repositories/IChainLevelInfoRepository.cs index b2f28ce55e32..6cb93a83e2e4 100644 --- a/src/Nethermind/Nethermind.State/Repositories/IChainLevelInfoRepository.cs +++ b/src/Nethermind/Nethermind.State/Repositories/IChainLevelInfoRepository.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Collections.Generic; using Nethermind.Core; using Nethermind.Core.Collections; @@ -13,6 +12,6 @@ public interface IChainLevelInfoRepository void PersistLevel(long number, ChainLevelInfo level, BatchWrite? batch = null); BatchWrite StartBatch(); ChainLevelInfo? LoadLevel(long number); - IOwnedReadOnlyList MultiLoadLevel(IReadOnlyList blockNumbers); + IOwnedReadOnlyList MultiLoadLevel(in ArrayPoolListRef blockNumbers); } } diff --git a/src/Nethermind/Nethermind.State/Snap/Constants.cs b/src/Nethermind/Nethermind.State/Snap/Constants.cs index 394923319b95..ef2ab6cf3850 100644 --- a/src/Nethermind/Nethermind.State/Snap/Constants.cs +++ b/src/Nethermind/Nethermind.State/Snap/Constants.cs @@ -5,6 +5,10 @@ namespace Nethermind.State.Snap { public class Constants { + /// + /// Maximum distance from head for pivot block validation in merge/beacon chain sync. + /// Note: For snap serving depth configuration, use ISyncConfig.SnapServingMaxDepth instead. + /// public const int MaxDistanceFromHead = 128; } } diff --git a/src/Nethermind/Nethermind.State/SnapServer/AccountCollector.cs b/src/Nethermind/Nethermind.State/SnapServer/AccountCollector.cs index fcb495fdc5c2..fc749f8b29c3 100644 --- a/src/Nethermind/Nethermind.State/SnapServer/AccountCollector.cs +++ b/src/Nethermind/Nethermind.State/SnapServer/AccountCollector.cs @@ -23,7 +23,7 @@ public int Collect(in ValueHash256 path, SpanSource value) return 32 + 1; } - Rlp.ValueDecoderContext ctx = new Rlp.ValueDecoderContext(value.Span); + Rlp.ValueDecoderContext ctx = new(value.Span); Account accnt = AccountDecoder.Instance.Decode(ref ctx); Accounts.Add(new PathWithAccount(path, accnt)); return 32 + AccountDecoder.Slim.GetLength(accnt); diff --git a/src/Nethermind/Nethermind.State/SnapServer/SnapServer.cs b/src/Nethermind/Nethermind.State/SnapServer/SnapServer.cs index ecb459db54e9..a9f9e5ad8a8e 100644 --- a/src/Nethermind/Nethermind.State/SnapServer/SnapServer.cs +++ b/src/Nethermind/Nethermind.State/SnapServer/SnapServer.cs @@ -22,7 +22,6 @@ using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; -using Nethermind.Evm.State; using Nethermind.Logging; using Nethermind.Serialization.Rlp; using Nethermind.State.Snap; @@ -36,7 +35,6 @@ public class SnapServer : ISnapServer private readonly IReadOnlyTrieStore _store; private readonly TrieStoreWithReadFlags _storeWithReadFlag; private readonly IReadOnlyKeyValueStore _codeDb; - private readonly IStateReader _stateReader; private readonly ILogManager _logManager; private readonly ILogger _logger; @@ -51,11 +49,10 @@ public class SnapServer : ISnapServer private const long HardResponseByteLimit = 2000000; private const int HardResponseNodeLimit = 100000; - public SnapServer(IReadOnlyTrieStore trieStore, IReadOnlyKeyValueStore codeDb, IStateReader stateReader, ILogManager logManager, ILastNStateRootTracker? lastNStateRootTracker = null) + public SnapServer(IReadOnlyTrieStore trieStore, IReadOnlyKeyValueStore codeDb, ILogManager logManager, ILastNStateRootTracker? lastNStateRootTracker = null) { _store = trieStore ?? throw new ArgumentNullException(nameof(trieStore)); _codeDb = codeDb ?? throw new ArgumentNullException(nameof(codeDb)); - _stateReader = stateReader; // TODO: Remove _lastNStateRootTracker = lastNStateRootTracker; _logManager = logManager ?? throw new ArgumentNullException(nameof(logManager)); _logger = logManager.GetClassLogger(); @@ -226,7 +223,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 9121b5bb0c2a..a369eead9e5f 100644 --- a/src/Nethermind/Nethermind.State/StateProvider.cs +++ b/src/Nethermind/Nethermind.State/StateProvider.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -18,12 +17,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,55 +30,32 @@ internal class StateProvider { private static readonly UInt256 _zero = UInt256.Zero; - private readonly Dictionary> _intraTxCache = new(); + 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 // False positives would be problematic as the code _must_ be persisted - private readonly ClockKeyCacheNonConcurrent _codeInsertFilter = new(1_024); + 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 = new(); 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() @@ -100,7 +74,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) @@ -110,7 +91,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; @@ -128,16 +109,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); @@ -151,7 +122,7 @@ public bool InsertCode(Address address, in ValueHash256 codeHash, ReadOnlyMemory // Don't reinsert if already inserted. This can be the case when the same // code is used by multiple deployments. Either from factory contracts (e.g. LPs) // or people copy and pasting popular contracts - if (!_codeInsertFilter.Get(codeHash)) + if (!_blockCodeInsertFilter.Get(codeHash) && !_persistedCodeInsertFilter.Get(codeHash)) { if (_codeBatch is null) { @@ -159,8 +130,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; } @@ -169,7 +140,7 @@ public bool InsertCode(Address address, in ValueHash256 codeHash, ReadOnlyMemory _codeBatchAlternate[codeHash] = code.ToArray(); } - _codeInsertFilter.Set(codeHash); + _blockCodeInsertFilter.Set(codeHash); inserted = true; } @@ -272,42 +243,14 @@ static void ThrowInsufficientBalanceException(Address address) public void SubtractFromBalance(Address address, in UInt256 balanceChange, IReleaseSpec releaseSpec) { - _needsStateRootUpdate = true; SetNewBalance(address, balanceChange, releaseSpec, true); } public void AddToBalance(Address address, in UInt256 balanceChange, IReleaseSpec releaseSpec) { - _needsStateRootUpdate = true; SetNewBalance(address, balanceChange, releaseSpec, false); } - /// - /// 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) { _needsStateRootUpdate = true; @@ -359,7 +302,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); @@ -425,7 +368,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); @@ -440,7 +383,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(); + } } } } @@ -486,7 +432,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) @@ -529,25 +475,19 @@ public bool AddToBalanceAndCreateIfNotExists(Address address, in UInt256 balance } } - public void Commit(IReleaseSpec releaseSpec, bool commitRoots, bool isGenesis) - => Commit(releaseSpec, NullStateTracer.Instance, commitRoots, isGenesis); - public void Commit(IReleaseSpec releaseSpec, IWorldStateTracer stateTracer, bool commitRoots, bool isGenesis) { 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; } @@ -585,7 +525,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) { @@ -668,16 +608,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; @@ -685,15 +620,21 @@ 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 (Hash256AsKey kvp in dict.Keys) + { + _persistedCodeInsertFilter.Set(kvp.Value.ValueHash256); + } + // Reuse Dictionary if not already re-initialized dict.Clear(); if (Interlocked.CompareExchange(ref _codeBatch, dict, null) is null) @@ -736,25 +677,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 ArrayPoolList bulkWrite = new(_blockChanges.Count); - foreach (var key in _blockChanges.Keys) + foreach (AddressAsKey key in _blockChanges.Keys) { - ref var change = ref CollectionsMarshal.GetValueRefOrNullRef(_blockChanges, key); + 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 @@ -763,8 +697,6 @@ private void FlushToTree() } } - _tree.BulkSet(bulkWrite); - if (writes > 0) Metrics.IncrementStateTreeWrites(writes); if (skipped > 0) @@ -780,9 +712,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); } @@ -793,34 +724,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; @@ -847,7 +751,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; } @@ -873,7 +777,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) { @@ -886,23 +790,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; @@ -931,10 +835,11 @@ public void Reset(bool resetBlockChanges = true) if (_logger.IsTrace) Trace(); if (resetBlockChanges) { + _blockCodeInsertFilter.Clear(); _blockChanges.Clear(); _codeBatch?.Clear(); } - _intraTxCache.Clear(); + _intraTxCache.ResetAndClear(); _committedThisRound.Clear(); _nullAccountReads.Clear(); _changes.Clear(); @@ -944,14 +849,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 1eac7ece078e..8230bc8fdc59 100644 --- a/src/Nethermind/Nethermind.State/StateReader.cs +++ b/src/Nethermind/Nethermind.State/StateReader.cs @@ -5,7 +5,6 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; -using Nethermind.Evm.State; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Trie; @@ -41,9 +40,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 933ec39162b4..cc81ea0b86a7 100644 --- a/src/Nethermind/Nethermind.State/StateReaderExtensions.cs +++ b/src/Nethermind/Nethermind.State/StateReaderExtensions.cs @@ -6,7 +6,6 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; -using Nethermind.Evm.State; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Trie; @@ -44,21 +43,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 ff0f9e0a3603..d71447eaa2e5 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 f1f71924f1d7..a756a8a5b950 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 000000000000..88026fd8d282 --- /dev/null +++ b/src/Nethermind/Nethermind.State/TrieStoreScopeProvider.cs @@ -0,0 +1,318 @@ +// 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.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; + +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); + + // Account may be null when EIP-161 deletes an empty account that had storage + // changes in the same block. Skip the storage root update since the account + // will not exist in the state trie. + if (account is null) continue; + 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}"); + } + } + + 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 26bdb9f5f22d..893db5c11d35 100644 --- a/src/Nethermind/Nethermind.State/WorldState.cs +++ b/src/Nethermind/Nethermind.State/WorldState.cs @@ -1,10 +1,11 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using System.Threading; using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; @@ -14,8 +15,6 @@ using Nethermind.Evm.Tracing.State; using Nethermind.Int256; using Nethermind.Logging; -using Nethermind.Trie; -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(); @@ -213,11 +213,6 @@ public void SubtractFromBalance(Address address, in UInt256 balanceChange, IRele DebugGuardInScope(); _stateProvider.SubtractFromBalance(address, balanceChange, spec); } - public void UpdateStorageRoot(Address address, Hash256 storageRoot) - { - DebugGuardInScope(); - _stateProvider.UpdateStorageRoot(address, storageRoot); - } public void IncrementNonce(Address address, UInt256 delta) { DebugGuardInScope(); @@ -232,12 +227,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) @@ -248,25 +240,30 @@ public UInt256 GetNonce(Address address) public IDisposable BeginScope(BlockHeader? baseBlock) { - if (_isInScope) throw new InvalidOperationException("Cannot create nested worldstate scope."); - _isInScope = true; + if (Interlocked.CompareExchange(ref _isInScope, true, false)) + { + throw new InvalidOperationException("Cannot create nested worldstate scope."); + } 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 ref readonly UInt256 GetBalance(Address address) { @@ -278,7 +275,7 @@ public ValueHash256 GetStorageRoot(Address address) { DebugGuardInScope(); if (address == null) throw new ArgumentNullException(nameof(address)); - return _stateProvider.GetStorageRoot(address); + return _persistentStorageProvider.GetStorageRoot(address); } public byte[] GetCode(Address address) @@ -318,23 +315,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) @@ -384,7 +381,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 6d384c06e0da..5faed688aef1 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; @@ -58,26 +56,11 @@ public event EventHandler? ReorgBoundaryReached public IStateReader GlobalStateReader { get; } - public ISnapServer? SnapServer => _trieStore.Scheme == INodeStorage.KeyScheme.Hash ? null : new SnapServer.SnapServer(_readOnlyTrieStore, _readaOnlyCodeCb, GlobalStateReader, _logManager, _lastNStateRootTracker); + public ISnapServer? SnapServer => _trieStore.Scheme == INodeStorage.KeyScheme.Hash ? null : new SnapServer.SnapServer(_readOnlyTrieStore, _readaOnlyCodeCb, _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 799ef5d0709a..5d1ff5491652 100644 --- a/src/Nethermind/Nethermind.State/WorldStateMetricsDecorator.cs +++ b/src/Nethermind/Nethermind.State/WorldStateMetricsDecorator.cs @@ -125,6 +125,7 @@ public void CommitTree(long blockNumber) public IDisposable BeginScope(BlockHeader? baseBlock) => innerState.BeginScope(baseBlock); public bool IsInScope => innerState.IsInScope; + public IWorldStateScopeProvider ScopeProvider => innerState.ScopeProvider; public ref readonly UInt256 GetBalance(Address account) => ref innerState.GetBalance(account); @@ -133,4 +134,10 @@ public void CommitTree(long blockNumber) public ref readonly ValueHash256 GetCodeHash(Address address) => ref innerState.GetCodeHash(address); ValueHash256 IAccountStateProvider.GetCodeHash(Address address) => innerState.GetCodeHash(address); + + UInt256 IAccountStateProvider.GetNonce(Address address) => innerState.GetNonce(address); + + bool IAccountStateProvider.IsStorageEmpty(Address address) => innerState.IsStorageEmpty(address); + + bool IAccountStateProvider.HasCode(Address address) => innerState.HasCode(address); } diff --git a/src/Nethermind/Nethermind.State/WorldStateScopeOperationLogger.cs b/src/Nethermind/Nethermind.State/WorldStateScopeOperationLogger.cs new file mode 100644 index 000000000000..eda6421c878e --- /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 15b2e0b2cb5d..b750fb3d64e5 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/BlockDownloaderTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/BlockDownloaderTests.cs @@ -21,7 +21,6 @@ using Nethermind.Crypto; using Nethermind.Int256; using Nethermind.Evm; -using Nethermind.Logging; using Nethermind.Network; using Nethermind.Specs; using Nethermind.State.Proofs; @@ -556,18 +555,18 @@ public async Task Prune_download_requests_map() IForwardSyncController forwardSyncController = ctx.ForwardSyncController; ctx.ConfigureBestPeer(syncPeer); - (await forwardSyncController.PrepareRequest(DownloaderOptions.Insert, 0, default)).Should().NotBeNull(); + (await forwardSyncController.PrepareRequest(DownloaderOptions.Insert, 0)).Should().NotBeNull(); forwardSyncController.DownloadRequestBufferSize.Should().Be(32); ctx.ConfigureBestPeer(syncPeer2); - (await forwardSyncController.PrepareRequest(DownloaderOptions.Insert, 0, default)).Should().NotBeNull(); + (await forwardSyncController.PrepareRequest(DownloaderOptions.Insert, 0)).Should().NotBeNull(); forwardSyncController.DownloadRequestBufferSize.Should().Be(64); ctx.ConfigureBestPeer(syncPeer3); - (await forwardSyncController.PrepareRequest(DownloaderOptions.Insert, 0, default)).Should().NotBeNull(); + (await forwardSyncController.PrepareRequest(DownloaderOptions.Insert, 0)).Should().NotBeNull(); forwardSyncController.DownloadRequestBufferSize.Should().Be(96); - (await forwardSyncController.PrepareRequest(DownloaderOptions.Insert, 0, default)).Should().BeNull(); + (await forwardSyncController.PrepareRequest(DownloaderOptions.Insert, 0)).Should().BeNull(); forwardSyncController.DownloadRequestBufferSize.Should().Be(32); } @@ -589,7 +588,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 4e6231f28636..099d77cadb3b 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/E2ESyncTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/E2ESyncTests.cs @@ -96,10 +96,10 @@ private int AllocatePort() /// /// Common code for all node /// - private IContainer CreateNode(PrivateKey nodeKey, Action configurer) + private async Task CreateNode(PrivateKey nodeKey, Func configurer) { IConfigProvider configProvider = new ConfigProvider(); - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance); ChainSpec spec = loader.LoadEmbeddedOrFromFile("chainspec/foundation.json"); // Set basefeepergas in genesis or it will fail 1559 validation. @@ -138,7 +138,7 @@ private IContainer CreateNode(PrivateKey nodeKey, Action() ; - ManualTimestamper timestamper; if (isPostMerge) { // Activate configured mainnet future EIP - timestamper = new ManualTimestamper(new DateTime(2030, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + ManualTimestamper timestamper = new(new DateTime(2030, 1, 1, 0, 0, 0, DateTimeKind.Utc)); builder .AddModule(new TestMergeModule(configProvider.GetConfig())) .AddSingleton(timestamper) // Used by test code @@ -181,7 +180,7 @@ private IContainer CreateNode(PrivateKey nodeKey, Action(timestamper) // Used by test code .AddSingleton(timestamper) @@ -194,16 +193,17 @@ private IContainer CreateNode(PrivateKey nodeKey, Action + _server = await CreateNode(serverKey, (cfg, spec) => { INetworkConfig networkConfig = cfg.GetConfig(); networkConfig.P2PPort = AllocatePort(); + return Task.CompletedTask; }); SyncTestContext serverCtx = _server.Resolve(); @@ -247,10 +247,11 @@ public async Task FullSync() using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource().ThatCancelAfter(TestTimeout); PrivateKey clientKey = TestItem.PrivateKeyB; - await using IContainer client = CreateNode(clientKey, (cfg, spec) => + await using IContainer client = await CreateNode(clientKey, (cfg, spec) => { INetworkConfig networkConfig = cfg.GetConfig(); networkConfig.P2PPort = AllocatePort(); + return Task.CompletedTask; }); await client.Resolve().SyncFromServer(_server, cancellationTokenSource.Token); @@ -263,17 +264,12 @@ public async Task FastSync() using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource().ThatCancelAfter(TestTimeout); PrivateKey clientKey = TestItem.PrivateKeyC; - await using IContainer client = CreateNode(clientKey, (cfg, spec) => + await using IContainer client = await CreateNode(clientKey, async (cfg, spec) => { SyncConfig syncConfig = (SyncConfig)cfg.GetConfig(); syncConfig.FastSync = true; - IBlockTree serverBlockTree = _server.Resolve(); - long serverHeadNumber = serverBlockTree.Head!.Number; - BlockHeader pivot = serverBlockTree.FindHeader(serverHeadNumber - HeadPivotDistance)!; - syncConfig.PivotHash = pivot.Hash!.ToString(); - syncConfig.PivotNumber = pivot.Number.ToString(); - syncConfig.PivotTotalDifficulty = pivot.TotalDifficulty!.Value.ToString(); + await SetPivot(syncConfig, cancellationTokenSource.Token); INetworkConfig networkConfig = cfg.GetConfig(); networkConfig.P2PPort = AllocatePort(); @@ -282,6 +278,18 @@ public async Task FastSync() await client.Resolve().SyncFromServer(_server, cancellationTokenSource.Token); } + private async Task SetPivot(SyncConfig syncConfig, CancellationToken cancellationToken) + { + IBlockProcessingQueue blockProcessingQueue = _server.Resolve(); + await blockProcessingQueue.WaitForBlockProcessing(cancellationToken); + IBlockTree serverBlockTree = _server.Resolve(); + long serverHeadNumber = serverBlockTree.Head!.Number; + BlockHeader pivot = serverBlockTree.FindHeader(serverHeadNumber - HeadPivotDistance)!; + syncConfig.PivotHash = pivot.Hash!.ToString(); + syncConfig.PivotNumber = pivot.Number; + syncConfig.PivotTotalDifficulty = pivot.TotalDifficulty!.Value.ToString(); + } + [Test] [Retry(5)] public async Task SnapSync() @@ -291,18 +299,13 @@ public async Task SnapSync() using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource().ThatCancelAfter(TestTimeout); PrivateKey clientKey = TestItem.PrivateKeyD; - await using IContainer client = CreateNode(clientKey, (cfg, spec) => + await using IContainer client = await CreateNode(clientKey, async (cfg, spec) => { SyncConfig syncConfig = (SyncConfig)cfg.GetConfig(); syncConfig.FastSync = true; syncConfig.SnapSync = true; - IBlockTree serverBlockTree = _server.Resolve(); - long serverHeadNumber = serverBlockTree.Head!.Number; - BlockHeader pivot = serverBlockTree.FindHeader(serverHeadNumber - HeadPivotDistance)!; - syncConfig.PivotHash = pivot.Hash!.ToString(); - syncConfig.PivotNumber = pivot.Number.ToString(); - syncConfig.PivotTotalDifficulty = pivot.TotalDifficulty!.Value.ToString(); + await SetPivot(syncConfig, cancellationTokenSource.Token); INetworkConfig networkConfig = cfg.GetConfig(); networkConfig.P2PPort = AllocatePort(); @@ -410,7 +413,7 @@ public async Task BuildBlockWithTxs(Transaction[] transactions, CancellationToke blockProductionContext.Should().NotBeNull(); blockProductionContext!.CurrentBestBlock.Should().NotBeNull(); - blockTree.SuggestBlock(blockProductionContext.CurrentBestBlock!).Should().Be(AddBlockResult.Added); + (await blockTree.SuggestBlockAsync(blockProductionContext.CurrentBestBlock!)).Should().Be(AddBlockResult.Added); await newBlockTask; } @@ -498,12 +501,7 @@ public async Task BuildBlockWithCode(byte[][] codes, CancellationToken cancellat private async Task VerifyHeadWith(IContainer server, CancellationToken cancellationToken) { - if (!blockProcessingQueue.IsEmpty) - { - await Wait.ForEvent(cancellationToken, - e => blockProcessingQueue.ProcessingQueueEmpty += e, - e => blockProcessingQueue.ProcessingQueueEmpty -= e); - } + await blockProcessingQueue.WaitForBlockProcessing(cancellationToken); IBlockTree otherBlockTree = server.Resolve(); diff --git a/src/Nethermind/Nethermind.Synchronization.Test/FastBlocks/BodiesSyncFeedTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/FastBlocks/BodiesSyncFeedTests.cs index 3ef05a221fc8..e019dbc61306 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 61260d3ddeb1..e72a272a30fb 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 ac96d2871385..9ae8b4d07462 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/IStateSyncTestOperation.cs b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/IStateSyncTestOperation.cs new file mode 100644 index 000000000000..4ccbb17e0f4e --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/IStateSyncTestOperation.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Logging; + +namespace Nethermind.Synchronization.Test.FastSync; + +public interface IStateSyncTestOperation +{ + Hash256 RootHash { get; } + void UpdateRootHash(); + void SetAccountsAndCommit(params (Hash256 Address, Account? Account)[] accounts); + void AssertFlushed(); + void CompareTrees(RemoteDbContext remote, ILogger logger, string stage, bool skipLogs = false); + void DeleteStateRoot(); +} diff --git a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/LocalDbContext.cs b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/LocalDbContext.cs new file mode 100644 index 000000000000..efbe3fa3eb13 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/LocalDbContext.cs @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Autofac.Features.AttributeFilters; +using FluentAssertions; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test; +using Nethermind.Db; +using Nethermind.Logging; +using Nethermind.State; +using Nethermind.Synchronization.FastSync; +using Nethermind.Trie; +using NUnit.Framework; + +namespace Nethermind.Synchronization.Test.FastSync; + +public class LocalDbContext( + [KeyFilter(DbNames.Code)] IDb codeDb, + [KeyFilter(DbNames.State)] IDb stateDb, + INodeStorage nodeStorage, + ILogManager logManager) + : IStateSyncTestOperation +{ + private TestMemDb CodeDb { get; } = (TestMemDb)codeDb; + private TestMemDb Db { get; } = (TestMemDb)stateDb; + private INodeStorage NodeStorage { get; } = nodeStorage; + private StateTree StateTree { get; } = new(TestTrieStoreFactory.Build(nodeStorage, logManager), logManager); + + public Hash256 RootHash => StateTree.RootHash; + + public void UpdateRootHash() => StateTree.UpdateRootHash(); + + public void SetAccountsAndCommit(params (Hash256 Address, Account? Account)[] accounts) + { + foreach (var (address, account) in accounts) + StateTree.Set(address, account); + StateTree.Commit(); + } + + public void AssertFlushed() + { + Db.WasFlushed.Should().BeTrue(); + CodeDb.WasFlushed.Should().BeTrue(); + } + + public void CompareTrees(RemoteDbContext remote, ILogger logger, string stage, bool skipLogs = false) + { + if (!skipLogs) logger.Info($"==================== {stage} ===================="); + StateTree.RootHash = remote.StateTree.RootHash; + + if (!skipLogs) logger.Info("-------------------- REMOTE --------------------"); + TreeDumper dumper = new TreeDumper(); + remote.StateTree.Accept(dumper, remote.StateTree.RootHash); + string remoteStr = dumper.ToString(); + if (!skipLogs) logger.Info(remoteStr); + if (!skipLogs) logger.Info("-------------------- LOCAL --------------------"); + dumper.Reset(); + StateTree.Accept(dumper, StateTree.RootHash); + string localStr = dumper.ToString(); + if (!skipLogs) logger.Info(localStr); + + if (stage == "END") + { + Assert.That(localStr, Is.EqualTo(remoteStr), $"{stage}{Environment.NewLine}{remoteStr}{Environment.NewLine}{localStr}"); + TrieStatsCollector collector = new(CodeDb, LimboLogs.Instance); + StateTree.Accept(collector, StateTree.RootHash); + Assert.That(collector.Stats.MissingNodes, Is.EqualTo(0)); + Assert.That(collector.Stats.MissingCode, Is.EqualTo(0)); + } + } + + public void DeleteStateRoot() + { + NodeStorage.Set(null, TreePath.Empty, RootHash, null); + } +} diff --git a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/PendingSyncItemsTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/PendingSyncItemsTests.cs index c3dc0ffd602e..bde54e640e7e 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/PendingSyncItemsTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/PendingSyncItemsTests.cs @@ -13,9 +13,9 @@ namespace Nethermind.Synchronization.Test.FastSync [TestFixture] public class PendingSyncItemsTests { - private IPendingSyncItems Init() + private IPendingSyncItems Init(bool isSnapSync = false) { - return new PendingSyncItems(); + return new PendingSyncItems(isSnapSync); } [Test] @@ -99,6 +99,41 @@ public void Prioritizes_depth() batch[2].Level.Should().Be(0); } + [Test] + public void Limit_batch_at_start() + { + IPendingSyncItems items = Init(); + + PushState(items, 0, 0); + PushState(items, 32, 0); + PushState(items, 64, 0); + + items.MaxStateLevel = 0; + + List batch = items.TakeBatch(256); + batch.Count.Should().Be(1); + + items.MaxStateLevel = 64; + + batch = items.TakeBatch(256); + batch.Count.Should().Be(2); + } + + [Test] + public void DoNot_Limit_batch_at_start_if_snap_sync() + { + IPendingSyncItems items = Init(isSnapSync: true); + + PushState(items, 0, 0); + PushState(items, 32, 0); + PushState(items, 64, 0); + + items.MaxStateLevel = 0; + + List batch = items.TakeBatch(256); + batch.Count.Should().Be(3); + } + [Test] public void Prioritizes_code_over_storage_over_state() { diff --git a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs index b396b0f465a3..c8927b9a1eed 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -13,33 +12,39 @@ using Nethermind.State; using Nethermind.State.Proofs; using Nethermind.State.Snap; +using Nethermind.Logging; using Nethermind.Synchronization.FastSync; using Nethermind.Synchronization.SnapSync; using NUnit.Framework; namespace Nethermind.Synchronization.Test.FastSync; +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +[TestFixture] [Parallelizable(ParallelScope.All)] public class StateSyncFeedHealingTests : StateSyncFeedTestsBase { [Test] public async Task HealTreeWithoutBoundaryProofs() { - DbContext dbContext = new DbContext(_logger, _logManager); - TestItem.Tree.FillStateTreeWithTestAccounts(dbContext.RemoteStateTree); + RemoteDbContext remote = new(_logManager); + TestItem.Tree.FillStateTreeWithTestAccounts(remote.StateTree); - Hash256 rootHash = dbContext.RemoteStateTree.RootHash; + Hash256 rootHash = remote.StateTree.RootHash; - ProcessAccountRange(dbContext.RemoteStateTree, dbContext.LocalStateTree, 1, rootHash, TestItem.Tree.AccountsWithPaths); + await using IContainer container = PrepareDownloader(remote, syncDispatcherAllocateTimeoutMs: 2000); + var local = container.Resolve(); + ISnapTrieFactory snapTrieFactory = container.Resolve(); + + ProcessAccountRange(remote.StateTree, snapTrieFactory, 1, rootHash, TestItem.Tree.AccountsWithPaths); - await using IContainer container = PrepareDownloader(dbContext); SafeContext ctx = container.Resolve(); await ActivateAndWait(ctx); DetailedProgress data = ctx.TreeFeed.GetDetailedProgress(); - dbContext.CompareTrees("END"); - Assert.That(dbContext.LocalStateTree.RootHash, Is.EqualTo(dbContext.RemoteStateTree.RootHash)); + local.CompareTrees(remote, _logger, "END"); + Assert.That(local.RootHash, Is.EqualTo(remote.StateTree.RootHash)); // I guess state root will be requested regardless Assert.That(data.RequestedNodesCount, Is.EqualTo(1)); // 4 boundary proof nodes stitched together => 0 @@ -48,11 +53,10 @@ public async Task HealTreeWithoutBoundaryProofs() [Test] public async Task HealBigSqueezedRandomTree() { - DbContext dbContext = new DbContext(_logger, _logManager); + RemoteDbContext remote = new(_logManager); int pathPoolCount = 100_000; Hash256[] pathPool = new Hash256[pathPoolCount]; - SortedDictionary accounts = new(); for (int i = 0; i < pathPoolCount; i++) { @@ -63,33 +67,35 @@ public async Task HealBigSqueezedRandomTree() pathPool[i] = keccak; } - // generate Remote Tree + int blockJumps = 5; + + // Store accounts snapshot at each block number + SortedDictionary[] accountsAtBlock = new SortedDictionary[blockJumps + 1]; + Hash256[] rootHashAtBlock = new Hash256[blockJumps + 1]; + + // Initialize accounts + SortedDictionary accounts = new(); + + // Generate initial Remote Tree (block 0) for (int accountIndex = 0; accountIndex < 10000; accountIndex++) { Account account = TestItem.GenerateRandomAccount(); Hash256 path = pathPool[TestItem.Random.Next(pathPool.Length - 1)]; - dbContext.RemoteStateTree.Set(path, account); + remote.StateTree.Set(path, account); accounts[path] = account; } - dbContext.RemoteStateTree.Commit(); + remote.StateTree.Commit(); - int startingHashIndex = 0; - int endHashIndex; - int blockJumps = 5; + // Pre-build all blocks and store state at each block for (int blockNumber = 1; blockNumber <= blockJumps; blockNumber++) { - for (int i = 0; i < 19; i++) - { - endHashIndex = startingHashIndex + 1000; - - ProcessAccountRange(dbContext.RemoteStateTree, dbContext.LocalStateTree, blockNumber, dbContext.RemoteStateTree.RootHash, - accounts.Where(a => a.Key >= pathPool[startingHashIndex] && a.Key <= pathPool[endHashIndex]).Select(a => new PathWithAccount(a.Key, a.Value)).ToArray()); - - startingHashIndex = endHashIndex + 1; - } + // Store snapshot of accounts and root hash at this block + accountsAtBlock[blockNumber] = new SortedDictionary(accounts); + rootHashAtBlock[blockNumber] = remote.StateTree.RootHash; + // Modify tree for next block for (int accountIndex = 0; accountIndex < 1000; accountIndex++) { Account account = TestItem.GenerateRandomAccount(); @@ -99,27 +105,56 @@ public async Task HealBigSqueezedRandomTree() { if (TestItem.Random.NextSingle() > 0.5) { - dbContext.RemoteStateTree.Set(path, account); + remote.StateTree.Set(path, account); accounts[path] = account; } else { - dbContext.RemoteStateTree.Set(path, null); + remote.StateTree.Set(path, null); accounts.Remove(path); } - - } else { - dbContext.RemoteStateTree.Set(path, account); + remote.StateTree.Set(path, account); accounts[path] = account; } } - dbContext.RemoteStateTree.Commit(); + remote.StateTree.Commit(); } + // Final state root + Hash256 finalRootHash = remote.StateTree.RootHash; + + await using IContainer container = PrepareDownloader(remote, syncDispatcherAllocateTimeoutMs: 1000); + var local = container.Resolve(); + ISnapTrieFactory snapTrieFactory = container.Resolve(); + + int startingHashIndex = 0; + int endHashIndex; + + // Now process account ranges using stored snapshots + for (int blockNumber = 1; blockNumber <= blockJumps; blockNumber++) + { + // Set remote tree to the state at this block number + remote.StateTree.RootHash = rootHashAtBlock[blockNumber]; + SortedDictionary blockAccounts = accountsAtBlock[blockNumber]; + + for (int i = 0; i < 19; i++) + { + endHashIndex = startingHashIndex + 1000; + + ProcessAccountRange(remote.StateTree, snapTrieFactory, blockNumber, rootHashAtBlock[blockNumber], + blockAccounts.Where(a => a.Key >= pathPool[startingHashIndex] && a.Key <= pathPool[endHashIndex]).Select(a => new PathWithAccount(a.Key, a.Value)).ToArray()); + + startingHashIndex = endHashIndex + 1; + } + } + + // Set remote tree back to final state for remaining processing + remote.StateTree.RootHash = finalRootHash; + endHashIndex = startingHashIndex + 1000; while (endHashIndex < pathPool.Length - 1) { @@ -129,28 +164,24 @@ public async Task HealBigSqueezedRandomTree() endHashIndex = pathPool.Length - 1; } - ProcessAccountRange(dbContext.RemoteStateTree, dbContext.LocalStateTree, blockJumps, dbContext.RemoteStateTree.RootHash, + ProcessAccountRange(remote.StateTree, snapTrieFactory, blockJumps, finalRootHash, accounts.Where(a => a.Key >= pathPool[startingHashIndex] && a.Key <= pathPool[endHashIndex]).Select(a => new PathWithAccount(a.Key, a.Value)).ToArray()); - startingHashIndex += 1000; } - dbContext.LocalStateTree.RootHash = dbContext.RemoteStateTree.RootHash; - - await using IContainer container = PrepareDownloader(dbContext, syncDispatcherAllocateTimeoutMs: 1000); SafeContext ctx = container.Resolve(); await ActivateAndWait(ctx, timeout: 20000); DetailedProgress data = ctx.TreeFeed.GetDetailedProgress(); - dbContext.LocalStateTree.UpdateRootHash(); - dbContext.CompareTrees("END"); + local.UpdateRootHash(); + local.CompareTrees(remote, _logger, "END"); _logger.Info($"REQUESTED NODES TO HEAL: {data.RequestedNodesCount}"); Assert.That(data.RequestedNodesCount, Is.LessThan(accounts.Count / 2)); } - private static void ProcessAccountRange(StateTree remoteStateTree, StateTree localStateTree, int blockNumber, Hash256 rootHash, PathWithAccount[] accounts) + private static void ProcessAccountRange(StateTree remoteStateTree, ISnapTrieFactory snapTrieFactory, int blockNumber, Hash256 rootHash, PathWithAccount[] accounts) { ValueHash256 startingHash = accounts.First().Path; ValueHash256 endHash = accounts.Last().Path; @@ -163,6 +194,6 @@ private static void ProcessAccountRange(StateTree remoteStateTree, StateTree loc remoteStateTree.Accept(accountProofCollector, remoteStateTree.RootHash); byte[][] lastProof = accountProofCollector.BuildResult().Proof!; - _ = SnapProviderHelper.AddAccountRange(localStateTree, blockNumber, rootHash, startingHash, limitHash, accounts, firstProof.Concat(lastProof).ToArray()); + _ = SnapProviderHelper.AddAccountRange(snapTrieFactory, blockNumber, rootHash, startingHash, limitHash, accounts, firstProof.Concat(lastProof).ToArray()); } } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedTests.cs index cd4ad936b116..bb7da579b385 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedTests.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Autofac; using FluentAssertions; @@ -30,77 +31,68 @@ namespace Nethermind.Synchronization.Test.FastSync [TestFixture(4, 0)] [TestFixture(4, 100)] [Parallelizable(ParallelScope.Fixtures)] - public class StateSyncFeedTests : StateSyncFeedTestsBase + public class StateSyncFeedTests( + int peerCount, + int maxNodeLatency) + : StateSyncFeedTestsBase(defaultPeerCount: peerCount, defaultPeerMaxRandomLatency: maxNodeLatency) { // Useful for set and forget run. But this test is taking a long time to have it set to other than 1. private const int TestRepeatCount = 1; - public StateSyncFeedTests(int peerCount, int maxNodeLatency) : base(peerCount, maxNodeLatency) - { - } - [Test] [TestCaseSource(nameof(Scenarios))] [Repeat(TestRepeatCount)] + [Explicit("This test is not stable, especially on slow Github Actions machines")] public async Task Big_test((string Name, Action SetupTree) testCase) { - DbContext dbContext = new(_logger, _logManager) - { - RemoteCodeDb = - { - [Keccak.Compute(TrieScenarios.Code0).Bytes] = TrieScenarios.Code0, - [Keccak.Compute(TrieScenarios.Code1).Bytes] = TrieScenarios.Code1, - [Keccak.Compute(TrieScenarios.Code2).Bytes] = TrieScenarios.Code2, - [Keccak.Compute(TrieScenarios.Code3).Bytes] = TrieScenarios.Code3, - }, - }; - testCase.SetupTree(dbContext.RemoteStateTree, dbContext.RemoteTrieStore, dbContext.RemoteCodeDb); - - dbContext.CompareTrees("BEFORE FIRST SYNC", true); - - await using IContainer container = PrepareDownloader(dbContext, mock => - mock.SetFilter(((MemDb)dbContext.RemoteStateDb).Keys.Take(((MemDb)dbContext.RemoteStateDb).Keys.Count - 4).Select(k => - { - return HashKey(k); - }).ToArray())); + RemoteDbContext remote = new(_logManager); + remote.CodeDb[Keccak.Compute(TrieScenarios.Code0).Bytes] = TrieScenarios.Code0; + remote.CodeDb[Keccak.Compute(TrieScenarios.Code1).Bytes] = TrieScenarios.Code1; + remote.CodeDb[Keccak.Compute(TrieScenarios.Code2).Bytes] = TrieScenarios.Code2; + remote.CodeDb[Keccak.Compute(TrieScenarios.Code3).Bytes] = TrieScenarios.Code3; + testCase.SetupTree(remote.StateTree, remote.TrieStore, remote.CodeDb); + + await using IContainer container = PrepareDownloader(remote, mock => + mock.SetFilter(((MemDb)remote.StateDb).Keys.Take(((MemDb)remote.StateDb).Keys.Count - 4).Select(HashKey).ToArray())); + var local = container.Resolve(); + + local.CompareTrees(remote, _logger, "BEFORE FIRST SYNC", true); SafeContext ctx = container.Resolve(); await ActivateAndWait(ctx); - dbContext.CompareTrees("AFTER FIRST SYNC", true); + local.CompareTrees(remote, _logger, "AFTER FIRST SYNC", true); - dbContext.LocalStateTree.RootHash = dbContext.RemoteStateTree.RootHash; for (byte i = 0; i < 8; i++) - dbContext.RemoteStateTree + remote.StateTree .Set(TestItem.Addresses[i], TrieScenarios.AccountJustState0.WithChangedBalance(i) .WithChangedNonce(1) .WithChangedCodeHash(Keccak.Compute(TrieScenarios.Code3)) - .WithChangedStorageRoot(SetStorage(dbContext.RemoteTrieStore, i, TestItem.Addresses[i]).RootHash)); + .WithChangedStorageRoot(SetStorage(remote.TrieStore, i, TestItem.Addresses[i]).RootHash)); - dbContext.RemoteStateTree.UpdateRootHash(); - dbContext.RemoteStateTree.Commit(); + remote.StateTree.UpdateRootHash(); + remote.StateTree.Commit(); - ctx.SuggestBlocksWithUpdatedRootHash(dbContext.RemoteStateTree.RootHash); + await ctx.SuggestBlocksWithUpdatedRootHash(remote.StateTree.RootHash); ctx.Feed.FallAsleep(); ctx.Pool.WakeUpAll(); await ActivateAndWait(ctx); - dbContext.CompareTrees("AFTER SECOND SYNC", true); + local.CompareTrees(remote, _logger, "AFTER SECOND SYNC", true); - dbContext.LocalStateTree.RootHash = dbContext.RemoteStateTree.RootHash; for (byte i = 0; i < 16; i++) - dbContext.RemoteStateTree + remote.StateTree .Set(TestItem.Addresses[i], TrieScenarios.AccountJustState0.WithChangedBalance(i) .WithChangedNonce(2) .WithChangedCodeHash(Keccak.Compute(TrieScenarios.Code3)) - .WithChangedStorageRoot(SetStorage(dbContext.RemoteTrieStore, (byte)(i % 7), TestItem.Addresses[i]).RootHash)); + .WithChangedStorageRoot(SetStorage(remote.TrieStore, (byte)(i % 7), TestItem.Addresses[i]).RootHash)); - dbContext.RemoteStateTree.UpdateRootHash(); - dbContext.RemoteStateTree.Commit(); + remote.StateTree.UpdateRootHash(); + remote.StateTree.Commit(); - ctx.SuggestBlocksWithUpdatedRootHash(dbContext.RemoteStateTree.RootHash); + await ctx.SuggestBlocksWithUpdatedRootHash(remote.StateTree.RootHash); ctx.Feed.FallAsleep(); ctx.Pool.WakeUpAll(); @@ -111,8 +103,8 @@ public async Task Big_test((string Name, Action Setu await ActivateAndWait(ctx); - dbContext.CompareTrees("END"); - dbContext.AssertFlushed(); + local.CompareTrees(remote, _logger, "END"); + local.AssertFlushed(); } private static Hash256 HashKey(byte[] k) @@ -125,28 +117,30 @@ private static Hash256 HashKey(byte[] k) [Repeat(TestRepeatCount)] public async Task Can_download_a_full_state((string Name, Action SetupTree) testCase) { - DbContext dbContext = new(_logger, _logManager); - testCase.SetupTree(dbContext.RemoteStateTree, dbContext.RemoteTrieStore, dbContext.RemoteCodeDb); + RemoteDbContext remote = new(_logManager); + testCase.SetupTree(remote.StateTree, remote.TrieStore, remote.CodeDb); + await using IContainer container = PrepareDownloader(remote); + var local = container.Resolve(); - dbContext.CompareTrees("BEGIN"); + local.CompareTrees(remote, _logger, "BEGIN"); - await using IContainer container = PrepareDownloader(dbContext); SafeContext ctx = container.Resolve(); await ActivateAndWait(ctx); - dbContext.CompareTrees("END"); + local.CompareTrees(remote, _logger, "END"); } [Test] [Repeat(TestRepeatCount)] public async Task Can_download_an_empty_tree() { - DbContext dbContext = new(_logger, _logManager); - await using IContainer container = PrepareDownloader(dbContext); + RemoteDbContext remote = new(_logManager); + await using IContainer container = PrepareDownloader(remote); + var local = container.Resolve(); SafeContext ctx = container.Resolve(); await ActivateAndWait(ctx); - dbContext.CompareTrees("END"); + local.CompareTrees(remote, _logger, "END"); } [Test] @@ -154,16 +148,15 @@ public async Task Can_download_an_empty_tree() [Repeat(TestRepeatCount)] public async Task Can_download_in_multiple_connections((string Name, Action SetupTree) testCase) { - DbContext dbContext = new(_logger, _logManager); - testCase.SetupTree(dbContext.RemoteStateTree, dbContext.RemoteTrieStore, dbContext.RemoteCodeDb); + RemoteDbContext remote = new(_logManager); + testCase.SetupTree(remote.StateTree, remote.TrieStore, remote.CodeDb); - - await using IContainer container = PrepareDownloader(dbContext, mock => - mock.SetFilter(new[] { dbContext.RemoteStateTree.RootHash })); + await using IContainer container = PrepareDownloader(remote, mock => + mock.SetFilter(new[] { remote.StateTree.RootHash })); + var local = container.Resolve(); SafeContext ctx = container.Resolve(); await ActivateAndWait(ctx, 1000); - ctx.Pool.WakeUpAll(); foreach (SyncPeerMock mock in ctx.SyncPeerMocks) { @@ -173,8 +166,7 @@ public async Task Can_download_in_multiple_connections((string Name, Action SetupTree) testCase) { - DbContext dbContext = new(_logger, _logManager); - testCase.SetupTree(dbContext.RemoteStateTree, dbContext.RemoteTrieStore, dbContext.RemoteCodeDb); + RemoteDbContext remote = new(_logManager); + testCase.SetupTree(remote.StateTree, remote.TrieStore, remote.CodeDb); + await using IContainer container = PrepareDownloader(remote, static mock => mock.MaxResponseLength = 1); + var local = container.Resolve(); - dbContext.CompareTrees("BEGIN"); + local.CompareTrees(remote, _logger, "BEGIN"); - await using IContainer container = PrepareDownloader(dbContext, static mock => mock.MaxResponseLength = 1); SafeContext ctx = container.Resolve(); await ActivateAndWait(ctx); - - dbContext.CompareTrees("END"); + local.CompareTrees(remote, _logger, "END"); } [Test] public async Task When_saving_root_goes_asleep_and_then_restart_to_new_tree_when_reactivated() { - DbContext dbContext = new(_logger, _logManager); - dbContext.RemoteStateTree.Set(TestItem.KeccakA, Build.An.Account.TestObject); - dbContext.RemoteStateTree.Commit(); + RemoteDbContext remote = new(_logManager); + remote.StateTree.Set(TestItem.KeccakA, Build.An.Account.TestObject); + remote.StateTree.Commit(); - dbContext.CompareTrees("BEGIN"); + await using IContainer container = PrepareDownloader(remote); + var local = container.Resolve(); + + local.CompareTrees(remote, _logger, "BEGIN"); - await using IContainer container = PrepareDownloader(dbContext); SafeContext ctx = container.Resolve(); await ActivateAndWait(ctx); - dbContext.CompareTrees("END"); + local.CompareTrees(remote, _logger, "END"); ctx.Feed.CurrentState.Should().Be(SyncFeedState.Dormant); } @@ -220,36 +214,36 @@ public async Task When_saving_root_goes_asleep_and_then_restart_to_new_tree_when [Retry(3)] public async Task Can_download_with_moving_target((string Name, Action SetupTree) testCase) { - DbContext dbContext = new(_logger, _logManager); - testCase.SetupTree(dbContext.RemoteStateTree, dbContext.RemoteTrieStore, dbContext.RemoteCodeDb); + RemoteDbContext remote = new(_logManager); + testCase.SetupTree(remote.StateTree, remote.TrieStore, remote.CodeDb); + + await using IContainer container = PrepareDownloader(remote, mock => + mock.SetFilter(((MemDb)remote.StateDb).Keys.Take(((MemDb)remote.StateDb).Keys.Count - 1).Select(k => HashKey(k)).ToArray())); + var local = container.Resolve(); - dbContext.CompareTrees("BEFORE FIRST SYNC"); + local.CompareTrees(remote, _logger, "BEFORE FIRST SYNC"); - await using IContainer container = PrepareDownloader(dbContext, mock => - mock.SetFilter(((MemDb)dbContext.RemoteStateDb).Keys.Take(((MemDb)dbContext.RemoteStateDb).Keys.Count - 1).Select(k => HashKey(k)).ToArray())); SafeContext ctx = container.Resolve(); await ActivateAndWait(ctx, TimeoutLength); + local.CompareTrees(remote, _logger, "AFTER FIRST SYNC"); - dbContext.CompareTrees("AFTER FIRST SYNC"); + remote.StateTree.Set(TestItem.AddressA, TrieScenarios.AccountJustState0.WithChangedBalance(123.Ether())); + remote.StateTree.Set(TestItem.AddressB, TrieScenarios.AccountJustState1.WithChangedBalance(123.Ether())); + remote.StateTree.Set(TestItem.AddressC, TrieScenarios.AccountJustState2.WithChangedBalance(123.Ether())); - dbContext.LocalStateTree.RootHash = dbContext.RemoteStateTree.RootHash; - dbContext.RemoteStateTree.Set(TestItem.AddressA, TrieScenarios.AccountJustState0.WithChangedBalance(123.Ether())); - dbContext.RemoteStateTree.Set(TestItem.AddressB, TrieScenarios.AccountJustState1.WithChangedBalance(123.Ether())); - dbContext.RemoteStateTree.Set(TestItem.AddressC, TrieScenarios.AccountJustState2.WithChangedBalance(123.Ether())); + local.CompareTrees(remote, _logger, "BEFORE ROOT HASH UPDATE"); - dbContext.CompareTrees("BEFORE ROOT HASH UPDATE"); + remote.StateTree.UpdateRootHash(); - dbContext.RemoteStateTree.UpdateRootHash(); + local.CompareTrees(remote, _logger, "BEFORE COMMIT"); - dbContext.CompareTrees("BEFORE COMMIT"); - - dbContext.RemoteStateTree.Commit(); + remote.StateTree.Commit(); ctx.Pool.WakeUpAll(); ctx.Feed.FallAsleep(); - ctx.SuggestBlocksWithUpdatedRootHash(dbContext.RemoteStateTree.RootHash); + await ctx.SuggestBlocksWithUpdatedRootHash(remote.StateTree.RootHash); foreach (SyncPeerMock mock in ctx.SyncPeerMocks) { @@ -258,8 +252,7 @@ public async Task Can_download_with_moving_target((string Name, Action SetupTree) testCase) { - DbContext dbContext = new(_logger, _logManager); - testCase.SetupTree(dbContext.RemoteStateTree, dbContext.RemoteTrieStore, dbContext.RemoteCodeDb); - + RemoteDbContext remote = new(_logManager); + testCase.SetupTree(remote.StateTree, remote.TrieStore, remote.CodeDb); - StorageTree remoteStorageTree = new(dbContext.RemoteTrieStore.GetTrieStore(TestItem.AddressD), Keccak.EmptyTreeHash, LimboLogs.Instance); + StorageTree remoteStorageTree = new(remote.TrieStore.GetTrieStore(TestItem.AddressD), Keccak.EmptyTreeHash, LimboLogs.Instance); remoteStorageTree.Set( Bytes.FromHexString("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb000"), new byte[] { 1 }); remoteStorageTree.Set( @@ -282,17 +274,18 @@ public async Task Dependent_branch_counter_is_zero_and_leaf_is_short((string Nam Bytes.FromHexString("eeeeeeeeeeeeeeeeeeeeeb111111111111111111111111111111111111111111"), new byte[] { 1 }); remoteStorageTree.Commit(); - dbContext.RemoteStateTree.Set(TestItem.AddressD, TrieScenarios.AccountJustState0.WithChangedStorageRoot(remoteStorageTree.RootHash)); - dbContext.RemoteStateTree.Commit(); + remote.StateTree.Set(TestItem.AddressD, TrieScenarios.AccountJustState0.WithChangedStorageRoot(remoteStorageTree.RootHash)); + remote.StateTree.Commit(); - dbContext.CompareTrees("BEGIN"); + await using IContainer container = PrepareDownloader(remote); + var local = container.Resolve(); + + local.CompareTrees(remote, _logger, "BEGIN"); - await using IContainer container = PrepareDownloader(dbContext); SafeContext ctx = container.Resolve(); await ActivateAndWait(ctx); - - dbContext.CompareTrees("END"); + local.CompareTrees(remote, _logger, "END"); } [Test] @@ -300,24 +293,24 @@ public async Task Dependent_branch_counter_is_zero_and_leaf_is_short((string Nam [Repeat(TestRepeatCount)] public async Task Scenario_plus_one_code((string Name, Action SetupTree) testCase) { - DbContext dbContext = new(_logger, _logManager); - testCase.SetupTree(dbContext.RemoteStateTree, dbContext.RemoteTrieStore, dbContext.RemoteCodeDb); + RemoteDbContext remote = new(_logManager); + testCase.SetupTree(remote.StateTree, remote.TrieStore, remote.CodeDb); - - dbContext.RemoteCodeDb.Set(Keccak.Compute(TrieScenarios.Code0), TrieScenarios.Code0); + remote.CodeDb.Set(Keccak.Compute(TrieScenarios.Code0), TrieScenarios.Code0); Account changedAccount = TrieScenarios.AccountJustState0.WithChangedCodeHash(Keccak.Compute(TrieScenarios.Code0)); - dbContext.RemoteStateTree.Set(TestItem.AddressD, changedAccount); - dbContext.RemoteStateTree.Commit(); + remote.StateTree.Set(TestItem.AddressD, changedAccount); + remote.StateTree.Commit(); + + await using IContainer container = PrepareDownloader(remote); + var local = container.Resolve(); - dbContext.CompareTrees("BEGIN"); + local.CompareTrees(remote, _logger, "BEGIN"); - await using IContainer container = PrepareDownloader(dbContext); SafeContext ctx = container.Resolve(); await ActivateAndWait(ctx); - - dbContext.CompareTrees("END"); + local.CompareTrees(remote, _logger, "END"); } [Test] @@ -325,27 +318,27 @@ public async Task Scenario_plus_one_code((string Name, Action SetupTree) testCase) { - DbContext dbContext = new(_logger, _logManager); - testCase.SetupTree(dbContext.RemoteStateTree, dbContext.RemoteTrieStore, dbContext.RemoteCodeDb); - + RemoteDbContext remote = new(_logManager); + testCase.SetupTree(remote.StateTree, remote.TrieStore, remote.CodeDb); - dbContext.RemoteCodeDb.Set(Keccak.Compute(TrieScenarios.Code0), TrieScenarios.Code0); + remote.CodeDb.Set(Keccak.Compute(TrieScenarios.Code0), TrieScenarios.Code0); - StorageTree remoteStorageTree = new(dbContext.RemoteTrieStore.GetTrieStore(TestItem.AddressD), Keccak.EmptyTreeHash, _logManager); + StorageTree remoteStorageTree = new(remote.TrieStore.GetTrieStore(TestItem.AddressD), Keccak.EmptyTreeHash, _logManager); remoteStorageTree.Set((UInt256)1, new byte[] { 1 }); remoteStorageTree.Commit(); - dbContext.RemoteStateTree.Set(TestItem.AddressD, TrieScenarios.AccountJustState0.WithChangedCodeHash(Keccak.Compute(TrieScenarios.Code0)).WithChangedStorageRoot(remoteStorageTree.RootHash)); - dbContext.RemoteStateTree.Commit(); + remote.StateTree.Set(TestItem.AddressD, TrieScenarios.AccountJustState0.WithChangedCodeHash(Keccak.Compute(TrieScenarios.Code0)).WithChangedStorageRoot(remoteStorageTree.RootHash)); + remote.StateTree.Commit(); - dbContext.CompareTrees("BEGIN"); + await using IContainer container = PrepareDownloader(remote); + var local = container.Resolve(); + + local.CompareTrees(remote, _logger, "BEGIN"); - await using IContainer container = PrepareDownloader(dbContext); SafeContext ctx = container.Resolve(); await ActivateAndWait(ctx); - - dbContext.CompareTrees("END"); + local.CompareTrees(remote, _logger, "END"); } [Test] @@ -353,35 +346,36 @@ public async Task Scenario_plus_one_code_one_storage((string Name, Action SetupTree) testCase) { - DbContext dbContext = new(_logger, _logManager); - testCase.SetupTree(dbContext.RemoteStateTree, dbContext.RemoteTrieStore, dbContext.RemoteCodeDb); + RemoteDbContext remote = new(_logManager); + testCase.SetupTree(remote.StateTree, remote.TrieStore, remote.CodeDb); - StorageTree remoteStorageTree = new(dbContext.RemoteTrieStore.GetTrieStore(TestItem.AddressD), Keccak.EmptyTreeHash, _logManager); + StorageTree remoteStorageTree = new(remote.TrieStore.GetTrieStore(TestItem.AddressD), Keccak.EmptyTreeHash, _logManager); remoteStorageTree.Set((UInt256)1, new byte[] { 1 }); remoteStorageTree.Commit(); - dbContext.RemoteStateTree.Set(TestItem.AddressD, TrieScenarios.AccountJustState0.WithChangedStorageRoot(remoteStorageTree.RootHash)); - dbContext.RemoteStateTree.Set(TestItem.AddressD, TrieScenarios.AccountJustState0.WithChangedStorageRoot(remoteStorageTree.RootHash)); - dbContext.RemoteStateTree.Commit(); + remote.StateTree.Set(TestItem.AddressD, TrieScenarios.AccountJustState0.WithChangedStorageRoot(remoteStorageTree.RootHash)); + remote.StateTree.Set(TestItem.AddressD, TrieScenarios.AccountJustState0.WithChangedStorageRoot(remoteStorageTree.RootHash)); + remote.StateTree.Commit(); - dbContext.CompareTrees("BEGIN"); + await using IContainer container = PrepareDownloader(remote); + var local = container.Resolve(); + + local.CompareTrees(remote, _logger, "BEGIN"); - await using IContainer container = PrepareDownloader(dbContext); SafeContext ctx = container.Resolve(); await ActivateAndWait(ctx); - - dbContext.CompareTrees("END"); + local.CompareTrees(remote, _logger, "END"); } [Test] public async Task When_empty_response_received_return_lesser_quality() { - DbContext dbContext = new(_logger, _logManager); - dbContext.RemoteStateTree.Set(TestItem.KeccakA, Build.An.Account.TestObject); - dbContext.RemoteStateTree.Commit(); + RemoteDbContext remote = new(_logManager); + remote.StateTree.Set(TestItem.KeccakA, Build.An.Account.TestObject); + remote.StateTree.Commit(); - await using IContainer container = BuildTestContainerBuilder(dbContext) + await using IContainer container = BuildTestContainerBuilder(remote) .Build(); SafeContext ctx = container.Resolve(); @@ -396,11 +390,11 @@ public async Task When_empty_response_received_return_lesser_quality() [Test] public async Task When_empty_response_received_with_no_peer_return_not_allocated() { - DbContext dbContext = new(_logger, _logManager); - dbContext.RemoteStateTree.Set(TestItem.KeccakA, Build.An.Account.TestObject); - dbContext.RemoteStateTree.Commit(); + RemoteDbContext remote = new(_logManager); + remote.StateTree.Set(TestItem.KeccakA, Build.An.Account.TestObject); + remote.StateTree.Commit(); - await using IContainer container = BuildTestContainerBuilder(dbContext) + await using IContainer container = BuildTestContainerBuilder(remote) .Build(); SafeContext ctx = container.Resolve(); @@ -416,48 +410,122 @@ public async Task When_empty_response_received_with_no_peer_return_not_allocated [Repeat(TestRepeatCount)] public async Task RepairPossiblyMissingStorage() { - DbContext dbContext = new(_logger, _logManager) - { - RemoteCodeDb = - { - [Keccak.Compute(TrieScenarios.Code0).Bytes] = TrieScenarios.Code0, - [Keccak.Compute(TrieScenarios.Code1).Bytes] = TrieScenarios.Code1, - [Keccak.Compute(TrieScenarios.Code2).Bytes] = TrieScenarios.Code2, - [Keccak.Compute(TrieScenarios.Code3).Bytes] = TrieScenarios.Code3, - }, - }; + RemoteDbContext remote = new(_logManager); + remote.CodeDb[Keccak.Compute(TrieScenarios.Code0).Bytes] = TrieScenarios.Code0; + remote.CodeDb[Keccak.Compute(TrieScenarios.Code1).Bytes] = TrieScenarios.Code1; + remote.CodeDb[Keccak.Compute(TrieScenarios.Code2).Bytes] = TrieScenarios.Code2; + remote.CodeDb[Keccak.Compute(TrieScenarios.Code3).Bytes] = TrieScenarios.Code3; Hash256 theAccount = TestItem.KeccakA; - StorageTree storageTree = new StorageTree(dbContext.RemoteTrieStore.GetTrieStore(theAccount), LimboLogs.Instance); + StorageTree storageTree = new StorageTree(remote.TrieStore.GetTrieStore(theAccount), LimboLogs.Instance); for (int i = 0; i < 10; i++) { storageTree.Set((UInt256)i, TestItem.Keccaks[i].BytesToArray()); } storageTree.Commit(); - StateTree state = dbContext.RemoteStateTree; + StateTree state = remote.StateTree; state.Set(TestItem.KeccakA, Build.An.Account.WithNonce(1).WithStorageRoot(storageTree.RootHash).TestObject); state.Set(TestItem.KeccakB, Build.An.Account.WithNonce(1).TestObject); state.Set(TestItem.KeccakC, Build.An.Account.WithNonce(1).TestObject); state.Commit(); + await using IContainer container = PrepareDownloader(remote); + var local = container.Resolve(); + // Local state only have the state - state = dbContext.LocalStateTree; - state.Set(TestItem.KeccakA, Build.An.Account.WithNonce(1).WithStorageRoot(storageTree.RootHash).TestObject); - state.Set(TestItem.KeccakB, Build.An.Account.WithNonce(1).TestObject); - state.Set(TestItem.KeccakC, Build.An.Account.WithNonce(1).TestObject); - state.Commit(); + local.SetAccountsAndCommit( + (TestItem.KeccakA, Build.An.Account.WithNonce(1).WithStorageRoot(storageTree.RootHash).TestObject), + (TestItem.KeccakB, Build.An.Account.WithNonce(1).TestObject), + (TestItem.KeccakC, Build.An.Account.WithNonce(1).TestObject)); // Local state missing root so that it would start - dbContext.LocalNodeStorage.Set(null, TreePath.Empty, state.RootHash, null); + local.DeleteStateRoot(); - await using IContainer container = PrepareDownloader(dbContext); container.Resolve().UpdatedStorages.Add(theAccount); SafeContext ctx = container.Resolve(); await ActivateAndWait(ctx); - dbContext.CompareTrees("END"); + local.CompareTrees(remote, _logger, "END"); + } + + [Test] + [Repeat(TestRepeatCount)] + [CancelAfter(10000)] + public async Task Pending_items_cache_mechanism_works_across_root_changes(CancellationToken cancellation) + { + RemoteDbContext remote = new(_logManager); + remote.CodeDb[Keccak.Compute(TrieScenarios.Code0).Bytes] = TrieScenarios.Code0; + remote.CodeDb[Keccak.Compute(TrieScenarios.Code1).Bytes] = TrieScenarios.Code1; + + // Set some data + for (byte i = 0; i < 12; i++) + { + StorageTree storage = SetStorage(remote.TrieStore, (byte)(i + 1), TestItem.Addresses[i]); + remote.StateTree.Set( + TestItem.Addresses[i], + TrieScenarios.AccountJustState0 + .WithChangedBalance((UInt256)(i + 10)) + .WithChangedNonce((UInt256)1) + .WithChangedCodeHash(Keccak.Compute(TrieScenarios.Code0)) + .WithChangedStorageRoot(storage.RootHash)); + } + remote.StateTree.UpdateRootHash(); + remote.StateTree.Commit(); + + await using IContainer container = PrepareDownloader(remote); + SafeContext ctx = container.Resolve(); + + ctx.Feed.SyncModeSelectorOnChanged(SyncMode.StateNodes); + + async Task RunOneRequest() + { + using StateSyncBatch request = (await ctx.Feed.PrepareRequest(cancellation))!; + if (request is null) return 0; + PeerInfo peer = new PeerInfo(ctx.SyncPeerMocks[0]); + await ctx.Downloader.Dispatch(peer, request!, cancellation); + int requestCount = request.RequestedNodes?.Count ?? 0; + ctx.Feed.HandleResponse(request, peer); + return requestCount; + } + + int totalRequest = 0; + for (int i = 0; i < 5; i++) + { + int oneCycleRequest = await RunOneRequest(); + if (oneCycleRequest == 0) break; + totalRequest += oneCycleRequest; + } + + for (byte i = 0; i < 4; i++) + { + StorageTree storage = SetStorage(remote.TrieStore, (byte)(i + 2), TestItem.Addresses[i]); + remote.StateTree.Set( + TestItem.Addresses[i], + TrieScenarios.AccountJustState0 + .WithChangedBalance((UInt256)(i + 100)) + .WithChangedNonce((UInt256)2) + .WithChangedCodeHash(Keccak.Compute(TrieScenarios.Code1)) + .WithChangedStorageRoot(storage.RootHash)); + } + remote.StateTree.UpdateRootHash(); + remote.StateTree.Commit(); + + await ctx.SuggestBlocksWithUpdatedRootHash(remote.StateTree.RootHash); + + ctx.Feed.FallAsleep(); + ctx.Feed.SyncModeSelectorOnChanged(SyncMode.StateNodes); + + int remainingRequest = 0; + for (int i = 0; i < 1000; i++) + { + int requestCount = await RunOneRequest(); + if (requestCount is 0) break; + remainingRequest += requestCount; + } + + remainingRequest.Should().Be(100); // Without the cache this would be 111 } } } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedTestsBase.cs b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedTestsBase.cs index e4cdc5e58dbd..2f4c0c04e3da 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedTestsBase.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedTestsBase.cs @@ -3,14 +3,15 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Autofac; +using Autofac.Features.AttributeFilters; using FluentAssertions; using Nethermind.Blockchain; using Nethermind.Blockchain.Synchronization; using Nethermind.Config; +using Nethermind.Consensus.Processing; using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; @@ -19,6 +20,7 @@ using Nethermind.Core.Test.Modules; using Nethermind.Core.Utils; using Nethermind.Db; +using Nethermind.Init.Modules; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Network.Contract.P2P; @@ -30,33 +32,26 @@ using Nethermind.Synchronization.FastSync; using Nethermind.Synchronization.ParallelSync; using Nethermind.Synchronization.Peers; +using Nethermind.Synchronization.SnapSync; using Nethermind.Synchronization.Test.ParallelSync; using Nethermind.Trie; using Nethermind.Trie.Pruning; -using NSubstitute; using NUnit.Framework; namespace Nethermind.Synchronization.Test.FastSync; -public abstract class StateSyncFeedTestsBase +public abstract class StateSyncFeedTestsBase( + int defaultPeerCount = 1, + int defaultPeerMaxRandomLatency = 0) { 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!; - private readonly int _defaultPeerCount; - private readonly int _defaultPeerMaxRandomLatency; - - public StateSyncFeedTestsBase(int defaultPeerCount = 1, int defaultPeerMaxRandomLatency = 0) - { - _defaultPeerCount = defaultPeerCount; - _defaultPeerMaxRandomLatency = defaultPeerMaxRandomLatency; - } - public static (string Name, Action Action)[] Scenarios => TrieScenarios.Scenarios; [SetUp] @@ -82,28 +77,31 @@ protected static StorageTree SetStorage(ITrieStore trieStore, byte i, Address ad return remoteStorageTree; } - protected IContainer PrepareDownloader(DbContext dbContext, Action? mockMutator = null, int syncDispatcherAllocateTimeoutMs = 10) + protected IContainer PrepareDownloader(RemoteDbContext remote, Action? mockMutator = null, int syncDispatcherAllocateTimeoutMs = 10) { - SyncPeerMock[] syncPeers = new SyncPeerMock[_defaultPeerCount]; - for (int i = 0; i < _defaultPeerCount; i++) + SyncPeerMock[] syncPeers = new SyncPeerMock[defaultPeerCount]; + for (int i = 0; i < defaultPeerCount; i++) { 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); + SyncPeerMock mock = new SyncPeerMock(remote.StateDb, remote.CodeDb, node: node, maxRandomizedLatencyMs: defaultPeerMaxRandomLatency); mockMutator?.Invoke(mock); syncPeers[i] = mock; } - ContainerBuilder builder = BuildTestContainerBuilder(dbContext, syncDispatcherAllocateTimeoutMs) + ContainerBuilder builder = BuildTestContainerBuilder(remote, syncDispatcherAllocateTimeoutMs) .AddSingleton(syncPeers); 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); } }); @@ -111,7 +109,7 @@ protected IContainer PrepareDownloader(DbContext dbContext, Action return builder.Build(); } - protected ContainerBuilder BuildTestContainerBuilder(DbContext dbContext, int syncDispatcherAllocateTimeoutMs = 10) + protected ContainerBuilder BuildTestContainerBuilder(RemoteDbContext remote, int syncDispatcherAllocateTimeoutMs = 10) { ContainerBuilder containerBuilder = new ContainerBuilder() .AddModule(new TestNethermindModule(new ConfigProvider(new SyncConfig() @@ -124,16 +122,22 @@ protected ContainerBuilder BuildTestContainerBuilder(DbContext dbContext, int sy return syncConfig; }) .AddSingleton(_logManager) - .AddKeyedSingleton(DbNames.Code, dbContext.LocalCodeDb) - .AddKeyedSingleton(DbNames.State, dbContext.LocalStateDb) - .AddSingleton(dbContext.LocalNodeStorage) + .AddKeyedSingleton(DbNames.Code, (_) => new TestMemDb()) // 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}{remote.StateTree.RootHash}{TestChainLength}", + () => Build.A.BlockTree().WithStateRoot(remote.StateTree.RootHash).OfChainLength(TestChainLength))) + + .Add() - .Add(); + // State DB and INodeStorage are needed by SynchronizerModule components (e.g. PathNodeRecovery) + .AddKeyedSingleton(DbNames.State, (_) => new TestMemDb()) + .AddSingleton((ctx) => new NodeStorage(ctx.ResolveNamed(DbNames.State))) + + .AddSingleton() + .AddSingleton(); containerBuilder.RegisterBuildCallback((ctx) => { @@ -169,7 +173,9 @@ protected class SafeContext( Lazy syncPeerPool, Lazy treeSync, Lazy stateSyncFeed, + Lazy> downloader, Lazy> syncDispatcher, + Lazy blockProcessingQueue, IBlockTree blockTree ) : IDisposable { @@ -177,20 +183,23 @@ IBlockTree blockTree public ISyncPeerPool Pool => syncPeerPool.Value; public TreeSync TreeFeed => treeSync.Value; public StateSyncFeed Feed => stateSyncFeed.Value; + public IBlockProcessingQueue BlockProcessingQueue => blockProcessingQueue.Value; + + public ISyncDownloader Downloader => downloader.Value; - private readonly AutoCancelTokenSource _autoCancelTokenSource = new AutoCancelTokenSource(); + private readonly AutoCancelTokenSource _autoCancelTokenSource = new(); public CancellationToken CancellationToken => _autoCancelTokenSource.Token; private bool _isDisposed; - public void SuggestBlocksWithUpdatedRootHash(Hash256 newRootHash) + public async Task SuggestBlocksWithUpdatedRootHash(Hash256 newRootHash) { Block newBlock = Build.A.Block .WithParent(blockTree.BestSuggestedHeader!) .WithStateRoot(newRootHash) .TestObject; - blockTree.SuggestBlock(newBlock).Should().Be(AddBlockResult.Added); + (await blockTree.SuggestBlockAsync(newBlock)).Should().Be(AddBlockResult.Added); blockTree.UpdateMainChain([newBlock], false, true); } @@ -207,70 +216,6 @@ public void Dispose() } } - protected class DbContext - { - private readonly ILogger _logger; - - public DbContext(ILogger logger, ILogManager logManager) - { - _logger = logger; - RemoteDb = new MemDb(); - LocalDb = new TestMemDb(); - RemoteStateDb = RemoteDb; - LocalStateDb = LocalDb; - LocalNodeStorage = new NodeStorage(LocalDb); - LocalCodeDb = new TestMemDb(); - RemoteCodeDb = new MemDb(); - RemoteTrieStore = TestTrieStoreFactory.Build(RemoteStateDb, logManager); - - RemoteStateTree = new StateTree(RemoteTrieStore, logManager); - LocalStateTree = new StateTree(TestTrieStoreFactory.Build(LocalStateDb, logManager), logManager); - } - - public MemDb RemoteCodeDb { get; } - public TestMemDb LocalCodeDb { get; } - public MemDb RemoteDb { get; } - public TestMemDb LocalDb { get; } - public ITrieStore RemoteTrieStore { get; } - public IDb RemoteStateDb { get; } - public IDb LocalStateDb { get; } - public NodeStorage LocalNodeStorage { get; } - public StateTree RemoteStateTree { get; } - public StateTree LocalStateTree { get; } - - public void CompareTrees(string stage, bool skipLogs = false) - { - if (!skipLogs) _logger.Info($"==================== {stage} ===================="); - LocalStateTree.RootHash = RemoteStateTree.RootHash; - - if (!skipLogs) _logger.Info("-------------------- REMOTE --------------------"); - TreeDumper dumper = new TreeDumper(); - RemoteStateTree.Accept(dumper, RemoteStateTree.RootHash); - string remote = dumper.ToString(); - if (!skipLogs) _logger.Info(remote); - if (!skipLogs) _logger.Info("-------------------- LOCAL --------------------"); - dumper.Reset(); - LocalStateTree.Accept(dumper, LocalStateTree.RootHash); - string local = dumper.ToString(); - if (!skipLogs) _logger.Info(local); - - if (stage == "END") - { - Assert.That(local, Is.EqualTo(remote), $"{stage}{Environment.NewLine}{remote}{Environment.NewLine}{local}"); - TrieStatsCollector collector = new(LocalCodeDb, LimboLogs.Instance); - LocalStateTree.Accept(collector, LocalStateTree.RootHash); - Assert.That(collector.Stats.MissingNodes, Is.EqualTo(0)); - Assert.That(collector.Stats.MissingCode, Is.EqualTo(0)); - } - } - - public void AssertFlushed() - { - LocalDb.WasFlushed.Should().BeTrue(); - LocalCodeDb.WasFlushed.Should().BeTrue(); - } - } - protected class SyncPeerMock : BaseSyncPeerMock { public override string Name => "Mock"; @@ -283,6 +228,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, @@ -294,18 +242,17 @@ 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(); - alwaysAvailableRootTracker.HasStateForBlock(Arg.Any()).Returns(true); + PruningConfig pruningConfig = new PruningConfig(); + TestFinalizedStateProvider testFinalizedStateProvider = new TestFinalizedStateProvider(pruningConfig.PruningBoundary); TrieStore trieStore = new TrieStore(new NodeStorage(stateDb), Nethermind.Trie.Pruning.No.Pruning, - Persist.EveryBlock, new PruningConfig(), LimboLogs.Instance); + Persist.EveryBlock, testFinalizedStateProvider, pruningConfig, LimboLogs.Instance); _stateDb = trieStore.TrieNodeRlpStore; _snapServer = new SnapServer( trieStore.AsReadOnly(), codeDb, - alwaysAvailableRootTracker, LimboLogs.Instance); } @@ -346,6 +293,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) @@ -359,7 +311,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) @@ -384,3 +336,22 @@ public override Task> GetTrieNodes(GetTrieNodesReques } } } + +public class RemoteDbContext +{ + public RemoteDbContext(ILogManager logManager) + { + CodeDb = new MemDb(); + Db = new MemDb(); + TrieStore = TestTrieStoreFactory.Build(Db, logManager); + StateTree = new StateTree(TrieStore, logManager); + } + + public MemDb CodeDb { get; } + public MemDb Db { get; } + public IDb StateDb => Db; + public ITrieStore TrieStore { get; } + public StateTree StateTree { get; } +} + + diff --git a/src/Nethermind/Nethermind.Synchronization.Test/ForwardHeaderProviderTests.Merge.cs b/src/Nethermind/Nethermind.Synchronization.Test/ForwardHeaderProviderTests.Merge.cs index 693149ea997f..4bfadcba06a6 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/ForwardHeaderProviderTests.Merge.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/ForwardHeaderProviderTests.Merge.cs @@ -8,7 +8,6 @@ using FluentAssertions; using Nethermind.Blockchain; using Nethermind.Config; -using Nethermind.Consensus; using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Test.Builders; diff --git a/src/Nethermind/Nethermind.Synchronization.Test/ForwardHeaderProviderTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/ForwardHeaderProviderTests.cs index 8fdee4f9018a..6a9d65d761a5 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/ForwardHeaderProviderTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/ForwardHeaderProviderTests.cs @@ -33,7 +33,6 @@ using Nethermind.Config; using Nethermind.Core.Test; using Nethermind.Core.Test.Modules; -using Nethermind.Stats; using Nethermind.Synchronization.Peers.AllocationStrategies; @@ -118,7 +117,7 @@ public async Task Ancestor_lookup_with_sync_pivot() { }, new ConfigProvider(new SyncConfig() { - PivotNumber = syncPivot.Number.ToString(), + PivotNumber = syncPivot.Number, PivotHash = syncPivot.Hash!.ToString(), })); @@ -248,6 +247,31 @@ public async Task Cache_block_headers_unless_peer_changed() await newSyncPeer.Received(1).GetBlockHeaders(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); } + [Test] + public async Task Cache_block_headers_with_disposal() + { + await using IContainer node = CreateNode(); + Context ctx = node.Resolve(); + + ISyncPeer syncPeer = Substitute.For(); + syncPeer.TotalDifficulty.Returns(UInt256.MaxValue); + syncPeer.GetBlockHeaders(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(ci => ctx.ResponseBuilder.BuildHeaderResponse(ci.ArgAt(0), ci.ArgAt(1), Response.AllCorrect)); + + PeerInfo peerInfo = new(syncPeer); + syncPeer.HeadNumber.Returns(1000); + ctx.ConfigureBestPeer(peerInfo); + + IForwardHeaderProvider forwardHeader = ctx.ForwardHeaderProvider; + + using IOwnedReadOnlyList? headers1 = await forwardHeader.GetBlockHeaders(0, 128, CancellationToken.None); + headers1.Should().NotBeNull(); + using IOwnedReadOnlyList? headers2 = await forwardHeader.GetBlockHeaders(0, 128, CancellationToken.None); + headers2.Should().NotBeNull(); + + await syncPeer.Received(1).GetBlockHeaders(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); + } + private class SlowSealValidator : ISealValidator { public bool ValidateParams(BlockHeader parent, BlockHeader header, bool isUncle = false) @@ -403,6 +427,48 @@ public async Task Faults_on_get_headers_faulting() await headerTask.Should().ThrowAsync(); } + [Test] + public async Task Reports_weak_peer_on_timeout_cancellation() + { + await using IContainer node = CreateNode(); + Context ctx = node.Resolve(); + + ISyncPeer syncPeer = Substitute.For(); + syncPeer.TotalDifficulty.Returns(UInt256.MaxValue); + syncPeer.HeadNumber.Returns(1000); + syncPeer.GetBlockHeaders(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .Returns?>>(x => throw new OperationCanceledException()); + + PeerInfo peerInfo = new(syncPeer); + ctx.ConfigureBestPeer(peerInfo); + + IForwardHeaderProvider forwardHeader = ctx.ForwardHeaderProvider; + (await forwardHeader.GetBlockHeaders(0, 128, CancellationToken.None)).Should().BeNull(); + ctx.PeerPool.Received().ReportWeakPeer(peerInfo, AllocationContexts.ForwardHeader); + } + + [Test] + public async Task Throws_on_sync_cancellation() + { + await using IContainer node = CreateNode(); + Context ctx = node.Resolve(); + + using CancellationTokenSource cts = new(); + cts.Cancel(); + + ISyncPeer syncPeer = Substitute.For(); + syncPeer.TotalDifficulty.Returns(UInt256.MaxValue); + syncPeer.HeadNumber.Returns(1000); + syncPeer.GetBlockHeaders(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .Returns?>>(x => throw new OperationCanceledException()); + + ctx.ConfigureBestPeer(syncPeer); + + IForwardHeaderProvider forwardHeader = ctx.ForwardHeaderProvider; + Func act = () => forwardHeader.GetBlockHeaders(0, 128, cts.Token); + await act.Should().ThrowAsync(); + } + [Flags] private enum Response { diff --git a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/FullStateFinderTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/FullStateFinderTests.cs new file mode 100644 index 000000000000..412bb7384e4c --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/FullStateFinderTests.cs @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Blockchain; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.State; +using Nethermind.Synchronization.ParallelSync; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Synchronization.Test.ParallelSync; + +public class FullStateFinderTests +{ + private readonly Hash256 _goodRoot = Keccak.Compute("test"); + private readonly Hash256 _badRoot = Keccak.Compute("test2"); + + [Test] + public void TestWillCheckForState() + { + IBlockTree blockTree = Build.A.BlockTree() + .WithStateRoot((b) => b.Number == 950 ? _goodRoot : _badRoot) + .OfChainLength(1000) + .TestObject; + + IStateReader stateReader = Substitute.For(); + stateReader.HasStateForBlock(Arg.Is((header) => header.StateRoot == _goodRoot)).Returns(true); + + FullStateFinder finder = new FullStateFinder(blockTree, stateReader); + finder.FindBestFullState().Should().Be(950); + } + + [Test] + public void TestWillCheckForStateWhenItWasPreviouslyFound() + { + IBlockTree blockTree = Build.A.BlockTree() + .WithStateRoot((b) => b.Number == 50 ? _goodRoot : _badRoot) + .OfChainLength(100) + .TestObject; + + IStateReader stateReader = Substitute.For(); + stateReader.HasStateForBlock(Arg.Is((header) => header.StateRoot == _goodRoot)).Returns(true); + + FullStateFinder finder = new FullStateFinder(blockTree, stateReader); + finder.FindBestFullState().Should().Be(50); + + BlockHeader parent = blockTree.FindHeader(50, BlockTreeLookupOptions.None)!; + + for (int i = 0; i < 500; i++) + { + Block block = Build.A.Block + .WithParent(parent) + .WithStateRoot(_badRoot) + .TestObject; + + blockTree.SuggestBlock(block); + + parent = block.Header; + } + + finder.FindBestFullState().Should().Be(50); + } + +} diff --git a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTests.Scenario.cs b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTests.Scenario.cs index f5852c48faf2..8313c70c314a 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/ParallelSync/SyncDispatcherTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/SyncDispatcherTests.cs index fbe2037b97f8..d9a581d54c8a 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/SyncDispatcherTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/SyncDispatcherTests.cs @@ -4,23 +4,19 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using FluentAssertions; -using Nethermind.Blockchain; using Nethermind.Blockchain.Synchronization; using Nethermind.Core.Crypto; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Stats; using Nethermind.Stats.Model; -using Nethermind.Synchronization.FastBlocks; using Nethermind.Synchronization.ParallelSync; using Nethermind.Synchronization.Peers; using Nethermind.Synchronization.Peers.AllocationStrategies; using Nethermind.Synchronization.Test.Mocks; -using NSubstitute; using NUnit.Framework; namespace Nethermind.Synchronization.Test.ParallelSync; diff --git a/src/Nethermind/Nethermind.Synchronization.Test/RangeQueryVisitorTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/RangeQueryVisitorTests.cs index 82d0bb2e4b16..3c791f4be81e 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/RangeQueryVisitorTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/RangeQueryVisitorTests.cs @@ -25,11 +25,9 @@ using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; using Nethermind.Db; -using Nethermind.Logging; using Nethermind.Serialization.Rlp; using Nethermind.State; using Nethermind.Trie; -using Nethermind.Trie.Pruning; using NUnit.Framework; using Bytes = Nethermind.Core.Extensions.Bytes; diff --git a/src/Nethermind/Nethermind.Synchronization.Test/ReceiptSyncFeedTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/ReceiptSyncFeedTests.cs index 60c714970dd0..ff7c6bc9f6be 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/ISnapTestHelper.cs b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/ISnapTestHelper.cs new file mode 100644 index 000000000000..b59a14d9c12b --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/ISnapTestHelper.cs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Linq; +using Autofac.Features.AttributeFilters; +using Nethermind.Core.Crypto; +using Nethermind.Db; + +namespace Nethermind.Synchronization.Test.SnapSync; + +public interface ISnapTestHelper +{ + int CountTrieNodes(); + bool TrieNodeKeyExists(Hash256 hash); + long TrieNodeWritesCount { get; } +} + +public class PatriciaSnapTestHelper([KeyFilter(DbNames.State)] IDb stateDb) : ISnapTestHelper +{ + public int CountTrieNodes() => stateDb.GetAllKeys().Count(); + public bool TrieNodeKeyExists(Hash256 hash) => stateDb.KeyExists(hash.Bytes); + public long TrieNodeWritesCount => ((MemDb)stateDb).WritesCount; +} diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/ProgressTrackerTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/ProgressTrackerTests.cs index 637d854833ff..0e2ae35e704e 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/ProgressTrackerTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/ProgressTrackerTests.cs @@ -7,6 +7,7 @@ using Nethermind.Blockchain.Synchronization; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; using Nethermind.Db; @@ -58,37 +59,34 @@ public void Will_create_multiple_get_address_range_request() { using ProgressTracker progressTracker = CreateProgressTracker(accountRangePartition: 4); - bool finished = progressTracker.IsFinished(out SnapSyncBatch? request); - request!.AccountRangeRequest.Should().NotBeNull(); - request.AccountRangeRequest!.StartingHash.Bytes[0].Should().Be(0); - request.AccountRangeRequest.LimitHash!.Value.Bytes[0].Should().Be(64); - finished.Should().BeFalse(); - request.Dispose(); - - finished = progressTracker.IsFinished(out request); - request!.AccountRangeRequest.Should().NotBeNull(); - request.AccountRangeRequest!.StartingHash.Bytes[0].Should().Be(64); - request.AccountRangeRequest.LimitHash!.Value.Bytes[0].Should().Be(128); - finished.Should().BeFalse(); - request.Dispose(); - - finished = progressTracker.IsFinished(out request); - request!.AccountRangeRequest.Should().NotBeNull(); - request.AccountRangeRequest!.StartingHash.Bytes[0].Should().Be(128); - request.AccountRangeRequest.LimitHash!.Value.Bytes[0].Should().Be(192); - finished.Should().BeFalse(); - request.Dispose(); - - finished = progressTracker.IsFinished(out request); - request!.AccountRangeRequest.Should().NotBeNull(); - request.AccountRangeRequest!.StartingHash.Bytes[0].Should().Be(192); - request.AccountRangeRequest.LimitHash!.Value.Bytes[0].Should().Be(255); - finished.Should().BeFalse(); - request.Dispose(); + Hash256[] expectedStarts = + [ + new("0x0000000000000000000000000000000000000000000000000000000000000000"), + new("0x4000000000000000000000000000000000000000000000000000000000000000"), + new("0x8000000000000000000000000000000000000000000000000000000000000000"), + new("0xc000000000000000000000000000000000000000000000000000000000000000"), + ]; + Hash256[] expectedLimits = + [ + new("0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + new("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + new("0xbfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + new("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + ]; + + for (int i = 0; i < 4; i++) + { + bool finished = progressTracker.IsFinished(out SnapSyncBatch? request); + request!.AccountRangeRequest.Should().NotBeNull(); + request.AccountRangeRequest!.StartingHash.Should().Be(expectedStarts[i]); + request.AccountRangeRequest.LimitHash!.Value.Should().Be(expectedLimits[i]); + finished.Should().BeFalse(); + request.Dispose(); + } - finished = progressTracker.IsFinished(out request); - request.Should().BeNull(); - finished.Should().BeFalse(); + bool finalFinished = progressTracker.IsFinished(out SnapSyncBatch? finalRequest); + finalRequest.Should().BeNull(); + finalFinished.Should().BeFalse(); } [Test] @@ -177,7 +175,7 @@ public void Should_partition_storage_request_if_last_processed_less_than_thresho StartingHash = new ValueHash256(start), LimitHash = limitHash }; - progressTracker.EnqueueNextSlot(storageRange, 0, lastProcessedHash); + progressTracker.EnqueueNextSlot(storageRange, 0, lastProcessedHash, 1_000_000_000); //ignore account range bool isFinished = progressTracker.IsFinished(out _); @@ -191,8 +189,8 @@ public void Should_partition_storage_request_if_last_processed_less_than_thresho isFinished.Should().BeFalse(); batch2.Should().NotBeNull(); - batch2?.StorageRangeRequest?.StartingHash.Should().Be(batch1?.StorageRangeRequest?.LimitHash); - batch1?.StorageRangeRequest?.StartingHash.Should().Be(lastProcessedHash); + batch2?.StorageRangeRequest?.StartingHash.Should().Be(batch1?.StorageRangeRequest?.LimitHash?.IncrementPath()); + batch1?.StorageRangeRequest?.StartingHash.Should().Be(lastProcessedHash.IncrementPath()); batch2?.StorageRangeRequest?.LimitHash.Should().Be(limitHash ?? Keccak.MaxValue); batch1?.StorageRangeRequest?.LimitHash.Should().Be(new ValueHash256(expectedSplit)); @@ -214,7 +212,7 @@ public void Should_not_partition_storage_request_if_last_processed_more_than_thr StartingHash = new ValueHash256(start), LimitHash = limitHash }; - progressTracker.EnqueueNextSlot(storageRange, 0, lastProcessedHash); + progressTracker.EnqueueNextSlot(storageRange, 0, lastProcessedHash, 100000000); //ignore account range bool isFinished = progressTracker.IsFinished(out _); @@ -224,7 +222,7 @@ public void Should_not_partition_storage_request_if_last_processed_more_than_thr isFinished.Should().BeFalse(); batch1.Should().NotBeNull(); - batch1?.StorageRangeRequest?.StartingHash.Should().Be(lastProcessedHash); + batch1?.StorageRangeRequest?.StartingHash.Should().Be(lastProcessedHash.IncrementPath()); batch1?.StorageRangeRequest?.LimitHash.Should().Be(limitHash ?? Keccak.MaxValue); } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/RecreateStateFromAccountRangesTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/RecreateStateFromAccountRangesTests.cs index b8fd9101af49..065bea5a11fd 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/RecreateStateFromAccountRangesTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/RecreateStateFromAccountRangesTests.cs @@ -25,6 +25,7 @@ namespace Nethermind.Synchronization.Test.SnapSync; +[TestFixture] public class RecreateStateFromAccountRangesTests { private StateTree _inputTree; @@ -35,6 +36,19 @@ public void Setup() _inputTree = TestItem.Tree.GetStateTree(); } + private ContainerBuilder CreateContainerBuilder() + { + ContainerBuilder builder = new ContainerBuilder() + .AddModule(new TestSynchronizerModule(new TestSyncConfig())) + .AddSingleton() + ; + + return builder; + } + + private IContainer CreateContainer() => + CreateContainerBuilder().Build(); + private byte[][] CreateProofForPath(ReadOnlySpan path, StateTree tree = null) { AccountProofCollector accountProofCollector = new(path); @@ -112,15 +126,15 @@ public void RecreateAccountStateFromOneRangeWithNonExistenceProof() byte[][] firstProof = CreateProofForPath(Keccak.Zero.Bytes); byte[][] lastProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[5].Path.Bytes); - using IContainer container = new ContainerBuilder().AddModule(new TestSynchronizerModule(new TestSyncConfig())).Build(); + using IContainer container = CreateContainer(); SnapProvider snapProvider = container.Resolve(); - IDb db = container.ResolveKeyed(DbNames.State); + ISnapTestHelper helper = container.Resolve(); AddRangeResult result = snapProvider.AddAccountRange(1, rootHash, Keccak.Zero, TestItem.Tree.AccountsWithPaths, firstProof!.Concat(lastProof!).ToArray()); Assert.That(result, Is.EqualTo(AddRangeResult.OK)); - Assert.That(db.GetAllKeys().Count, Is.EqualTo(10)); // we persist proof nodes (boundary nodes) via stitching - Assert.That(db.KeyExists(rootHash), Is.False); + Assert.That(helper.CountTrieNodes(), Is.EqualTo(10)); // we persist proof nodes (boundary nodes) via stitching + Assert.That(helper.TrieNodeKeyExists(rootHash), Is.False); } [Test] @@ -131,15 +145,15 @@ public void RecreateAccountStateFromOneRangeWithExistenceProof() byte[][] firstProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[0].Path.Bytes); byte[][] lastProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[5].Path.Bytes); - using IContainer container = new ContainerBuilder().AddModule(new TestSynchronizerModule(new TestSyncConfig())).Build(); + using IContainer container = CreateContainer(); SnapProvider snapProvider = container.Resolve(); - IDb db = container.ResolveKeyed(DbNames.State); + ISnapTestHelper helper = container.Resolve(); var result = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[0].Path, TestItem.Tree.AccountsWithPaths, firstProof!.Concat(lastProof!).ToArray()); Assert.That(result, Is.EqualTo(AddRangeResult.OK)); - Assert.That(db.GetAllKeys().Count, Is.EqualTo(10)); // we persist proof nodes (boundary nodes) via stitching - Assert.That(db.KeyExists(rootHash), Is.False); + Assert.That(helper.CountTrieNodes(), Is.EqualTo(10)); // we persist proof nodes (boundary nodes) via stitching + Assert.That(helper.TrieNodeKeyExists(rootHash), Is.False); } [Test] @@ -147,15 +161,15 @@ public void RecreateAccountStateFromOneRangeWithoutProof() { Hash256 rootHash = _inputTree.RootHash; // "0x8c81279168edc449089449bc0f2136fc72c9645642845755633cf259cd97988b" - using IContainer container = new ContainerBuilder().AddModule(new TestSynchronizerModule(new TestSyncConfig())).Build(); + using IContainer container = CreateContainer(); SnapProvider snapProvider = container.Resolve(); - IDb db = container.ResolveKeyed(DbNames.State); + ISnapTestHelper helper = container.Resolve(); var result = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[0].Path, TestItem.Tree.AccountsWithPaths); Assert.That(result, Is.EqualTo(AddRangeResult.OK)); - Assert.That(db.GetAllKeys().Count, Is.EqualTo(10)); // we don't have the proofs so we persist all nodes - Assert.That(db.KeyExists(rootHash), Is.False); // the root node is NOT a part of the proof nodes + Assert.That(helper.CountTrieNodes(), Is.EqualTo(10)); // we don't have the proofs so we persist all nodes + Assert.That(helper.TrieNodeKeyExists(rootHash), Is.False); // the root node is NOT a part of the proof nodes } [Test] @@ -164,23 +178,23 @@ public void RecreateAccountStateFromMultipleRange() Hash256 rootHash = _inputTree.RootHash; // "0x8c81279168edc449089449bc0f2136fc72c9645642845755633cf259cd97988b" // output state - using IContainer container = new ContainerBuilder().AddModule(new TestSynchronizerModule(new TestSyncConfig())).Build(); + using IContainer container = CreateContainer(); SnapProvider snapProvider = container.Resolve(); - IDb db = container.ResolveKeyed(DbNames.State); + ISnapTestHelper helper = container.Resolve(); byte[][] firstProof = CreateProofForPath(Keccak.Zero.Bytes); byte[][] lastProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[1].Path.Bytes); var result1 = snapProvider.AddAccountRange(1, rootHash, Keccak.Zero, TestItem.Tree.AccountsWithPaths[0..2], firstProof!.Concat(lastProof!).ToArray()); - Assert.That(db.GetAllKeys().Count, Is.EqualTo(2)); + Assert.That(helper.CountTrieNodes(), Is.EqualTo(2)); firstProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[2].Path.Bytes); lastProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[3].Path.Bytes); var result2 = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[2].Path, TestItem.Tree.AccountsWithPaths[2..4], firstProof!.Concat(lastProof!).ToArray()); - Assert.That(db.GetAllKeys().Count, Is.EqualTo(5)); // we don't persist proof nodes (boundary nodes) + Assert.That(helper.CountTrieNodes(), Is.EqualTo(4)); // we don't persist proof nodes (boundary nodes) firstProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[4].Path.Bytes); lastProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[5].Path.Bytes); @@ -190,8 +204,8 @@ public void RecreateAccountStateFromMultipleRange() Assert.That(result1, Is.EqualTo(AddRangeResult.OK)); Assert.That(result2, Is.EqualTo(AddRangeResult.OK)); Assert.That(result3, Is.EqualTo(AddRangeResult.OK)); - Assert.That(db.GetAllKeys().Count, Is.EqualTo(10)); // we persist proof nodes (boundary nodes) via stitching - Assert.That(db.KeyExists(rootHash), Is.False); + Assert.That(helper.CountTrieNodes(), Is.EqualTo(8)); // we persist proof nodes (boundary nodes) via stitching + Assert.That(helper.TrieNodeKeyExists(rootHash), Is.False); } [Test] @@ -200,21 +214,21 @@ public void RecreateAccountStateFromMultipleRange_InReverseOrder() Hash256 rootHash = _inputTree.RootHash; // "0x8c81279168edc449089449bc0f2136fc72c9645642845755633cf259cd97988b" // output state - using IContainer container = new ContainerBuilder().AddModule(new TestSynchronizerModule(new TestSyncConfig())).Build(); + using IContainer container = CreateContainer(); SnapProvider snapProvider = container.Resolve(); - IDb db = container.ResolveKeyed(DbNames.State); + ISnapTestHelper helper = container.Resolve(); byte[][] firstProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[4].Path.Bytes); byte[][] lastProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[5].Path.Bytes); var result3 = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[4].Path, TestItem.Tree.AccountsWithPaths[4..6], firstProof!.Concat(lastProof!).ToArray()); - Assert.That(db.GetAllKeys().Count, Is.EqualTo(4)); + Assert.That(helper.CountTrieNodes(), Is.EqualTo(4)); firstProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[2].Path.Bytes); lastProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[3].Path.Bytes); var result2 = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[2].Path, TestItem.Tree.AccountsWithPaths[2..4], firstProof!.Concat(lastProof!).ToArray()); - Assert.That(db.GetAllKeys().Count, Is.EqualTo(6)); // we don't persist proof nodes (boundary nodes) + Assert.That(helper.CountTrieNodes(), Is.EqualTo(6)); // we don't persist proof nodes (boundary nodes) firstProof = CreateProofForPath(Keccak.Zero.Bytes); lastProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[1].Path.Bytes); @@ -223,8 +237,8 @@ public void RecreateAccountStateFromMultipleRange_InReverseOrder() Assert.That(result1, Is.EqualTo(AddRangeResult.OK)); Assert.That(result2, Is.EqualTo(AddRangeResult.OK)); Assert.That(result3, Is.EqualTo(AddRangeResult.OK)); - Assert.That(db.GetAllKeys().Count, Is.EqualTo(10)); // we persist proof nodes (boundary nodes) via stitching - Assert.That(db.KeyExists(rootHash), Is.False); + Assert.That(helper.CountTrieNodes(), Is.EqualTo(8)); // we persist proof nodes (boundary nodes) via stitching + Assert.That(helper.TrieNodeKeyExists(rootHash), Is.False); } [Test] @@ -233,21 +247,21 @@ public void RecreateAccountStateFromMultipleRange_OutOfOrder() Hash256 rootHash = _inputTree.RootHash; // "0x8c81279168edc449089449bc0f2136fc72c9645642845755633cf259cd97988b" // output state - using IContainer container = new ContainerBuilder().AddModule(new TestSynchronizerModule(new TestSyncConfig())).Build(); + using IContainer container = CreateContainer(); SnapProvider snapProvider = container.Resolve(); - IDb db = container.ResolveKeyed(DbNames.State); + ISnapTestHelper helper = container.Resolve(); byte[][] firstProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[4].Path.Bytes); byte[][] lastProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[5].Path.Bytes); var result3 = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[4].Path, TestItem.Tree.AccountsWithPaths[4..6], firstProof!.Concat(lastProof!).ToArray()); - Assert.That(db.GetAllKeys().Count, Is.EqualTo(4)); + Assert.That(helper.CountTrieNodes(), Is.EqualTo(4)); firstProof = CreateProofForPath(Keccak.Zero.Bytes); lastProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[1].Path.Bytes); var result1 = snapProvider.AddAccountRange(1, rootHash, Keccak.Zero, TestItem.Tree.AccountsWithPaths[0..2], firstProof!.Concat(lastProof!).ToArray()); - Assert.That(db.GetAllKeys().Count, Is.EqualTo(6)); // we don't persist proof nodes (boundary nodes) + Assert.That(helper.CountTrieNodes(), Is.EqualTo(6)); // we don't persist proof nodes (boundary nodes) firstProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[2].Path.Bytes); lastProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[3].Path.Bytes); @@ -256,8 +270,8 @@ public void RecreateAccountStateFromMultipleRange_OutOfOrder() Assert.That(result1, Is.EqualTo(AddRangeResult.OK)); Assert.That(result2, Is.EqualTo(AddRangeResult.OK)); Assert.That(result3, Is.EqualTo(AddRangeResult.OK)); - Assert.That(db.GetAllKeys().Count, Is.EqualTo(10)); // we persist proof nodes (boundary nodes) via stitching - Assert.That(db.KeyExists(rootHash), Is.False); + Assert.That(helper.CountTrieNodes(), Is.EqualTo(8)); // we persist proof nodes (boundary nodes) via stitching + Assert.That(helper.TrieNodeKeyExists(rootHash), Is.False); } [Test] @@ -266,16 +280,16 @@ public void RecreateAccountStateFromMultipleOverlappingRange() Hash256 rootHash = _inputTree.RootHash; // "0x8c81279168edc449089449bc0f2136fc72c9645642845755633cf259cd97988b" // output state - using IContainer container = new ContainerBuilder().AddModule(new TestSynchronizerModule(new TestSyncConfig())).Build(); + using IContainer container = CreateContainer(); SnapProvider snapProvider = container.Resolve(); - IDb db = container.ResolveKeyed(DbNames.State); + ISnapTestHelper helper = container.Resolve(); byte[][] firstProof = CreateProofForPath(Keccak.Zero.Bytes); byte[][] lastProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[2].Path.Bytes); var result1 = snapProvider.AddAccountRange(1, rootHash, Keccak.Zero, TestItem.Tree.AccountsWithPaths[0..3], firstProof!.Concat(lastProof!).ToArray()); - Assert.That(db.GetAllKeys().Count, Is.EqualTo(3)); + Assert.That(helper.CountTrieNodes(), Is.EqualTo(3)); firstProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[2].Path.Bytes); lastProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[3].Path.Bytes); @@ -287,7 +301,7 @@ public void RecreateAccountStateFromMultipleOverlappingRange() var result3 = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[3].Path, TestItem.Tree.AccountsWithPaths[3..5], firstProof!.Concat(lastProof!).ToArray()); - Assert.That(db.GetAllKeys().Count, Is.EqualTo(6)); // we don't persist proof nodes (boundary nodes) + Assert.That(helper.CountTrieNodes(), Is.EqualTo(6)); // we don't persist proof nodes (boundary nodes) firstProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[4].Path.Bytes); lastProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[5].Path.Bytes); @@ -298,8 +312,8 @@ public void RecreateAccountStateFromMultipleOverlappingRange() Assert.That(result2, Is.EqualTo(AddRangeResult.OK)); Assert.That(result3, Is.EqualTo(AddRangeResult.OK)); Assert.That(result4, Is.EqualTo(AddRangeResult.OK)); - Assert.That(db.GetAllKeys().Count, Is.EqualTo(10)); // we persist proof nodes (boundary nodes) via stitching - Assert.That(db.KeyExists(rootHash), Is.False); + Assert.That(helper.CountTrieNodes(), Is.EqualTo(10)); // we persist proof nodes (boundary nodes) via stitching + Assert.That(helper.TrieNodeKeyExists(rootHash), Is.False); } [Test] @@ -312,14 +326,15 @@ public void CorrectlyDetermineHasMoreChildren() byte[][] lastProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[1].Path.Bytes); byte[][] proofs = firstProof.Concat(lastProof).ToArray(); - StateTree newTree = new(new TestRawTrieStore(new MemDb()), LimboLogs.Instance); + using IContainer container = CreateContainer(); + ISnapTrieFactory factory = container.Resolve(); PathWithAccount[] receiptAccounts = TestItem.Tree.AccountsWithPaths[0..2]; bool HasMoreChildren(ValueHash256 limitHash) { - (AddRangeResult _, bool moreChildrenToRight, IList _, IList _) = - SnapProviderHelper.AddAccountRange(newTree, 0, rootHash, Keccak.Zero, limitHash.ToCommitment(), receiptAccounts, proofs); + (AddRangeResult _, bool moreChildrenToRight, IList _, IList _, Hash256 _) = + SnapProviderHelper.AddAccountRange(factory, 0, rootHash, Keccak.Zero, limitHash.ToCommitment(), receiptAccounts, proofs); return moreChildrenToRight; } @@ -360,14 +375,15 @@ public void CorrectlyDetermineMaxKeccakExist() byte[][] lastProof = CreateProofForPath(ac2.Path.Bytes, tree); byte[][] proofs = firstProof.Concat(lastProof).ToArray(); - StateTree newTree = new(new TestRawTrieStore(new MemDb()), LimboLogs.Instance); + using IContainer container = CreateContainer(); + ISnapTrieFactory factory = container.Resolve(); PathWithAccount[] receiptAccounts = { ac1, ac2 }; bool HasMoreChildren(ValueHash256 limitHash) { - (AddRangeResult _, bool moreChildrenToRight, IList _, IList _) = - SnapProviderHelper.AddAccountRange(newTree, 0, rootHash, Keccak.Zero, limitHash.ToCommitment(), receiptAccounts, proofs); + (AddRangeResult _, bool moreChildrenToRight, IList _, IList _, Hash256 _) = + SnapProviderHelper.AddAccountRange(factory, 0, rootHash, Keccak.Zero, limitHash.ToCommitment(), receiptAccounts, proofs); return moreChildrenToRight; } @@ -389,16 +405,16 @@ public void MissingAccountFromRange() Hash256 rootHash = _inputTree.RootHash; // "0x8c81279168edc449089449bc0f2136fc72c9645642845755633cf259cd97988b" // output state - using IContainer container = new ContainerBuilder().AddModule(new TestSynchronizerModule(new TestSyncConfig())).Build(); + using IContainer container = CreateContainer(); SnapProvider snapProvider = container.Resolve(); - IDb db = container.ResolveKeyed(DbNames.State); + ISnapTestHelper helper = container.Resolve(); byte[][] firstProof = CreateProofForPath(Keccak.Zero.Bytes); byte[][] lastProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[1].Path.Bytes); var result1 = snapProvider.AddAccountRange(1, rootHash, Keccak.Zero, TestItem.Tree.AccountsWithPaths[0..2], firstProof!.Concat(lastProof!).ToArray()); - Assert.That(db.GetAllKeys().Count, Is.EqualTo(2)); + Assert.That(helper.CountTrieNodes(), Is.EqualTo(2)); firstProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[2].Path.Bytes); lastProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[3].Path.Bytes); @@ -406,7 +422,7 @@ public void MissingAccountFromRange() // missing TestItem.Tree.AccountsWithHashes[2] var result2 = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[2].Path, TestItem.Tree.AccountsWithPaths[3..4], firstProof!.Concat(lastProof!).ToArray()); - Assert.That(db.GetAllKeys().Count, Is.EqualTo(2)); + Assert.That(helper.CountTrieNodes(), Is.EqualTo(2)); firstProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[4].Path.Bytes); lastProof = CreateProofForPath(TestItem.Tree.AccountsWithPaths[5].Path.Bytes); @@ -416,7 +432,7 @@ public void MissingAccountFromRange() Assert.That(result1, Is.EqualTo(AddRangeResult.OK)); Assert.That(result2, Is.EqualTo(AddRangeResult.DifferentRootHash)); Assert.That(result3, Is.EqualTo(AddRangeResult.OK)); - Assert.That(db.GetAllKeys().Count, Is.EqualTo(6)); - Assert.That(db.KeyExists(rootHash), Is.False); + Assert.That(helper.CountTrieNodes(), Is.EqualTo(6)); + Assert.That(helper.TrieNodeKeyExists(rootHash), Is.False); } } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/RecreateStateFromStorageRangesTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/RecreateStateFromStorageRangesTests.cs index c35e93d66f4b..955e3c3bc590 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/RecreateStateFromStorageRangesTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/RecreateStateFromStorageRangesTests.cs @@ -15,13 +15,11 @@ using Nethermind.Core.Test.Builders; using Nethermind.Db; using Nethermind.Int256; -using Nethermind.Logging; using Nethermind.State; using Nethermind.State.Proofs; using Nethermind.State.Snap; using Nethermind.Synchronization.SnapSync; using Nethermind.Trie; -using Nethermind.Trie.Pruning; using NUnit.Framework; namespace Nethermind.Synchronization.Test.SnapSync @@ -29,7 +27,6 @@ namespace Nethermind.Synchronization.Test.SnapSync [TestFixture] public class RecreateStateFromStorageRangesTests { - private TestRawTrieStore _store; private StateTree _inputStateTree; private StorageTree _inputStorageTree; @@ -45,6 +42,20 @@ public void Setup() [OneTimeTearDown] public void TearDown() => ((IDisposable)_store)?.Dispose(); + private ContainerBuilder CreateContainerBuilder() + { + ContainerBuilder builder = new ContainerBuilder() + .AddModule(new TestSynchronizerModule(new TestSyncConfig())) + .AddKeyedSingleton(DbNames.State, (_) => (IDb)new TestMemDb()) + .AddSingleton() + ; + + return builder; + } + + private IContainer CreateContainer() => + CreateContainerBuilder().Build(); + [Test] public void RecreateStorageStateFromOneRangeWithNonExistenceProof() { @@ -54,7 +65,7 @@ public void RecreateStorageStateFromOneRangeWithNonExistenceProof() _inputStateTree!.Accept(accountProofCollector, _inputStateTree.RootHash); var proof = accountProofCollector.BuildResult(); - using IContainer container = new ContainerBuilder().AddModule(new TestSynchronizerModule(new TestSyncConfig())).Build(); + using IContainer container = CreateContainer(); SnapProvider snapProvider = container.Resolve(); var storageRange = PrepareStorageRequest(TestItem.Tree.AccountAddress0, rootHash, Keccak.Zero); @@ -72,7 +83,7 @@ public void RecreateAccountStateFromOneRangeWithExistenceProof() _inputStateTree!.Accept(accountProofCollector, _inputStateTree.RootHash); var proof = accountProofCollector.BuildResult(); - using IContainer container = new ContainerBuilder().AddModule(new TestSynchronizerModule(new TestSyncConfig())).Build(); + using IContainer container = CreateContainer(); SnapProvider snapProvider = container.Resolve(); var storageRange = PrepareStorageRequest(TestItem.Tree.AccountAddress0, rootHash, Keccak.Zero); @@ -86,7 +97,7 @@ public void RecreateStorageStateFromOneRangeWithoutProof() { Hash256 rootHash = _inputStorageTree!.RootHash; // "..." - using IContainer container = new ContainerBuilder().AddModule(new TestSynchronizerModule(new TestSyncConfig())).Build(); + using IContainer container = CreateContainer(); SnapProvider snapProvider = container.Resolve(); var storageRange = PrepareStorageRequest(TestItem.Tree.AccountAddress0, rootHash, TestItem.Tree.SlotsWithPaths[0].Path); @@ -101,7 +112,7 @@ public void RecreateAccountStateFromMultipleRange() Hash256 rootHash = _inputStorageTree!.RootHash; // "..." // output state - using IContainer container = new ContainerBuilder().AddModule(new TestSynchronizerModule(new TestSyncConfig())).Build(); + using IContainer container = CreateContainer(); SnapProvider snapProvider = container.Resolve(); AccountProofCollector accountProofCollector = new(TestItem.Tree.AccountAddress0.Bytes, new ValueHash256[] { Keccak.Zero, TestItem.Tree.SlotsWithPaths[1].Path }); @@ -136,7 +147,7 @@ public void MissingAccountFromRange() Hash256 rootHash = _inputStorageTree!.RootHash; // "..." // output state - using IContainer container = new ContainerBuilder().AddModule(new TestSynchronizerModule(new TestSyncConfig())).Build(); + using IContainer container = CreateContainer(); SnapProvider snapProvider = container.Resolve(); AccountProofCollector accountProofCollector = new(TestItem.Tree.AccountAddress0.Bytes, new ValueHash256[] { Keccak.Zero, TestItem.Tree.SlotsWithPaths[1].Path }); @@ -169,13 +180,15 @@ public void MissingAccountFromRange() public void AddStorageRange_WhereProofIsTheSameAsAllKey_ShouldStillStore() { Hash256 account = TestItem.KeccakA; - TestMemDb testMemDb = new(); - var rawTrieStore = new RawScopedTrieStore(new NodeStorage(testMemDb), account); - StorageTree tree = new(rawTrieStore, LimboLogs.Instance); - - (AddRangeResult result, bool moreChildrenToRight) = SnapProviderHelper.AddStorageRange( - tree, - new PathWithAccount(account, new Account(1, 1, new Hash256("0xeb8594ba5b3314111518b584bbd3801fb3aed5970bd8b47fd9ff744505fe101c"), TestItem.KeccakA)), + using IContainer container = CreateContainerBuilder() + .Build(); + ISnapTestHelper helper = container.Resolve(); + ISnapTrieFactory factory = container.Resolve(); + + var pathWithAccount = new PathWithAccount(account, new Account(1, 1, new Hash256("0xeb8594ba5b3314111518b584bbd3801fb3aed5970bd8b47fd9ff744505fe101c"), TestItem.KeccakA)); + (AddRangeResult result, bool moreChildrenToRight, Hash256 _, bool rootFinished) = SnapProviderHelper.AddStorageRange( + factory, + pathWithAccount, [ new PathWithStorageSlot(new ValueHash256("0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"), Bytes.FromHexString("94654f75e491acf8c380d2a6906e67e2e56813665e")), ], @@ -188,28 +201,30 @@ public void AddStorageRange_WhereProofIsTheSameAsAllKey_ShouldStillStore() result.Should().Be(AddRangeResult.OK); moreChildrenToRight.Should().BeFalse(); - testMemDb.WritesCount.Should().Be(1); + helper.TrieNodeWritesCount.Should().Be(1); } [Test] public void AddStorageRange_EmptySlots_ReturnsEmptySlots() { Hash256 account = TestItem.KeccakA; - TestMemDb testMemDb = new(); - var rawTrieStore = new RawScopedTrieStore(new NodeStorage(testMemDb), account); - StorageTree tree = new(rawTrieStore, LimboLogs.Instance); + using IContainer container = CreateContainerBuilder() + .Build(); - (AddRangeResult result, bool moreChildrenToRight) = SnapProviderHelper.AddStorageRange( - tree, - new PathWithAccount(account, new Account(1, 1, new Hash256("0xeb8594ba5b3314111518b584bbd3801fb3aed5970bd8b47fd9ff744505fe101c"), TestItem.KeccakA)), + ISnapTestHelper helper = container.Resolve(); + ISnapTrieFactory factory = container.Resolve(); + + var pathWithAccount = new PathWithAccount(account, new Account(1, 1, new Hash256("0xeb8594ba5b3314111518b584bbd3801fb3aed5970bd8b47fd9ff744505fe101c"), TestItem.KeccakA)); + (AddRangeResult result, bool moreChildrenToRight, Hash256 _, bool rootFinished) = SnapProviderHelper.AddStorageRange( + factory, + pathWithAccount, Array.Empty(), // Empty slots list Keccak.Zero, null, proofs: null); - result.Should().Be(AddRangeResult.EmptySlots); - moreChildrenToRight.Should().BeFalse(); - testMemDb.WritesCount.Should().Be(0); // No writes should happen + result.Should().Be(AddRangeResult.EmptyRange); + helper.TrieNodeWritesCount.Should().Be(0); // No writes should happen } private static StorageRange PrepareStorageRequest(ValueHash256 accountPath, Hash256 storageRoot, ValueHash256 startingHash) diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/SnapProviderTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/SnapProviderTests.cs index f9c245f47ada..6ecd89fe74df 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/SnapProviderTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/SnapProviderTests.cs @@ -7,7 +7,6 @@ using Nethermind.Logging; using Nethermind.State.Snap; using Nethermind.Synchronization.SnapSync; -using NSubstitute; using NUnit.Framework; using System; using System.Collections.Generic; @@ -22,8 +21,10 @@ using Nethermind.Db; using Nethermind.Serialization.Rlp; using Nethermind.State; +using Nethermind.State.Proofs; using Nethermind.State.SnapServer; using Nethermind.Trie.Pruning; +using Nethermind.Trie; using AccountRange = Nethermind.State.Snap.AccountRange; namespace Nethermind.Synchronization.Test.SnapSync; @@ -31,12 +32,26 @@ namespace Nethermind.Synchronization.Test.SnapSync; [TestFixture] public class SnapProviderTests { + + private ContainerBuilder CreateContainerBuilder(TestSyncConfig? testSyncConfig = null) + { + TestSyncConfig testConfig = testSyncConfig ?? new TestSyncConfig(); + + ContainerBuilder builder = new ContainerBuilder() + .AddModule(new TestSynchronizerModule(testConfig)); + + return builder; + } + + private IContainer CreateContainer(TestSyncConfig? testSyncConfig = null) + { + return CreateContainerBuilder(testSyncConfig).Build(); + } + [Test] public void AddAccountRange_AccountListIsEmpty_ThrowArgumentException() { - using IContainer container = new ContainerBuilder() - .AddModule(new TestSynchronizerModule(new TestSyncConfig())) - .Build(); + using IContainer container = CreateContainer(); SnapProvider snapProvider = container.Resolve(); @@ -52,9 +67,7 @@ public void AddAccountRange_AccountListIsEmpty_ThrowArgumentException() [Test] public void AddAccountRange_ResponseHasEmptyListOfAccountsAndOneProof_ReturnsExpiredRootHash() { - using IContainer container = new ContainerBuilder() - .AddModule(new TestSynchronizerModule(new TestSyncConfig())) - .Build(); + using IContainer container = CreateContainer(); SnapProvider snapProvider = container.Resolve(); @@ -69,9 +82,7 @@ public void AddAccountRange_ResponseHasEmptyListOfAccountsAndOneProof_ReturnsExp [Test] public void AddStorageRange_ResponseReversedOrderedListOfAccounts_ReturnsInvalidOrder() { - using IContainer container = new ContainerBuilder() - .AddModule(new TestSynchronizerModule(new TestSyncConfig())) - .Build(); + using IContainer container = CreateContainer(); SnapProvider snapProvider = container.Resolve(); ProgressTracker progressTracker = container.Resolve(); @@ -102,9 +113,7 @@ public void AddStorageRange_ResponseReversedOrderedListOfAccounts_ReturnsInvalid [Test] public void AddStorageRange_EmptySlotsList_ReturnsEmptySlots() { - using IContainer container = new ContainerBuilder() - .AddModule(new TestSynchronizerModule(new TestSyncConfig())) - .Build(); + using IContainer container = CreateContainer(); SnapProvider snapProvider = container.Resolve(); ProgressTracker progressTracker = container.Resolve(); @@ -121,11 +130,61 @@ public void AddStorageRange_EmptySlotsList_ReturnsEmptySlots() storage, 0, emptySlots, - null).Should().Be(AddRangeResult.EmptySlots); + null).Should().Be(AddRangeResult.EmptyRange); progressTracker.IsSnapGetRangesFinished().Should().BeFalse(); } + [Test] + public void AddStorageRange_ShouldPersistEntries() + { + const int slotCount = 6; + TestMemDb stateDb = new TestMemDb(); + TestRawTrieStore store = new TestRawTrieStore(stateDb); + + // Build storage tree with RLP-encoded 32-byte values + Hash256 accountHash = TestItem.Tree.AccountAddress0; + StorageTree storageTree = new StorageTree(store.GetTrieStore(accountHash), LimboLogs.Instance); + PathWithStorageSlot[] slots = new PathWithStorageSlot[slotCount]; + for (int i = 0; i < slotCount; i++) + { + ValueHash256 slotKey = Keccak.Compute(i.ToBigEndianByteArray()); + byte[] value = (i + 1).ToBigEndianByteArray(); + byte[] rlpValue = Rlp.Encode(value).Bytes; + storageTree.Set(slotKey, rlpValue, false); + slots[i] = new PathWithStorageSlot(slotKey, rlpValue); + } + storageTree.Commit(); + Array.Sort(slots, (a, b) => a.Path.CompareTo(b.Path)); + + StateTree stateTree = new StateTree(store.GetTrieStore(null), LimboLogs.Instance); + stateTree.Set(accountHash, Build.An.Account.WithBalance(1).WithStorageRoot(storageTree.RootHash).TestObject); + stateTree.Commit(); + + // Collect proofs + AccountProofCollector proofCollector = new(accountHash.Bytes, + new ValueHash256[] { Keccak.Zero, slots[^1].Path }); + stateTree.Accept(proofCollector, stateTree.RootHash); + var proof = proofCollector.BuildResult(); + + using IContainer container = CreateContainer(); + SnapProvider snapProvider = container.Resolve(); + + StorageRange storageRange = new() + { + StartingHash = Keccak.Zero, + Accounts = new ArrayPoolList(1) + { + new(accountHash, new Account(0, 1).WithChangedStorageRoot(storageTree.RootHash)) + }, + }; + + snapProvider.AddStorageRangeForAccount( + storageRange, 0, slots, + proof!.StorageProofs![0].Proof!.Concat(proof!.StorageProofs![1].Proof!).ToArray()) + .Should().Be(AddRangeResult.OK); + } + [Test] public void AddAccountRange_SetStartRange_ToAfterLastPath() { @@ -142,11 +201,10 @@ public void AddAccountRange_SetStartRange_ToAfterLastPath() (SnapServer ss, Hash256 root) = BuildSnapServerFromEntries(entries); - using IContainer container = new ContainerBuilder() - .AddModule(new TestSynchronizerModule(new TestSyncConfig() - { - SnapSyncAccountRangePartitionCount = 1 - })) + using IContainer container = CreateContainerBuilder(new TestSyncConfig() + { + SnapSyncAccountRangePartitionCount = 1 + }) .WithSuggestedHeaderOfStateRoot(root) .Build(); @@ -173,22 +231,23 @@ public void AddAccountRange_ShouldNotStoreStorageAfterLimit() { (Hash256, Account)[] entries = [ - (TestItem.KeccakA, TestItem.GenerateRandomAccount().WithChangedStorageRoot(TestItem.GetRandomKeccak())), - (TestItem.KeccakB, TestItem.GenerateRandomAccount().WithChangedStorageRoot(TestItem.GetRandomKeccak())), - (TestItem.KeccakC, TestItem.GenerateRandomAccount().WithChangedStorageRoot(TestItem.GetRandomKeccak())), - (TestItem.KeccakD, TestItem.GenerateRandomAccount().WithChangedStorageRoot(TestItem.GetRandomKeccak())), - (TestItem.KeccakE, TestItem.GenerateRandomAccount().WithChangedStorageRoot(TestItem.GetRandomKeccak())), - (TestItem.KeccakF, TestItem.GenerateRandomAccount().WithChangedStorageRoot(TestItem.GetRandomKeccak())), + (new Hash256("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), TestItem.GenerateRandomAccount().WithChangedStorageRoot(TestItem.GetRandomKeccak())), + (new Hash256("2fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), TestItem.GenerateRandomAccount().WithChangedStorageRoot(TestItem.GetRandomKeccak())), + (new Hash256("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), TestItem.GenerateRandomAccount().WithChangedStorageRoot(TestItem.GetRandomKeccak())), + // Should split it right here + + (new Hash256("9fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), TestItem.GenerateRandomAccount().WithChangedStorageRoot(TestItem.GetRandomKeccak())), + (new Hash256("afffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), TestItem.GenerateRandomAccount().WithChangedStorageRoot(TestItem.GetRandomKeccak())), + (new Hash256("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), TestItem.GenerateRandomAccount().WithChangedStorageRoot(TestItem.GetRandomKeccak())), ]; Array.Sort(entries, static (e1, e2) => e1.Item1.CompareTo(e2.Item1)); (SnapServer ss, Hash256 root) = BuildSnapServerFromEntries(entries); - using IContainer container = new ContainerBuilder() - .AddModule(new TestSynchronizerModule(new TestSyncConfig() - { - SnapSyncAccountRangePartitionCount = 2 - })) + using IContainer container = CreateContainerBuilder(new TestSyncConfig() + { + SnapSyncAccountRangePartitionCount = 2 + }) .WithSuggestedHeaderOfStateRoot(root) .Build(); @@ -198,6 +257,7 @@ public void AddAccountRange_ShouldNotStoreStorageAfterLimit() (IOwnedReadOnlyList accounts, IOwnedReadOnlyList proofs) = ss.GetAccountRanges( root, Keccak.Zero, Keccak.MaxValue, 1.MB(), default); + // The range given out here should be half. progressTracker.IsFinished(out SnapSyncBatch? batch).Should().Be(false); using AccountsAndProofs accountsAndProofs = new(); @@ -206,7 +266,7 @@ public void AddAccountRange_ShouldNotStoreStorageAfterLimit() snapProvider.AddAccountRange(batch?.AccountRangeRequest!, accountsAndProofs).Should().Be(AddRangeResult.OK); - container.ResolveNamed(DbNames.State).GetAllKeys().Count().Should().Be(6); + container.ResolveNamed(DbNames.State).GetAllKeys().Count().Should().Be(3); // 3 child. Root branch node not saved due to state sync compatibility } [TestCase("badreq-roothash.zip")] @@ -227,9 +287,13 @@ public void Test_EdgeCases(string testFileName) List pathWithAccounts = accounts.Select((acc, idx) => new PathWithAccount(paths[idx], acc)).ToList(); List proofs = asReq.Proofs.Select((str) => Bytes.FromHexString(str)).ToList(); - StateTree stree = new StateTree(new TestRawTrieStore(new TestMemDb()), LimboLogs.Instance); + TestMemDb db = new TestMemDb(); + NodeStorage nodeStorage = new NodeStorage(db); + var adapter = new SnapUpperBoundAdapter(new RawScopedTrieStore(nodeStorage)); + StateTree stree = new StateTree(adapter, LimboLogs.Instance); + var factory = new TestSnapTrieFactory(() => new PatriciaSnapStateTree(stree, adapter, nodeStorage)); SnapProviderHelper.AddAccountRange( - stree, + factory, 0, new ValueHash256(asReq.Root), new ValueHash256(asReq.StartingHash), @@ -261,9 +325,7 @@ private static (SnapServer, Hash256) BuildSnapServerFromEntries((Hash256, Accoun st.Commit(); } - IStateReader stateRootTracker = Substitute.For(); - stateRootTracker.HasStateForBlock(Build.A.BlockHeader.WithStateRoot(st.RootHash).TestObject).Returns(true); - var ss = new SnapServer(trieStore.AsReadOnly(), new TestMemDb(), stateRootTracker, LimboLogs.Instance); + var ss = new SnapServer(trieStore.AsReadOnly(), new TestMemDb(), LimboLogs.Instance); return (ss, st.RootHash); } } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/SnapServerTest.cs b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/SnapServerTest.cs index 2c17a1316ea7..9cc5217c68f1 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/SnapServerTest.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/SnapServerTest.cs @@ -6,11 +6,12 @@ using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; +using Nethermind.Core.Threading; using Nethermind.Db; using Nethermind.Int256; -using Nethermind.Libp2p.Core.Enums; using Nethermind.Logging; using Nethermind.State; using Nethermind.State.Snap; @@ -24,54 +25,155 @@ namespace Nethermind.Synchronization.Test.SnapSync; +[TestFixture] public class SnapServerTest { - private class Context + internal interface IWriteBatch : IDisposable { - internal SnapServer Server { get; init; } = null!; - internal SnapProvider SnapProvider { get; init; } = null!; - internal StateTree Tree { get; init; } = null!; - internal MemDb ClientStateDb { get; init; } = null!; + void SetAccount(Address address, Account account); + void SetAccount(Hash256 accountPath, Account account); + void SetSlot(Hash256 storagePath, ValueHash256 slotKey, byte[] value, bool rlpEncode = true); } - private Context CreateContext(IStateReader? stateRootTracker = null, ILastNStateRootTracker? lastNStateRootTracker = null) + internal interface ISnapServerContext : IDisposable { - MemDb stateDbServer = new(); - MemDb codeDbServer = new(); - TestRawTrieStore store = new TestRawTrieStore(stateDbServer); - StateTree tree = new(store, LimboLogs.Instance); - SnapServer server = new(store.AsReadOnly(), codeDbServer, stateRootTracker ?? CreateConstantStateRootTracker(true), LimboLogs.Instance, lastNStateRootTracker); + ISnapServer Server { get; } + SnapProvider SnapProvider { get; } + Hash256 RootHash { get; } + int PersistedNodeCount { get; } - MemDb clientStateDb = new(); - using ProgressTracker progressTracker = new(clientStateDb, new TestSyncConfig(), new StateSyncPivot(null!, new TestSyncConfig(), LimboLogs.Instance), LimboLogs.Instance); + IWriteBatch BeginWriteBatch(); + Hash256 GetStorageRoot(Hash256 storagePath); + } + + private class TrieSnapServerContext : ISnapServerContext + { + private readonly TestRawTrieStore _store; + private readonly StateTree _tree; + private readonly MemDb _clientStateDb; + + public ISnapServer Server { get; } + public SnapProvider SnapProvider { get; } + public Hash256 RootHash => _tree.RootHash; + public int PersistedNodeCount => _clientStateDb.Keys.Count; - INodeStorage nodeStorage = new NodeStorage(clientStateDb); + internal TrieSnapServerContext(ILastNStateRootTracker? lastNStateRootTracker = null) + { + MemDb stateDbServer = new(); + MemDb codeDbServer = new(); + _store = new TestRawTrieStore(stateDbServer); + _tree = new StateTree(_store, LimboLogs.Instance); + Server = new SnapServer(_store.AsReadOnly(), codeDbServer, LimboLogs.Instance, lastNStateRootTracker); + + _clientStateDb = new MemDb(); + using ProgressTracker progressTracker = new(_clientStateDb, new TestSyncConfig(), new StateSyncPivot(null!, new TestSyncConfig(), LimboLogs.Instance), LimboLogs.Instance); + INodeStorage nodeStorage = new NodeStorage(_clientStateDb); + SnapProvider = new SnapProvider(progressTracker, new MemDb(), new PatriciaSnapTrieFactory(nodeStorage, LimboLogs.Instance), LimboLogs.Instance); + } - SnapProvider snapProvider = new(progressTracker, new MemDb(), nodeStorage, LimboLogs.Instance); + public IWriteBatch BeginWriteBatch() => new WriteBatch(this); + public Hash256 GetStorageRoot(Hash256 accountPath) => _tree.Get(accountPath)!.StorageRoot; + public void Dispose() => ((IDisposable)_store).Dispose(); - return new Context + private class WriteBatch(TrieSnapServerContext ctx) : IWriteBatch { - Server = server, - SnapProvider = snapProvider, - Tree = tree, - ClientStateDb = clientStateDb - }; + private readonly List<(Hash256 Path, Account Account)> _pendingAccounts = new(); + private readonly Dictionary _storageTrees = new(); + + public void SetAccount(Address address, Account account) => + _pendingAccounts.Add((address.ToAccountPath.ToCommitment(), account)); + + public void SetAccount(Hash256 accountPath, Account account) => + _pendingAccounts.Add((accountPath, account)); + + public void SetSlot(Hash256 storagePath, ValueHash256 slotKey, byte[] value, bool rlpEncode = true) + { + if (!_storageTrees.TryGetValue(storagePath, out StorageTree? st)) + { + st = new StorageTree(ctx._store.GetTrieStore(storagePath), LimboLogs.Instance); + _storageTrees[storagePath] = st; + } + st.Set(slotKey, value, rlpEncode); + } + + public void Dispose() + { + Dictionary storageRoots = new(); + foreach (var (path, st) in _storageTrees) + { + st.Commit(); + storageRoots[path] = st.RootHash; + } + + foreach (var (path, account) in _pendingAccounts) + { + Account finalAccount = storageRoots.TryGetValue(path, out Hash256? root) + ? account.WithChangedStorageRoot(root) + : account; + ctx._tree.Set(path, finalAccount); + } + + ctx._tree.Commit(); + } + } + } + + private ISnapServerContext CreateContext(ILastNStateRootTracker? lastNStateRootTracker = null) => + new TrieSnapServerContext(lastNStateRootTracker); + + private static void FillWithTestAccounts(ISnapServerContext context) + { + using var batch = context.BeginWriteBatch(); + foreach (var pwa in TestItem.Tree.AccountsWithPaths) + batch.SetAccount(pwa.Path.ToCommitment(), pwa.Account); + } + + private static void FillMultipleAccounts(ISnapServerContext context, int count) + { + using var batch = context.BeginWriteBatch(); + for (int i = 0; i < count; i++) + batch.SetAccount(Keccak.Compute(i.ToBigEndianByteArray()), Build.An.Account.WithBalance((UInt256)i).TestObject); + } + + private static Hash256 FillAccountWithDefaultStorage(ISnapServerContext context) + { + using (var batch = context.BeginWriteBatch()) + { + for (int i = 0; i < 6; i++) + batch.SetSlot(TestItem.Tree.AccountAddress0, TestItem.Tree.SlotsWithPaths[i].Path, TestItem.Tree.SlotsWithPaths[i].SlotRlpValue, rlpEncode: false); + batch.SetAccount(TestItem.Tree.AccountAddress0, Build.An.Account.WithBalance(1).TestObject); + } + return context.GetStorageRoot(TestItem.Tree.AccountAddress0); + } + + private static Hash256 FillAccountWithStorage(ISnapServerContext context, int slotCount) + { + using (var batch = context.BeginWriteBatch()) + { + for (int i = 0; i < slotCount; i++) + { + var key = Keccak.Compute(i.ToBigEndianByteArray()); + batch.SetSlot(TestItem.Tree.AccountAddress0, key, key.BytesToArray(), rlpEncode: false); + } + batch.SetAccount(TestItem.Tree.AccountAddress0, Build.An.Account.WithBalance(1).TestObject); + } + return context.GetStorageRoot(TestItem.Tree.AccountAddress0); } [Test] public void TestGetAccountRange() { - Context context = CreateContext(); - TestItem.Tree.FillStateTreeWithTestAccounts(context.Tree); + using var context = CreateContext(); + FillWithTestAccounts(context); (IOwnedReadOnlyList accounts, IOwnedReadOnlyList proofs) = - context.Server.GetAccountRanges(context.Tree.RootHash, Keccak.Zero, Keccak.MaxValue, 4000, CancellationToken.None); + context.Server.GetAccountRanges(context.RootHash, Keccak.Zero, Keccak.MaxValue, 4000, CancellationToken.None); - AddRangeResult result = context.SnapProvider.AddAccountRange(1, context.Tree.RootHash, Keccak.Zero, + AddRangeResult result = context.SnapProvider.AddAccountRange(1, context.RootHash, Keccak.Zero, accounts.ToArray(), proofs.ToArray()); result.Should().Be(AddRangeResult.OK); - context.ClientStateDb.Keys.Count.Should().Be(10); + context.PersistedNodeCount.Should().Be(10); accounts.Dispose(); proofs.Dispose(); } @@ -79,11 +181,11 @@ public void TestGetAccountRange() [Test] public void TestGetAccountRange_InvalidRange() { - Context context = CreateContext(); - TestItem.Tree.FillStateTreeWithTestAccounts(context.Tree); + using var context = CreateContext(); + FillWithTestAccounts(context); (IOwnedReadOnlyList accounts, IOwnedReadOnlyList proofs) = - context.Server.GetAccountRanges(context.Tree.RootHash, Keccak.MaxValue, Keccak.Zero, 4000, CancellationToken.None); + context.Server.GetAccountRanges(context.RootHash, Keccak.MaxValue, Keccak.Zero, 4000, CancellationToken.None); accounts.Count.Should().Be(0); accounts.Dispose(); @@ -93,15 +195,15 @@ public void TestGetAccountRange_InvalidRange() [Test] public void TestGetTrieNode_Root() { - Context context = CreateContext(); - TestItem.Tree.FillStateTreeWithTestAccounts(context.Tree); + using var context = CreateContext(); + FillWithTestAccounts(context); using IOwnedReadOnlyList result = context.Server.GetTrieNodes([ new PathGroup() { Group = [[]] } - ], context.Tree.RootHash, default)!; + ], context.RootHash, default)!; result.Count.Should().Be(1); } @@ -109,15 +211,15 @@ public void TestGetTrieNode_Root() [Test] public void TestGetTrieNode_Storage_Root() { - Context context = CreateContext(); - TestItem.Tree.FillStateTreeWithTestAccounts(context.Tree); + using var context = CreateContext(); + FillWithTestAccounts(context); using IOwnedReadOnlyList result = context.Server.GetTrieNodes([ new PathGroup() { Group = [TestItem.Tree.AccountsWithPaths[0].Path.Bytes.ToArray(), []] } - ], context.Tree.RootHash, default)!; + ], context.RootHash, default)!; result.Count.Should().Be(1); } @@ -133,15 +235,15 @@ public void TestNoState(bool withLastNStateTracker) lastNStateTracker.HasStateRoot(Arg.Any()).Returns(false); } - Context context = CreateContext(stateRootTracker: CreateConstantStateRootTracker(withLastNStateTracker), lastNStateRootTracker: lastNStateTracker); + using var context = CreateContext(lastNStateRootTracker: lastNStateTracker); (IOwnedReadOnlyList accounts, IOwnedReadOnlyList accountProofs) = - context.Server.GetAccountRanges(context.Tree.RootHash, Keccak.Zero, Keccak.MaxValue, 4000, CancellationToken.None); + context.Server.GetAccountRanges(context.RootHash, Keccak.Zero, Keccak.MaxValue, 4000, CancellationToken.None); accounts.Count.Should().Be(0); (IOwnedReadOnlyList> storageSlots, IOwnedReadOnlyList? proofs) = - context.Server.GetStorageRanges(context.Tree.RootHash, [TestItem.Tree.AccountsWithPaths[0]], + context.Server.GetStorageRanges(context.RootHash, [TestItem.Tree.AccountsWithPaths[0]], ValueKeccak.Zero, ValueKeccak.MaxValue, 10, CancellationToken.None); storageSlots.Count.Should().Be(0); @@ -155,18 +257,18 @@ public void TestNoState(bool withLastNStateTracker) [Test] public void TestGetAccountRangeMultiple() { - Context context = CreateContext(); - TestItem.Tree.FillStateTreeWithTestAccounts(context.Tree); + using var context = CreateContext(); + FillWithTestAccounts(context); Hash256 startRange = Keccak.Zero; while (true) { (IOwnedReadOnlyList accounts, IOwnedReadOnlyList proofs) = - context.Server.GetAccountRanges(context.Tree.RootHash, startRange, Keccak.MaxValue, 100, CancellationToken.None); + context.Server.GetAccountRanges(context.RootHash, startRange, Keccak.MaxValue, 100, CancellationToken.None); try { - AddRangeResult result = context.SnapProvider.AddAccountRange(1, context.Tree.RootHash, startRange, + AddRangeResult result = context.SnapProvider.AddAccountRange(1, context.RootHash, startRange, accounts, proofs); result.Should().Be(AddRangeResult.OK); @@ -182,7 +284,7 @@ public void TestGetAccountRangeMultiple() proofs.Dispose(); } } - context.ClientStateDb.Keys.Count.Should().Be(10); + context.PersistedNodeCount.Should().Be(10); } [TestCase(10, 10)] @@ -191,18 +293,18 @@ public void TestGetAccountRangeMultiple() [TestCase(10000, 10000)] public void TestGetAccountRangeMultipleLarger(int stateSize, int byteLimit) { - Context context = CreateContext(); - TestItem.Tree.FillStateTreeMultipleAccount(context.Tree, stateSize); + using var context = CreateContext(); + FillMultipleAccounts(context, stateSize); Hash256 startRange = Keccak.Zero; while (true) { (IOwnedReadOnlyList accounts, IOwnedReadOnlyList proofs) = - context.Server.GetAccountRanges(context.Tree.RootHash, startRange, Keccak.MaxValue, byteLimit, CancellationToken.None); + context.Server.GetAccountRanges(context.RootHash, startRange, Keccak.MaxValue, byteLimit, CancellationToken.None); try { - AddRangeResult result = context.SnapProvider.AddAccountRange(1, context.Tree.RootHash, startRange, + AddRangeResult result = context.SnapProvider.AddAccountRange(1, context.RootHash, startRange, accounts, proofs); result.Should().Be(AddRangeResult.OK); @@ -227,19 +329,19 @@ public void TestGetAccountRangeMultipleLarger(int stateSize, int byteLimit) [TestCase(10000, 10000000)] public void TestGetAccountRangeArtificialLimit(int stateSize, int byteLimit) { - Context context = CreateContext(); - TestItem.Tree.FillStateTreeMultipleAccount(context.Tree, stateSize); + using var context = CreateContext(); + FillMultipleAccounts(context, stateSize); Hash256 startRange = Keccak.Zero; ValueHash256 limit = new ValueHash256("0x8000000000000000000000000000000000000000000000000000000000000000"); while (true) { (IOwnedReadOnlyList accounts, IOwnedReadOnlyList proofs) = context.Server - .GetAccountRanges(context.Tree.RootHash, startRange, limit, byteLimit, CancellationToken.None); + .GetAccountRanges(context.RootHash, startRange, limit, byteLimit, CancellationToken.None); try { - AddRangeResult result = context.SnapProvider.AddAccountRange(1, context.Tree.RootHash, startRange, + AddRangeResult result = context.SnapProvider.AddAccountRange(1, context.RootHash, startRange, accounts, proofs); result.Should().Be(AddRangeResult.OK); @@ -261,22 +363,11 @@ public void TestGetAccountRangeArtificialLimit(int stateSize, int byteLimit) [Test] public void TestGetStorageRange() { - MemDb stateDb = new MemDb(); - MemDb codeDb = new MemDb(); - TestRawTrieStore store = new TestRawTrieStore(stateDb); - - (StateTree inputStateTree, StorageTree inputStorageTree, Hash256 _) = TestItem.Tree.GetTrees(store); - - SnapServer server = new(store.AsReadOnly(), codeDb, CreateConstantStateRootTracker(true), LimboLogs.Instance); - - IDb codeDb2 = new MemDb(); - IDb stateDb2 = new MemDb(); - - using ProgressTracker progressTracker = new(stateDb2, new TestSyncConfig(), new StateSyncPivot(null!, new TestSyncConfig(), LimboLogs.Instance), LimboLogs.Instance); - SnapProvider snapProvider = new(progressTracker, codeDb2, new NodeStorage(stateDb2), LimboLogs.Instance); + using var context = CreateContext(); + Hash256 storageRoot = FillAccountWithDefaultStorage(context); (IOwnedReadOnlyList> storageSlots, IOwnedReadOnlyList? proofs) = - server.GetStorageRanges(inputStateTree.RootHash, [TestItem.Tree.AccountsWithPaths[0]], + context.Server.GetStorageRanges(context.RootHash, [TestItem.Tree.AccountsWithPaths[0]], ValueKeccak.Zero, ValueKeccak.MaxValue, 10, CancellationToken.None); try @@ -284,9 +375,9 @@ public void TestGetStorageRange() var storageRangeRequest = new StorageRange() { StartingHash = Keccak.Zero, - Accounts = new ArrayPoolList(1) { new(TestItem.Tree.AccountsWithPaths[0].Path, new Account(UInt256.Zero).WithChangedStorageRoot(inputStorageTree.RootHash)) } + Accounts = new ArrayPoolList(1) { new(TestItem.Tree.AccountsWithPaths[0].Path, new Account(UInt256.Zero).WithChangedStorageRoot(storageRoot)) } }; - AddRangeResult result = snapProvider.AddStorageRangeForAccount(storageRangeRequest, 0, storageSlots[0], proofs); + AddRangeResult result = context.SnapProvider.AddStorageRangeForAccount(storageRangeRequest, 0, storageSlots[0], proofs); result.Should().Be(AddRangeResult.OK); } @@ -300,20 +391,15 @@ public void TestGetStorageRange() [Test] public void TestGetStorageRange_NoSlotsForAccount() { - MemDb stateDb = new MemDb(); - MemDb codeDb = new MemDb(); - TestRawTrieStore store = new TestRawTrieStore(stateDb); - - (StateTree inputStateTree, StorageTree inputStorageTree, Hash256 _) = TestItem.Tree.GetTrees(store); - - SnapServer server = new(store.AsReadOnly(), codeDb, CreateConstantStateRootTracker(true), LimboLogs.Instance); + using var context = CreateContext(); + FillAccountWithDefaultStorage(context); ValueHash256 lastStorageHash = TestItem.Tree.SlotsWithPaths[^1].Path; var asInt = lastStorageHash.ToUInt256(); ValueHash256 beyondLast = new ValueHash256((++asInt).ToBigEndian()); (IOwnedReadOnlyList> storageSlots, IOwnedReadOnlyList? proofs) = - server.GetStorageRanges(inputStateTree.RootHash, [TestItem.Tree.AccountsWithPaths[0]], + context.Server.GetStorageRanges(context.RootHash, [TestItem.Tree.AccountsWithPaths[0]], beyondLast, ValueKeccak.MaxValue, 10, CancellationToken.None); storageSlots.Count.Should().Be(0); @@ -326,25 +412,14 @@ public void TestGetStorageRange_NoSlotsForAccount() [Test] public void TestGetStorageRangeMulti() { - MemDb stateDb = new MemDb(); - MemDb codeDb = new MemDb(); - TestRawTrieStore store = new TestRawTrieStore(stateDb); - - (StateTree inputStateTree, StorageTree inputStorageTree, Hash256 _) = TestItem.Tree.GetTrees(store, 10000); - - SnapServer server = new(store.AsReadOnly(), codeDb, CreateConstantStateRootTracker(true), LimboLogs.Instance); - - IDb stateDb2 = new MemDb(); - IDb codeDb2 = new MemDb(); - - using ProgressTracker progressTracker = new(stateDb2, new TestSyncConfig(), new StateSyncPivot(null!, new TestSyncConfig(), LimboLogs.Instance), LimboLogs.Instance); - SnapProvider snapProvider = new(progressTracker, codeDb2, new NodeStorage(stateDb2), LimboLogs.Instance); + using var context = CreateContext(); + Hash256 storageRoot = FillAccountWithStorage(context, 10000); Hash256 startRange = Keccak.Zero; while (true) { (IOwnedReadOnlyList> storageSlots, IOwnedReadOnlyList? proofs) = - server.GetStorageRanges(inputStateTree.RootHash, [TestItem.Tree.AccountsWithPaths[0]], + context.Server.GetStorageRanges(context.RootHash, [TestItem.Tree.AccountsWithPaths[0]], startRange, ValueKeccak.MaxValue, 10000, CancellationToken.None); try @@ -352,9 +427,9 @@ public void TestGetStorageRangeMulti() var storageRangeRequest = new StorageRange() { StartingHash = startRange, - Accounts = new ArrayPoolList(1) { new(TestItem.Tree.AccountsWithPaths[0].Path, new Account(UInt256.Zero).WithChangedStorageRoot(inputStorageTree.RootHash)) } + Accounts = new ArrayPoolList(1) { new(TestItem.Tree.AccountsWithPaths[0].Path, new Account(UInt256.Zero).WithChangedStorageRoot(storageRoot)) } }; - AddRangeResult result = snapProvider.AddStorageRangeForAccount(storageRangeRequest, 0, storageSlots[0], proofs); + AddRangeResult result = context.SnapProvider.AddStorageRangeForAccount(storageRangeRequest, 0, storageSlots[0], proofs); result.Should().Be(AddRangeResult.OK); if (startRange == storageSlots[0][^1].Path.ToCommitment()) @@ -375,73 +450,65 @@ public void TestGetStorageRangeMulti() [Test] public void TestWithHugeTree() { - MemDb stateDb = new MemDb(); - MemDb codeDb = new MemDb(); - TestRawTrieStore store = new TestRawTrieStore(stateDb); - - StateTree stateTree = new(store, LimboLogs.Instance); + using var context = CreateContext(); // generate Remote Tree - for (int accountIndex = 0; accountIndex < 10000; accountIndex++) + using (var batch = context.BeginWriteBatch()) { - stateTree.Set(TestItem.GetRandomAddress(), TestItem.GenerateRandomAccount()); + for (int accountIndex = 0; accountIndex < 10000; accountIndex++) + batch.SetAccount(TestItem.GetRandomAddress(), TestItem.GenerateRandomAccount()); } - stateTree.Commit(); List accountWithStorage = new(); - for (int i = 1000; i < 10000; i += 1000) + using (var batch = context.BeginWriteBatch()) { - Address address = TestItem.GetRandomAddress(); - StorageTree storageTree = new(store.GetTrieStore(address), LimboLogs.Instance); - for (int j = 0; j < i; j += 1) + for (int i = 1000; i < 10000; i += 1000) { - storageTree.Set(TestItem.GetRandomKeccak(), TestItem.GetRandomKeccak().Bytes.ToArray()); + Address address = TestItem.GetRandomAddress(); + Hash256 storagePath = address.ToAccountPath.ToCommitment(); + for (int j = 0; j < i; j += 1) + batch.SetSlot(storagePath, TestItem.GetRandomKeccak(), TestItem.GetRandomKeccak().Bytes.ToArray()); + batch.SetAccount(address, TestItem.GenerateRandomAccount()); + accountWithStorage.Add(new PathWithAccount(address.ToAccountPath, new Account(0))); } - storageTree.Commit(); - var account = TestItem.GenerateRandomAccount().WithChangedStorageRoot(storageTree.RootHash); - stateTree.Set(address, account); - accountWithStorage.Add(new PathWithAccount() { Path = Keccak.Compute(address.Bytes), Account = account }); } - stateTree.Commit(); - - SnapServer server = new(store.AsReadOnly(), codeDb, CreateConstantStateRootTracker(true), LimboLogs.Instance); // size of one PathWithAccount ranges from 39 -> 72 (IOwnedReadOnlyList accounts, IOwnedReadOnlyList accountProofs) - = server.GetAccountRanges(stateTree.RootHash, Keccak.Zero, Keccak.MaxValue, 10, CancellationToken.None); + = context.Server.GetAccountRanges(context.RootHash, Keccak.Zero, Keccak.MaxValue, 10, CancellationToken.None); accounts.Count.Should().Be(1); accounts.Dispose(); accountProofs.Dispose(); (accounts, accountProofs) = - server.GetAccountRanges(stateTree.RootHash, Keccak.Zero, Keccak.MaxValue, 100, CancellationToken.None); + context.Server.GetAccountRanges(context.RootHash, Keccak.Zero, Keccak.MaxValue, 100, CancellationToken.None); accounts.Count.Should().BeGreaterThan(2); accounts.Dispose(); accountProofs.Dispose(); (accounts, accountProofs) = - server.GetAccountRanges(stateTree.RootHash, Keccak.Zero, Keccak.MaxValue, 10000, CancellationToken.None); + context.Server.GetAccountRanges(context.RootHash, Keccak.Zero, Keccak.MaxValue, 10000, CancellationToken.None); accounts.Count.Should().BeGreaterThan(138); accounts.Dispose(); accountProofs.Dispose(); // TODO: Double check the threshold (accounts, accountProofs) = - server.GetAccountRanges(stateTree.RootHash, Keccak.Zero, Keccak.MaxValue, 720000, CancellationToken.None); + context.Server.GetAccountRanges(context.RootHash, Keccak.Zero, Keccak.MaxValue, 720000, CancellationToken.None); accounts.Count.Should().Be(10009); accounts.Dispose(); accountProofs.Dispose(); (accounts, accountProofs) = - server.GetAccountRanges(stateTree.RootHash, Keccak.Zero, Keccak.MaxValue, 10000000, CancellationToken.None); + context.Server.GetAccountRanges(context.RootHash, Keccak.Zero, Keccak.MaxValue, 10000000, CancellationToken.None); accounts.Count.Should().Be(10009); accounts.Dispose(); accountProofs.Dispose(); var accountWithStorageArray = accountWithStorage.ToArray(); - (IOwnedReadOnlyList> slots, IOwnedReadOnlyList? proofs) = server.GetStorageRanges(stateTree.RootHash, accountWithStorageArray[..1], ValueKeccak.Zero, ValueKeccak.MaxValue, 10, CancellationToken.None); + (IOwnedReadOnlyList> slots, IOwnedReadOnlyList? proofs) = context.Server.GetStorageRanges(context.RootHash, accountWithStorageArray[..1], ValueKeccak.Zero, ValueKeccak.MaxValue, 10, CancellationToken.None); slots.Count.Should().Be(1); slots[0].Count.Should().Be(1); proofs.Should().NotBeNull(); @@ -449,7 +516,7 @@ public void TestWithHugeTree() slots.DisposeRecursive(); proofs?.Dispose(); - (slots, proofs) = server.GetStorageRanges(stateTree.RootHash, accountWithStorageArray[..1], ValueKeccak.Zero, ValueKeccak.MaxValue, 1000000, CancellationToken.None); + (slots, proofs) = context.Server.GetStorageRanges(context.RootHash, accountWithStorageArray[..1], ValueKeccak.Zero, ValueKeccak.MaxValue, 1000000, CancellationToken.None); slots.Count.Should().Be(1); slots[0].Count.Should().Be(1000); proofs.Should().BeEmpty(); @@ -457,14 +524,14 @@ public void TestWithHugeTree() slots.DisposeRecursive(); proofs?.Dispose(); - (slots, proofs) = server.GetStorageRanges(stateTree.RootHash, accountWithStorageArray[..2], ValueKeccak.Zero, ValueKeccak.MaxValue, 10, CancellationToken.None); + (slots, proofs) = context.Server.GetStorageRanges(context.RootHash, accountWithStorageArray[..2], ValueKeccak.Zero, ValueKeccak.MaxValue, 10, CancellationToken.None); slots.Count.Should().Be(1); slots[0].Count.Should().Be(1); proofs.Should().NotBeNull(); slots.DisposeRecursive(); proofs?.Dispose(); - (slots, proofs) = server.GetStorageRanges(stateTree.RootHash, accountWithStorageArray[..2], ValueKeccak.Zero, ValueKeccak.MaxValue, 100000, CancellationToken.None); + (slots, proofs) = context.Server.GetStorageRanges(context.RootHash, accountWithStorageArray[..2], ValueKeccak.Zero, ValueKeccak.MaxValue, 100000, CancellationToken.None); slots.Count.Should().Be(2); slots[0].Count.Should().Be(1000); slots[1].Count.Should().Be(539); @@ -474,7 +541,7 @@ public void TestWithHugeTree() // incomplete tree will be returned as the hard limit is 2000000 - (slots, proofs) = server.GetStorageRanges(stateTree.RootHash, accountWithStorageArray, ValueKeccak.Zero, ValueKeccak.MaxValue, 3000000, CancellationToken.None); + (slots, proofs) = context.Server.GetStorageRanges(context.RootHash, accountWithStorageArray, ValueKeccak.Zero, ValueKeccak.MaxValue, 3000000, CancellationToken.None); slots.Count.Should().Be(8); slots[^1].Count.Should().BeLessThan(8000); proofs.Should().NotBeEmpty(); @@ -482,11 +549,4 @@ public void TestWithHugeTree() slots.DisposeRecursive(); proofs?.Dispose(); } - - private IStateReader CreateConstantStateRootTracker(bool available) - { - IStateReader tracker = Substitute.For(); - tracker.HasStateForBlock(Arg.Any()).Returns(available); - return tracker; - } } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/StateSyncPivotTest.cs b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/StateSyncPivotTest.cs index bd6404cf9c0a..815606d13b7a 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/SnapSync/TestSnapTrieFactory.cs b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/TestSnapTrieFactory.cs new file mode 100644 index 000000000000..ff84d5238850 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/TestSnapTrieFactory.cs @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core.Crypto; +using Nethermind.Synchronization.SnapSync; + +namespace Nethermind.Synchronization.Test.SnapSync; + +internal class TestSnapTrieFactory(Func createTree) : ISnapTrieFactory +{ + public ISnapTree CreateStateTree() => createTree(); + public ISnapTree CreateStorageTree(in ValueHash256 accountPath) => createTree(); +} diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncPeerPoolTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncPeerPoolTests.cs index 5eb813d9ca62..0ac56b1ab26e 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncPeerPoolTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncPeerPoolTests.cs @@ -618,6 +618,118 @@ public async Task When_no_peer_will_cancel_on_cancellation_token() result.Should().BeNull(); } + [Test] + public async Task Waiter_wakes_on_signal_after_free() + { + await using Context ctx = new(); + await SetupPeers(ctx, 1); + + // Allocate the only peer + SyncPeerAllocation first = await ctx.Pool.Allocate(new BySpeedStrategy(TransferSpeedType.Headers, true), AllocationContexts.All, 1000); + first.HasPeer.Should().BeTrue(); + + // Start a second allocation that must wait (only 1 peer, already allocated) + Task secondTask = ctx.Pool.Allocate(new BySpeedStrategy(TransferSpeedType.Headers, true), AllocationContexts.All, 2000); + + // Free the first — this signals peers changed and wakes the waiter + ctx.Pool.Free(first); + + SyncPeerAllocation second = await secondTask; + second.HasPeer.Should().BeTrue(); + } + + [Test] + public async Task Cancellation_while_waiting_returns_failed_allocation() + { + await using Context ctx = new(); + ctx.Pool.Start(); + + // No peers added — allocation will wait until cancelled + using CancellationTokenSource cts = new(); + cts.CancelAfter(100); + + SyncPeerAllocation result = await ctx.Pool.Allocate( + new BySpeedStrategy(TransferSpeedType.Headers, true), + AllocationContexts.All, + 5000, + cts.Token); + + result.HasPeer.Should().BeFalse(); + } + + [Test] + public async Task Dispose_while_waiting_does_not_hang() + { + Context ctx = new(); + ctx.Pool.Start(); + + // No peers — allocation will block + Task allocTask = ctx.Pool.Allocate( + new BySpeedStrategy(TransferSpeedType.Headers, true), + AllocationContexts.All, + 10000); + + await Task.Delay(50); + await ctx.DisposeAsync(); + + // Must complete (not hang) and return failed + SyncPeerAllocation result = await allocTask.WaitAsync(TimeSpan.FromSeconds(2)); + result.HasPeer.Should().BeFalse(); + } + + [Test] + public async Task Concurrent_allocations_capped_by_available_peers() + { + await using Context ctx = new(); + await SetupPeers(ctx, 3); + + // Request 5 allocations with only 3 peers + Task[] tasks = new Task[5]; + for (int i = 0; i < tasks.Length; i++) + { + tasks[i] = ctx.Pool.Allocate( + new BySpeedStrategy(TransferSpeedType.Headers, true), + AllocationContexts.All, + 100); + } + + await Task.WhenAll(tasks); + + int successful = 0; + for (int i = 0; i < tasks.Length; i++) + { + if (tasks[i].Result.HasPeer) successful++; + } + + successful.Should().Be(3); + } + + [Test] + public async Task All_waiters_wake_when_peers_freed() + { + await using Context ctx = new(); + await SetupPeers(ctx, 2); + + // Allocate both peers — pool exhausted + SyncPeerAllocation a1 = await ctx.Pool.Allocate(new BySpeedStrategy(TransferSpeedType.Headers, true), AllocationContexts.All, 1000); + SyncPeerAllocation a2 = await ctx.Pool.Allocate(new BySpeedStrategy(TransferSpeedType.Headers, true), AllocationContexts.All, 1000); + a1.HasPeer.Should().BeTrue(); + a2.HasPeer.Should().BeTrue(); + + // Start 2 waiters — both blocked, no peers available + Task w1 = ctx.Pool.Allocate(new BySpeedStrategy(TransferSpeedType.Headers, true), AllocationContexts.All, 3000); + Task w2 = ctx.Pool.Allocate(new BySpeedStrategy(TransferSpeedType.Headers, true), AllocationContexts.All, 3000); + + // Free both peers — both waiters must wake + ctx.Pool.Free(a1); + ctx.Pool.Free(a2); + + SyncPeerAllocation r1 = await w1; + SyncPeerAllocation r2 = await w2; + r1.HasPeer.Should().BeTrue(); + r2.HasPeer.Should().BeTrue(); + } + private async Task SetupPeers(Context ctx, int count) { SimpleSyncPeerMock[] peers = new SimpleSyncPeerMock[count]; diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs index 4a8bcfde2c03..ffc7eb77719a 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs @@ -6,7 +6,6 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Test.Builders; -using Nethermind.Logging; using Nethermind.State; using Nethermind.Synchronization.FastBlocks; using Nethermind.Synchronization.ParallelSync; @@ -28,10 +27,10 @@ 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); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); blockTree.BestSuggestedHeader.ReturnsNull(); Assert.That(syncProgressResolver.FindBestHeader(), Is.EqualTo(0)); } @@ -43,10 +42,10 @@ 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); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); blockTree.BestSuggestedBody.ReturnsNull(); Assert.That(syncProgressResolver.FindBestFullBlock(), Is.EqualTo(0)); } @@ -58,10 +57,10 @@ 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); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); Block head = Build.A.Block.WithHeader(Build.A.BlockHeader.WithNumber(5).WithStateRoot(TestItem.KeccakA).TestObject).TestObject; blockTree.Head.Returns(head); blockTree.BestSuggestedHeader.Returns(head.Header); @@ -76,10 +75,10 @@ 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); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); Block head = Build.A.Block.WithHeader(Build.A.BlockHeader.WithNumber(5).WithStateRoot(TestItem.KeccakA).TestObject).TestObject; BlockHeader suggested = Build.A.BlockHeader.WithNumber(6).WithStateRoot(TestItem.KeccakB).TestObject; blockTree.Head.Returns(head); @@ -98,10 +97,10 @@ 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); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); Block head = Build.A.Block.WithHeader(Build.A.BlockHeader.WithNumber(5).WithStateRoot(TestItem.KeccakA).TestObject).TestObject; BlockHeader suggested = Build.A.BlockHeader.WithNumber(6).WithStateRoot(TestItem.KeccakB).TestObject; blockTree.Head.Returns(head); @@ -120,10 +119,10 @@ 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); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); Assert.That(syncProgressResolver.IsFastBlocksHeadersFinished(), Is.True); Assert.That(syncProgressResolver.IsFastBlocksBodiesFinished(), Is.True); Assert.That(syncProgressResolver.IsFastBlocksReceiptsFinished(), Is.True); @@ -139,13 +138,13 @@ 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)); blockTree.LowestInsertedHeader.Returns(Build.A.BlockHeader.WithNumber(1).WithStateRoot(TestItem.KeccakA).TestObject); - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); Assert.That(syncProgressResolver.IsFastBlocksBodiesFinished(), Is.False); } @@ -159,17 +158,17 @@ 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); - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, true, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, true, syncConfig); Assert.That(syncProgressResolver.IsFastBlocksReceiptsFinished(), Is.True); } - private SyncProgressResolver CreateProgressResolver(IBlockTree blockTree, IStateReader stateReader, bool isReceiptFinished, SyncConfig syncConfig, LimboLogs limboLogs) + private SyncProgressResolver CreateProgressResolver(IBlockTree blockTree, IStateReader stateReader, bool isReceiptFinished, SyncConfig syncConfig) { ISyncFeed receiptFeed = Substitute.For>(); receiptFeed.IsFinished.Returns(isReceiptFinished); @@ -181,8 +180,7 @@ private SyncProgressResolver CreateProgressResolver(IBlockTree blockTree, IState Substitute.For>(), Substitute.For>(), receiptFeed, - Substitute.For>(), - limboLogs + Substitute.For>() ); } } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncReportTest.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncReportTest.cs index d542b0498f02..f8a0a0eeafa7 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/SyncServerTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs index 031426920ff0..e65fa535eff9 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs @@ -666,7 +666,7 @@ public async Task Broadcast_NewBlock_on_arrival_to_sqrt_of_peers([Values(1, 2, 3 int count = 0; remoteServer .When(r => r.AddNewBlock(Arg.Is(b => b.Hash == remoteBlockTree.Head!.Hash), Arg.Any())) - .Do(_ => count++); + .Do(_ => Interlocked.Increment(ref count)); PeerInfo[] peers = Enumerable.Range(0, peerCount).Take(peerCount) .Select(_ => new PeerInfo(new SyncPeerMock(remoteBlockTree, remoteSyncServer: remoteServer))) .ToArray(); @@ -679,6 +679,8 @@ public async Task Broadcast_NewBlock_on_arrival_to_sqrt_of_peers([Values(1, 2, 3 } [Test] + [Retry(3)] + [Parallelizable(ParallelScope.None)] public void Broadcast_BlockRangeUpdate_when_latest_increased_enough() { Context ctx = new(); @@ -715,7 +717,7 @@ public void Broadcast_BlockRangeUpdate_when_latest_increased_enough() localBlockTree.AddBranch(blocksCount * 2 / 3, splitBlockNumber: startBlock, splitVariant: 0); localBlockTree.AddBranch(blocksCount, splitBlockNumber: startBlock, splitVariant: 0); - var expectedUpdates = Enumerable.Range(startBlock + 1, blocksCount) + (long earliest, int latest)[] expectedUpdates = Enumerable.Range(startBlock + 1, blocksCount) .Where(x => x % frequency == 0) .Select(x => (earliest: localBlockTree.Genesis!.Number, latest: x)) .ToArray()[^2..]; @@ -727,7 +729,7 @@ public void Broadcast_BlockRangeUpdate_when_latest_increased_enough() .Where(c => c.GetMethodInfo().Name == nameof(ISyncPeer.NotifyOfNewRange)) .Select(c => c.GetArguments().Cast().Select(b => b.Number).ToArray()) .Select(a => (earliest: a[0], latest: a[1])).ToArray()[^2..], - Is.EquivalentTo(expectedUpdates).After(8000, 100) // Wait for background notifications to finish + Is.EquivalentTo(expectedUpdates).After(15000, 50) // Wait for background notifications to finish ); } } @@ -775,6 +777,14 @@ public void GetNodeData_returns_cached_trie_nodes() ctx.SyncServer.GetNodeData(new[] { nodeKey }, CancellationToken.None, NodeDataType.All).Should().BeEquivalentTo(new[] { TestItem.KeccakB.BytesToArray() }); } + [Test] + public void Correctly_clips_lowestBlock() + { + Context ctx = new(); + ctx.BlockTree.GetLowestBlock().Returns(5); + ctx.SyncServer.LowestBlock.Should().Be(0); + } + private class Context { public Context() diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SynchronizerModuleTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SynchronizerModuleTests.cs index 3920eb07a27a..d635aadccf8e 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/TestSynchronizerModule.cs b/src/Nethermind/Nethermind.Synchronization.Test/TestSynchronizerModule.cs index c4f869415048..5f73c6fb23b6 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/TestSynchronizerModule.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/TestSynchronizerModule.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using Autofac; using Nethermind.Api; using Nethermind.Blockchain; @@ -12,6 +13,7 @@ using Nethermind.Init.Modules; using Nethermind.Logging; using Nethermind.Stats; +using Nethermind.Synchronization.SnapSync; using Nethermind.Trie; using NSubstitute; diff --git a/src/Nethermind/Nethermind.Synchronization.Test/Trie/HealingTreeTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/Trie/HealingTreeTests.cs index 6491496fecd8..f83ffe65e7e0 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/Trie/HealingTreeTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/Trie/HealingTreeTests.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq.Expressions; using System.Threading.Tasks; using Autofac; using FluentAssertions; @@ -193,7 +192,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.Test/Trie/RecoveryTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/Trie/RecoveryTests.cs index b6a4e9eb1dd7..493305d109aa 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/Trie/RecoveryTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/Trie/RecoveryTests.cs @@ -1,14 +1,11 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using System.Linq; -using System.Net; using System.Threading; using System.Threading.Tasks; using FluentAssertions; -using Nethermind.Blockchain; using Nethermind.Blockchain.Synchronization; using Nethermind.Core.Buffers; using Nethermind.Core.Collections; @@ -20,7 +17,6 @@ using Nethermind.Network.Contract.P2P; using Nethermind.State.Healing; using Nethermind.State.Snap; -using Nethermind.Stats; using Nethermind.Synchronization.Peers; using Nethermind.Synchronization.Peers.AllocationStrategies; using Nethermind.Synchronization.Trie; diff --git a/src/Nethermind/Nethermind.Synchronization.Test/TrieScenarios.cs b/src/Nethermind/Nethermind.Synchronization.Test/TrieScenarios.cs index e74aa5848b36..6b165dda8b82 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/TrieScenarios.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/TrieScenarios.cs @@ -63,279 +63,275 @@ public static void InitOnce() public static (string Name, Action Action)[] Scenarios => LazyInitializer.EnsureInitialized(ref _scenarios, InitScenarios); - private static (string Name, Action Action)[] InitScenarios() - { - return new (string, Action)[] + private static (string Name, Action Action)[] InitScenarios() => + [ + ("empty", static (tree, _, codeDb) => + { + codeDb[Keccak.Compute(Code0).Bytes] = Code0; + tree.Commit(); + }), + ("set_3_via_address", static (tree, stateDb, codeDb) => + { + codeDb[Keccak.Compute(Code0).Bytes] = Code0; + SetStorage(tree, stateDb, TestItem.AddressA, Account0); + SetStorage(tree, stateDb, TestItem.AddressB, Account0); + SetStorage(tree, stateDb, TestItem.AddressC, Account0); + tree.Commit(); + }), + ("storage_hash_and_code_hash_same", static (tree, stateDb, codeDb) => + { + byte[] code = Bytes.FromHexString("e3a120b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf601"); + Hash256 codeHash = Keccak.Compute(code); + Hash256 account = new("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + StorageTree remoteStorageTree = new(stateDb.GetTrieStore(account), Keccak.EmptyTreeHash, LimboLogs.Instance); + remoteStorageTree.Set((UInt256) 1, new byte[] {1}); + remoteStorageTree.Commit(); + remoteStorageTree.UpdateRootHash(); + codeDb[codeHash.Bytes] = code; + tree.Set(account, AccountJustState0.WithChangedStorageRoot(remoteStorageTree.RootHash).WithChangedCodeHash(codeHash)); + tree.Commit(); + }), + ("storage_hash_and_code_hash_same_with_additional_account_of_same_storage_root", static (tree, stateDb, codeDb) => { - ("empty", static (tree, _, codeDb) => - { - codeDb[Keccak.Compute(Code0).Bytes] = Code0; - tree.Commit(); - }), - ("set_3_via_address", static (tree, stateDb, codeDb) => - { - codeDb[Keccak.Compute(Code0).Bytes] = Code0; - SetStorage(tree, stateDb, TestItem.AddressA, Account0); - SetStorage(tree, stateDb, TestItem.AddressB, Account0); - SetStorage(tree, stateDb, TestItem.AddressC, Account0); - tree.Commit(); - }), - ("storage_hash_and_code_hash_same", static (tree, stateDb, codeDb) => - { - byte[] code = Bytes.FromHexString("e3a120b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf601"); - Hash256 codeHash = Keccak.Compute(code); - Hash256 account = new Hash256("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - StorageTree remoteStorageTree = new(stateDb.GetTrieStore(account), Keccak.EmptyTreeHash, LimboLogs.Instance); - remoteStorageTree.Set((UInt256) 1, new byte[] {1}); - remoteStorageTree.Commit(); - remoteStorageTree.UpdateRootHash(); - codeDb[codeHash.Bytes] = code; - tree.Set(account, AccountJustState0.WithChangedStorageRoot(remoteStorageTree.RootHash).WithChangedCodeHash(codeHash)); - tree.Commit(); - }), - ("storage_hash_and_code_hash_same_with_additional_account_of_same_storage_root", static (tree, stateDb, codeDb) => - { - byte[] code = Bytes.FromHexString("e3a120b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf601"); - Hash256 codeHash = Keccak.Compute(code); + byte[] code = Bytes.FromHexString("e3a120b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf601"); + Hash256 codeHash = Keccak.Compute(code); - Hash256 account1 = new Hash256("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - StorageTree remoteStorageTree1 = new(stateDb.GetTrieStore(account1), Keccak.EmptyTreeHash, LimboLogs.Instance); - remoteStorageTree1.Set((UInt256) 1, new byte[] {1}); - remoteStorageTree1.Commit(); - remoteStorageTree1.UpdateRootHash(); + Hash256 account1 = new("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + StorageTree remoteStorageTree1 = new(stateDb.GetTrieStore(account1), Keccak.EmptyTreeHash, LimboLogs.Instance); + remoteStorageTree1.Set((UInt256) 1, new byte[] {1}); + remoteStorageTree1.Commit(); + remoteStorageTree1.UpdateRootHash(); - Hash256 account2 = new Hash256("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); - StorageTree remoteStorageTree2 = new(stateDb.GetTrieStore(account2), Keccak.EmptyTreeHash, LimboLogs.Instance); - remoteStorageTree2.Set((UInt256) 1, new byte[] {1}); - remoteStorageTree2.Commit(); - remoteStorageTree2.UpdateRootHash(); + Hash256 account2 = new("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); + StorageTree remoteStorageTree2 = new(stateDb.GetTrieStore(account2), Keccak.EmptyTreeHash, LimboLogs.Instance); + remoteStorageTree2.Set((UInt256) 1, new byte[] {1}); + remoteStorageTree2.Commit(); + remoteStorageTree2.UpdateRootHash(); - codeDb[codeHash.Bytes] = code; - tree.Set(account1, AccountJustState0.WithChangedStorageRoot(remoteStorageTree1.RootHash)); - tree.Set(account2, AccountJustState0.WithChangedStorageRoot(remoteStorageTree2.RootHash).WithChangedCodeHash(codeHash)); - tree.Commit(); - }), - ("storage_hash_and_code_hash_same_with_additional_account_of_same_code", static (tree, stateDb, codeDb) => - { - byte[] code = Bytes.FromHexString("e3a120b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf601"); - Hash256 codeHash = Keccak.Compute(code); + codeDb[codeHash.Bytes] = code; + tree.Set(account1, AccountJustState0.WithChangedStorageRoot(remoteStorageTree1.RootHash)); + tree.Set(account2, AccountJustState0.WithChangedStorageRoot(remoteStorageTree2.RootHash).WithChangedCodeHash(codeHash)); + tree.Commit(); + }), + ("storage_hash_and_code_hash_same_with_additional_account_of_same_code", static (tree, stateDb, codeDb) => + { + byte[] code = Bytes.FromHexString("e3a120b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf601"); + Hash256 codeHash = Keccak.Compute(code); - Hash256 accountWithStorage = - new Hash256("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); - StorageTree remoteStorageTree = new(stateDb.GetTrieStore(accountWithStorage), Keccak.EmptyTreeHash, LimboLogs.Instance); - remoteStorageTree.Set((UInt256) 1, new byte[] {1}); - remoteStorageTree.Commit(); - remoteStorageTree.UpdateRootHash(); + Hash256 accountWithStorage = new("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); + StorageTree remoteStorageTree = new(stateDb.GetTrieStore(accountWithStorage), Keccak.EmptyTreeHash, LimboLogs.Instance); + remoteStorageTree.Set((UInt256) 1, new byte[] {1}); + remoteStorageTree.Commit(); + remoteStorageTree.UpdateRootHash(); - codeDb[codeHash.Bytes] = code; - tree.Set(new Hash256("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), AccountJustState0.WithChangedCodeHash(codeHash)); - tree.Set(accountWithStorage, AccountJustState0.WithChangedStorageRoot(remoteStorageTree.RootHash).WithChangedCodeHash(codeHash)); - tree.Commit(); - }), - ("branch_with_same_accounts_at_different_addresses", static (tree, _, codeDb) => - { - codeDb[Keccak.Compute(Code0).Bytes] = Code0; - tree.Set(new Hash256("1baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), AccountJustState0); - tree.Set(new Hash256("2baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), AccountJustState0); - tree.Commit(); - }), - ("set_3_delete_1", static (tree, stateDb, codeDb) => - { - codeDb[Keccak.Compute(Code0).Bytes] = Code0; - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), Account0); - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb0"), Account0); - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb1"), Account0); - tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb1"), null); - tree.Commit(); - }), - ("set_3_delete_2", static (tree, stateDb, codeDb) => - { - codeDb[Keccak.Compute(Code0).Bytes] = Code0; - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), Account0); - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb0"), Account0); - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb1"), Account0); - tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb0"), null); - tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb1"), null); - tree.Commit(); - }), - ("set_3_delete_all", static (tree, _, _) => - { -// SetStorage(stateDb); - tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), Account0); - tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb0"), Account0); - tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb1"), Account0); - tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb0"), null); - tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb1"), null); - tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), null); - tree.Commit(); - }), - ("extension_read_full_match", static (tree, stateDb, codeDb) => - { - codeDb[Keccak.Compute(Code0).Bytes] = Code0; - codeDb[Keccak.Compute(Code1).Bytes] = Code1; - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), Account0); - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111111"), Account1); - Account _ = tree.Get(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111111"))!; - tree.UpdateRootHash(); - Hash256 __ = tree.RootHash; - tree.Commit(); - }), - ("extension_read_missing", static (tree, stateDb, codeDb) => - { - codeDb[Keccak.Compute(Code0).Bytes] = Code0; - codeDb[Keccak.Compute(Code1).Bytes] = Code1; - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), Account0); - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111111"), Account1); - Account _ = tree.Get(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddd"))!; - tree.UpdateRootHash(); - Hash256 __ = tree.RootHash; - tree.Commit(); - }), - ("extension_new_branch", static (tree, stateDb, codeDb) => - { - codeDb[Keccak.Compute(Code0).Bytes] = Code0; - codeDb[Keccak.Compute(Code1).Bytes] = Code1; - codeDb[Keccak.Compute(Code2).Bytes] = Code2; - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), Account0); - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111111"), Account1); - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddd"), Account2); - tree.UpdateRootHash(); - Hash256 _ = tree.RootHash; - tree.Commit(); - }), - ("just_state", static (tree, _, _) => - { - tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), AccountJustState0); - tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111111"), AccountJustState1); - tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddd"), AccountJustState2); - tree.UpdateRootHash(); - Hash256 _ = tree.RootHash; - tree.Commit(); - }), - ("extension_delete_missing", static (tree, stateDb, codeDb) => - { - codeDb[Keccak.Compute(Code0).Bytes] = Code0; - codeDb[Keccak.Compute(Code1).Bytes] = Code1; - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), Account0); - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111111"), Account1); - tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddd"), null); - tree.UpdateRootHash(); - Hash256 _ = tree.RootHash; - tree.Commit(); - }), - ("extension_create_new_extension", static (tree, stateDb, codeDb) => - { - codeDb[Keccak.Compute(Code0).Bytes] = Code0; - codeDb[Keccak.Compute(Code1).Bytes] = Code1; - codeDb[Keccak.Compute(Code2).Bytes] = Code2; - codeDb[Keccak.Compute(Code3).Bytes] = Code3; - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), Account0); - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111111"), Account1); - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeaaaaaaaaaaaaaaaab00000000"), Account2); - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeaaaaaaaaaaaaaaaab11111111"), Account3); - tree.UpdateRootHash(); - Hash256 _ = tree.RootHash; - tree.Commit(); - }), - ("leaf_new_value", static (tree, stateDb, codeDb) => - { - codeDb[Keccak.Compute(Code1).Bytes] = Code1; - SetStorage(tree, stateDb, new Hash256("1111111111111111111111111111111111111111111111111111111111111111"), Account0); - SetStorage(tree, stateDb, new Hash256("1111111111111111111111111111111111111111111111111111111111111111"), Account1); - tree.UpdateRootHash(); - Hash256 _ = tree.RootHash; - tree.Commit(); - }), - ("leaf_no_change", static (tree, stateDb, codeDb) => - { - codeDb[Keccak.Compute(Code0).Bytes] = Code0; - SetStorage(tree, stateDb, new Hash256("1111111111111111111111111111111111111111111111111111111111111111"), Account0); - SetStorage(tree, stateDb, new Hash256("1111111111111111111111111111111111111111111111111111111111111111"), Account0); - tree.UpdateRootHash(); - Hash256 _ = tree.RootHash; - tree.Commit(); - }), - ("leaf_delete", static (tree, _, _) => - { - tree.Set(new Hash256("1111111111111111111111111111111111111111111111111111111111111111"), Account0); - tree.Set(new Hash256("1111111111111111111111111111111111111111111111111111111111111111"), null); - tree.UpdateRootHash(); - Hash256 _ = tree.RootHash; - tree.Commit(); - }), - ("leaf_delete_missing", static (tree, stateDb, codeDb) => - { - codeDb[Keccak.Compute(Code0).Bytes] = Code0; - SetStorage(tree, stateDb, new Hash256("1111111111111111111111111111111111111111111111111111111111111111"), Account0); - tree.Set(new Hash256("1111111111111111111111111111111ddddddddddddddddddddddddddddddddd"), null); - tree.UpdateRootHash(); - Hash256 _ = tree.RootHash; - tree.Commit(); - }), - ("leaf_update_extension", static (tree, stateDb, codeDb) => - { - codeDb[Keccak.Compute(Code0).Bytes] = Code0; - codeDb[Keccak.Compute(Code1).Bytes] = Code1; - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111111111111111111111111111111"), Account0); - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000000000000000000000000000"), Account1); - tree.UpdateRootHash(); - Hash256 _ = tree.RootHash; - tree.Commit(); - }), - ("leaf_read", static (tree, stateDb, codeDb) => - { - codeDb[Keccak.Compute(Code0).Bytes] = Code0; - SetStorage(tree, stateDb, new Hash256("1111111111111111111111111111111111111111111111111111111111111111"), Account0); - Account _ = tree.Get(new Hash256("1111111111111111111111111111111111111111111111111111111111111111"))!; - tree.UpdateRootHash(); - Hash256 __ = tree.RootHash; - tree.Commit(); - }), - ("leaf_update_missing", static (tree, stateDb, codeDb) => - { - codeDb[Keccak.Compute(Code0).Bytes] = Code0; - SetStorage(tree, stateDb, new Hash256("1111111111111111111111111111111111111111111111111111111111111111"), Account0); - Account _ = tree.Get(new Hash256("111111111111111111111111111111111111111111111111111111111ddddddd"))!; - tree.UpdateRootHash(); - Hash256 __ = tree.RootHash; - tree.Commit(); - }), - ("branch_update_missing", static (tree, stateDb, codeDb) => - { - codeDb[Keccak.Compute(Code0).Bytes] = Code0; - codeDb[Keccak.Compute(Code1).Bytes] = Code1; - codeDb[Keccak.Compute(Code2).Bytes] = Code2; - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000"), Account0); - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111"), Account1); - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb22222"), Account2); - tree.UpdateRootHash(); - Hash256 _ = tree.RootHash; - tree.Commit(); - }), - ("branch_read_missing", static (tree, stateDb, codeDb) => - { - codeDb[Keccak.Compute(Code0).Bytes] = Code0; - codeDb[Keccak.Compute(Code1).Bytes] = Code1; - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000"), Account0); - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111"), Account1); - Account _ = tree.Get(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb22222"))!; - tree.UpdateRootHash(); - Hash256 __ = tree.RootHash; - tree.Commit(); - }), - ("branch_delete_missing", static (tree, stateDb, codeDb) => - { - codeDb[Keccak.Compute(Code0).Bytes] = Code0; - codeDb[Keccak.Compute(Code1).Bytes] = Code1; - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000"), Account0); - SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111"), Account1); - tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb22222"), null); - tree.UpdateRootHash(); - Hash256 _ = tree.RootHash; - tree.Commit(); - }) - }; - } + codeDb[codeHash.Bytes] = code; + tree.Set(new Hash256("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), AccountJustState0.WithChangedCodeHash(codeHash)); + tree.Set(accountWithStorage, AccountJustState0.WithChangedStorageRoot(remoteStorageTree.RootHash).WithChangedCodeHash(codeHash)); + tree.Commit(); + }), + ("branch_with_same_accounts_at_different_addresses", static (tree, _, codeDb) => + { + codeDb[Keccak.Compute(Code0).Bytes] = Code0; + tree.Set(new Hash256("1baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), AccountJustState0); + tree.Set(new Hash256("2baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), AccountJustState0); + tree.Commit(); + }), + ("set_3_delete_1", static (tree, stateDb, codeDb) => + { + codeDb[Keccak.Compute(Code0).Bytes] = Code0; + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), Account0); + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb0"), Account0); + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb1"), Account0); + tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb1"), null); + tree.Commit(); + }), + ("set_3_delete_2", static (tree, stateDb, codeDb) => + { + codeDb[Keccak.Compute(Code0).Bytes] = Code0; + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), Account0); + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb0"), Account0); + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb1"), Account0); + tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb0"), null); + tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb1"), null); + tree.Commit(); + }), + ("set_3_delete_all", static (tree, _, _) => + { + // SetStorage(stateDb); + tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), Account0); + tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb0"), Account0); + tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb1"), Account0); + tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb0"), null); + tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb1eeeeeb1"), null); + tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), null); + tree.Commit(); + }), + ("extension_read_full_match", static (tree, stateDb, codeDb) => + { + codeDb[Keccak.Compute(Code0).Bytes] = Code0; + codeDb[Keccak.Compute(Code1).Bytes] = Code1; + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), Account0); + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111111"), Account1); + Account _ = tree.Get(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111111"))!; + tree.UpdateRootHash(); + Hash256 __ = tree.RootHash; + tree.Commit(); + }), + ("extension_read_missing", static (tree, stateDb, codeDb) => + { + codeDb[Keccak.Compute(Code0).Bytes] = Code0; + codeDb[Keccak.Compute(Code1).Bytes] = Code1; + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), Account0); + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111111"), Account1); + Account _ = tree.Get(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddd"))!; + tree.UpdateRootHash(); + Hash256 __ = tree.RootHash; + tree.Commit(); + }), + ("extension_new_branch", static (tree, stateDb, codeDb) => + { + codeDb[Keccak.Compute(Code0).Bytes] = Code0; + codeDb[Keccak.Compute(Code1).Bytes] = Code1; + codeDb[Keccak.Compute(Code2).Bytes] = Code2; + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), Account0); + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111111"), Account1); + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddd"), Account2); + tree.UpdateRootHash(); + Hash256 _ = tree.RootHash; + tree.Commit(); + }), + ("just_state", static (tree, _, _) => + { + tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), AccountJustState0); + tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111111"), AccountJustState1); + tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddd"), AccountJustState2); + tree.UpdateRootHash(); + Hash256 _ = tree.RootHash; + tree.Commit(); + }), + ("extension_delete_missing", static (tree, stateDb, codeDb) => + { + codeDb[Keccak.Compute(Code0).Bytes] = Code0; + codeDb[Keccak.Compute(Code1).Bytes] = Code1; + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), Account0); + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111111"), Account1); + tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddd"), null); + tree.UpdateRootHash(); + Hash256 _ = tree.RootHash; + tree.Commit(); + }), + ("extension_create_new_extension", static (tree, stateDb, codeDb) => + { + codeDb[Keccak.Compute(Code0).Bytes] = Code0; + codeDb[Keccak.Compute(Code1).Bytes] = Code1; + codeDb[Keccak.Compute(Code2).Bytes] = Code2; + codeDb[Keccak.Compute(Code3).Bytes] = Code3; + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), Account0); + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111111"), Account1); + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeaaaaaaaaaaaaaaaab00000000"), Account2); + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeaaaaaaaaaaaaaaaab11111111"), Account3); + tree.UpdateRootHash(); + Hash256 _ = tree.RootHash; + tree.Commit(); + }), + ("leaf_new_value", static (tree, stateDb, codeDb) => + { + codeDb[Keccak.Compute(Code1).Bytes] = Code1; + SetStorage(tree, stateDb, new Hash256("1111111111111111111111111111111111111111111111111111111111111111"), Account0); + SetStorage(tree, stateDb, new Hash256("1111111111111111111111111111111111111111111111111111111111111111"), Account1); + tree.UpdateRootHash(); + Hash256 _ = tree.RootHash; + tree.Commit(); + }), + ("leaf_no_change", static (tree, stateDb, codeDb) => + { + codeDb[Keccak.Compute(Code0).Bytes] = Code0; + SetStorage(tree, stateDb, new Hash256("1111111111111111111111111111111111111111111111111111111111111111"), Account0); + SetStorage(tree, stateDb, new Hash256("1111111111111111111111111111111111111111111111111111111111111111"), Account0); + tree.UpdateRootHash(); + Hash256 _ = tree.RootHash; + tree.Commit(); + }), + ("leaf_delete", static (tree, _, _) => + { + tree.Set(new Hash256("1111111111111111111111111111111111111111111111111111111111111111"), Account0); + tree.Set(new Hash256("1111111111111111111111111111111111111111111111111111111111111111"), null); + tree.UpdateRootHash(); + Hash256 _ = tree.RootHash; + tree.Commit(); + }), + ("leaf_delete_missing", static (tree, stateDb, codeDb) => + { + codeDb[Keccak.Compute(Code0).Bytes] = Code0; + SetStorage(tree, stateDb, new Hash256("1111111111111111111111111111111111111111111111111111111111111111"), Account0); + tree.Set(new Hash256("1111111111111111111111111111111ddddddddddddddddddddddddddddddddd"), null); + tree.UpdateRootHash(); + Hash256 _ = tree.RootHash; + tree.Commit(); + }), + ("leaf_update_extension", static (tree, stateDb, codeDb) => + { + codeDb[Keccak.Compute(Code0).Bytes] = Code0; + codeDb[Keccak.Compute(Code1).Bytes] = Code1; + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111111111111111111111111111111"), Account0); + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000000000000000000000000000"), Account1); + tree.UpdateRootHash(); + Hash256 _ = tree.RootHash; + tree.Commit(); + }), + ("leaf_read", static (tree, stateDb, codeDb) => + { + codeDb[Keccak.Compute(Code0).Bytes] = Code0; + SetStorage(tree, stateDb, new Hash256("1111111111111111111111111111111111111111111111111111111111111111"), Account0); + Account _ = tree.Get(new Hash256("1111111111111111111111111111111111111111111111111111111111111111"))!; + tree.UpdateRootHash(); + Hash256 __ = tree.RootHash; + tree.Commit(); + }), + ("leaf_update_missing", static (tree, stateDb, codeDb) => + { + codeDb[Keccak.Compute(Code0).Bytes] = Code0; + SetStorage(tree, stateDb, new Hash256("1111111111111111111111111111111111111111111111111111111111111111"), Account0); + Account _ = tree.Get(new Hash256("111111111111111111111111111111111111111111111111111111111ddddddd"))!; + tree.UpdateRootHash(); + Hash256 __ = tree.RootHash; + tree.Commit(); + }), + ("branch_update_missing", static (tree, stateDb, codeDb) => + { + codeDb[Keccak.Compute(Code0).Bytes] = Code0; + codeDb[Keccak.Compute(Code1).Bytes] = Code1; + codeDb[Keccak.Compute(Code2).Bytes] = Code2; + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000"), Account0); + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111"), Account1); + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb22222"), Account2); + tree.UpdateRootHash(); + Hash256 _ = tree.RootHash; + tree.Commit(); + }), + ("branch_read_missing", static (tree, stateDb, codeDb) => + { + codeDb[Keccak.Compute(Code0).Bytes] = Code0; + codeDb[Keccak.Compute(Code1).Bytes] = Code1; + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000"), Account0); + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111"), Account1); + Account _ = tree.Get(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb22222"))!; + tree.UpdateRootHash(); + Hash256 __ = tree.RootHash; + tree.Commit(); + }), + ("branch_delete_missing", static (tree, stateDb, codeDb) => + { + codeDb[Keccak.Compute(Code0).Bytes] = Code0; + codeDb[Keccak.Compute(Code1).Bytes] = Code1; + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000"), Account0); + SetStorage(tree, stateDb, new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111"), Account1); + tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb22222"), null); + tree.UpdateRootHash(); + Hash256 _ = tree.RootHash; + tree.Commit(); + }) + ]; private static StorageTree SetStorage(ITrieStore trieStore, Address account) => SetStorage(trieStore, account.ToAccountPath.ToCommitment()); diff --git a/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloader.cs b/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloader.cs index 07da3a289046..c799974963b5 100644 --- a/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloader.cs +++ b/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloader.cs @@ -16,7 +16,6 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Core.Extensions; -using Nethermind.Crypto; using Nethermind.Logging; using Nethermind.Serialization.Rlp; using Nethermind.State.Proofs; @@ -162,6 +161,8 @@ private void BlockTreeOnNewHeadBlock(object? sender, BlockEventArgs e) if (cancellation.IsCancellationRequested) return null; // check before every heavy operation if (headers is null || headers.Count <= 1) return null; + if (_logger.IsTrace) _logger.Trace($"Prepared request from block {headers[0].Number} to {headers[^1].Number}"); + if (previousStartingHeaderNumber == headers[0].Number) { // When the block is suggested right between a `NewPayload` and `ForkChoiceUpdatedHandler` the block is not added because it was added already @@ -214,7 +215,7 @@ private void BlockTreeOnNewHeadBlock(object? sender, BlockEventArgs e) if (!_blockValidator.ValidateSuggestedBlock(currentBlock, entry.ParentHeader, out string? errorMessage)) { PeerInfo peer = entry.PeerInfo; - if (_logger.IsWarn) _logger.Warn($"Invalid downloaded block from {peer}, {errorMessage}"); + if (_logger.IsDebug) _logger.Debug($"Invalid downloaded block from {peer}, {errorMessage}"); if (peer is not null) _syncPeerPool.ReportBreachOfProtocol(peer, DisconnectReason.ForwardSyncFailed, $"invalid block received: {errorMessage}. Block: {currentBlock.Header.ToString(BlockHeader.Format.Short)}"); entry.RetryBlockRequest(); @@ -381,7 +382,7 @@ public SyncResponseHandlingResult HandleResponse(BlocksRequest response, PeerInf response.OwnedBodies?.Disown(); SyncResponseHandlingResult result = SyncResponseHandlingResult.OK; - using ArrayPoolList blocks = new ArrayPoolList(response.BodiesRequests?.Count ?? 0); + using ArrayPoolListRef blocks = new(response.BodiesRequests?.Count ?? 0); int bodiesCount = 0; int receiptsCount = 0; @@ -408,7 +409,7 @@ public SyncResponseHandlingResult HandleResponse(BlocksRequest response, PeerInf if (!_blockValidator.ValidateBodyAgainstHeader(entry.Header, body, out string errorMessage)) { - if (_logger.IsWarn) _logger.Warn($"Invalid downloaded block from {peer}, {errorMessage}"); + if (_logger.IsDebug) _logger.Debug($"Invalid downloaded block from {peer}, {errorMessage}"); if (peer is not null) _syncPeerPool.ReportBreachOfProtocol(peer, DisconnectReason.ForwardSyncFailed, $"invalid block received: {errorMessage}. Block: {entry.Header.ToString(BlockHeader.Format.Short)}"); result = SyncResponseHandlingResult.LesserQuality; @@ -495,7 +496,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/BlocksSyncPeerSelectionStrategyFactory.cs b/src/Nethermind/Nethermind.Synchronization/Blocks/BlocksSyncPeerSelectionStrategyFactory.cs index 6961506f303a..84b104835d45 100644 --- a/src/Nethermind/Nethermind.Synchronization/Blocks/BlocksSyncPeerSelectionStrategyFactory.cs +++ b/src/Nethermind/Nethermind.Synchronization/Blocks/BlocksSyncPeerSelectionStrategyFactory.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Nethermind.Stats; using Nethermind.Synchronization.ParallelSync; using Nethermind.Synchronization.Peers.AllocationStrategies; diff --git a/src/Nethermind/Nethermind.Synchronization/Blocks/FastSyncFeed.cs b/src/Nethermind/Nethermind.Synchronization/Blocks/FastSyncFeed.cs index 3083bd734462..7830bbd158ab 100644 --- a/src/Nethermind/Nethermind.Synchronization/Blocks/FastSyncFeed.cs +++ b/src/Nethermind/Nethermind.Synchronization/Blocks/FastSyncFeed.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Threading; using System.Threading.Tasks; using Nethermind.Blockchain.Synchronization; diff --git a/src/Nethermind/Nethermind.Synchronization/Blocks/IForwardSyncController.cs b/src/Nethermind/Nethermind.Synchronization/Blocks/IForwardSyncController.cs index dcd05163d74d..a29679dfd534 100644 --- a/src/Nethermind/Nethermind.Synchronization/Blocks/IForwardSyncController.cs +++ b/src/Nethermind/Nethermind.Synchronization/Blocks/IForwardSyncController.cs @@ -13,7 +13,7 @@ namespace Nethermind.Synchronization.Blocks; /// public interface IForwardSyncController { - public Task PrepareRequest(DownloaderOptions buildOptions, int fastSyncLag, CancellationToken token); + public Task PrepareRequest(DownloaderOptions buildOptions, int fastSyncLag, CancellationToken token = default); public SyncResponseHandlingResult HandleResponse(BlocksRequest response, PeerInfo? peer); public int DownloadRequestBufferSize { get; } diff --git a/src/Nethermind/Nethermind.Synchronization/Blocks/MultiBlockDownloader.cs b/src/Nethermind/Nethermind.Synchronization/Blocks/MultiBlockDownloader.cs index 9acfa30d00eb..6230143f3649 100644 --- a/src/Nethermind/Nethermind.Synchronization/Blocks/MultiBlockDownloader.cs +++ b/src/Nethermind/Nethermind.Synchronization/Blocks/MultiBlockDownloader.cs @@ -16,23 +16,32 @@ public class MultiBlockDownloader : ISyncDownloader { public async Task Dispatch(PeerInfo peerInfo, BlocksRequest request, CancellationToken cancellationToken) { - request.DownloadTask = Task.Run(async () => + cancellationToken.ThrowIfCancellationRequested(); + if (request.BodiesRequests.Count == 0 && request.ReceiptsRequests.Count == 0) { - if (request.BodiesRequests.Count > 0) - { - using IOwnedReadOnlyList bodiesHash = request.BodiesRequests.Select(b => b.Hash) - .ToPooledList(request.BodiesRequests.Count); - request.OwnedBodies = await peerInfo.SyncPeer.GetBlockBodies(bodiesHash, cancellationToken); - } + request.DownloadTask = Task.CompletedTask; + return; + } - if (request.ReceiptsRequests.Count > 0) - { - using IOwnedReadOnlyList receiptsHash = request.ReceiptsRequests.Select(b => b.Hash) - .ToPooledList(request.ReceiptsRequests.Count); - var ownedReceipts = await peerInfo.SyncPeer.GetReceipts(receiptsHash, cancellationToken); - request.Receipts = ownedReceipts; - } - }, cancellationToken); + request.DownloadTask = DownloadAsync(peerInfo, request, cancellationToken); await request.DownloadTask; } + + private static async Task DownloadAsync(PeerInfo peerInfo, BlocksRequest request, CancellationToken cancellationToken) + { + if (request.BodiesRequests.Count > 0) + { + using IOwnedReadOnlyList bodiesHash = request.BodiesRequests.Select(b => b.Hash) + .ToPooledList(request.BodiesRequests.Count); + request.OwnedBodies = await peerInfo.SyncPeer.GetBlockBodies(bodiesHash, cancellationToken); + } + + if (request.ReceiptsRequests.Count > 0) + { + using IOwnedReadOnlyList receiptsHash = request.ReceiptsRequests.Select(b => b.Hash) + .ToPooledList(request.ReceiptsRequests.Count); + var ownedReceipts = await peerInfo.SyncPeer.GetReceipts(receiptsHash, cancellationToken); + request.Receipts = ownedReceipts; + } + } } diff --git a/src/Nethermind/Nethermind.Synchronization/Blocks/PowForwardHeaderProvider.cs b/src/Nethermind/Nethermind.Synchronization/Blocks/PowForwardHeaderProvider.cs index 640252a7f686..61f63f98d0a8 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; } @@ -116,7 +116,7 @@ private IOwnedReadOnlyList? LastResponseBatch } LastResponseBatch = newResponse; - return LastResponseBatch; + return newResponse.ToPooledList(newResponse.Count); } private void OnNewBestPeer(PeerInfo newBestPeer) @@ -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; } @@ -175,6 +175,12 @@ private void OnNewBestPeer(PeerInfo newBestPeer) syncPeerPool.ReportWeakPeer(bestPeer, AllocationContexts.ForwardHeader); return null; } + catch (OperationCanceledException) when (!cancellation.IsCancellationRequested) + { + // Request was cancelled due to timeout in protocol handler, not because the sync was cancelled + syncPeerPool.ReportWeakPeer(bestPeer, AllocationContexts.ForwardHeader); + return null; + } catch (EthSyncException e) { if (_logger.IsDebug) _logger.Debug($"Failed to download forward header from {bestPeer}, {e}"); diff --git a/src/Nethermind/Nethermind.Synchronization/DbTuner/SyncDbOptimizer.cs b/src/Nethermind/Nethermind.Synchronization/DbTuner/SyncDbOptimizer.cs index bb5e64e60594..6f5d1625f328 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 ec74a91c7180..36b0379c777f 100644 --- a/src/Nethermind/Nethermind.Synchronization/FastBlocks/FastHeadersSyncFeed.cs +++ b/src/Nethermind/Nethermind.Synchronization/FastBlocks/FastHeadersSyncFeed.cs @@ -18,7 +18,6 @@ using Nethermind.Core.Extensions; using Nethermind.Int256; using Nethermind.Logging; -using Nethermind.Serialization.Json; using Nethermind.Stats; using Nethermind.Stats.Model; using Nethermind.Synchronization.ParallelSync; @@ -225,7 +224,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 a1e6a7e27668..c8b87ae733ea 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/ITreeSync.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/ITreeSync.cs index 209955595770..5461785c74b3 100644 --- a/src/Nethermind/Nethermind.Synchronization/FastSync/ITreeSync.cs +++ b/src/Nethermind/Nethermind.Synchronization/FastSync/ITreeSync.cs @@ -3,7 +3,6 @@ using System; using Nethermind.Core; -using Nethermind.Core.Crypto; namespace Nethermind.Synchronization.FastSync; diff --git a/src/Nethermind/Nethermind.Synchronization/FastSync/ITreeSyncStore.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/ITreeSyncStore.cs new file mode 100644 index 000000000000..67d781e3778f --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/FastSync/ITreeSyncStore.cs @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Trie; + +namespace Nethermind.Synchronization.FastSync; + +/// +/// High-level storage interface for TreeSync that abstracts both storage operations +/// and verification operations. Allows different backends (Patricia, Flat) to provide +/// completely different implementations. +/// +public interface ITreeSyncStore +{ + /// + /// Check if a trie node exists in storage. + /// + bool NodeExists(Hash256? address, in TreePath path, in ValueHash256 hash); + + /// + /// Save a trie node to storage. + /// + /// Storage address for storage tries, null for state trie. + /// The path to this node in the trie. + /// The hash of the node data. + /// The RLP-encoded node data. + void SaveNode(Hash256? address, in TreePath path, in ValueHash256 hash, ReadOnlySpan data); + + /// + /// Called when sync is complete and state should be finalized and flushed. + /// + /// The block header containing the synced state root. + void FinalizeSync(BlockHeader pivotHeader); + + /// + /// Create a verification context for checking storage roots during sync. + /// The context is created with root node data that hasn't been persisted yet. + /// + ITreeSyncVerificationContext CreateVerificationContext(byte[] rootNodeData); +} + +/// +/// Context for verifying storage roots during sync. +/// Allows querying accounts from in-flight (not yet persisted) trie data. +/// +public interface ITreeSyncVerificationContext +{ + /// + /// Get an account by its address hash for verification purposes. + /// + Account? GetAccount(Hash256 addressHash); +} diff --git a/src/Nethermind/Nethermind.Synchronization/FastSync/PatriciaTreeSyncStore.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/PatriciaTreeSyncStore.cs new file mode 100644 index 000000000000..6d65641576bc --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/FastSync/PatriciaTreeSyncStore.cs @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Logging; +using Nethermind.State; +using Nethermind.Trie; +using Nethermind.Trie.Pruning; + +namespace Nethermind.Synchronization.FastSync; + +public class PatriciaTreeSyncStore(INodeStorage nodeStorage, ILogManager logManager) : ITreeSyncStore +{ + public bool NodeExists(Hash256? address, in TreePath path, in ValueHash256 hash) => + nodeStorage.KeyExists(address, path, hash); + + public void SaveNode(Hash256? address, in TreePath path, in ValueHash256 hash, ReadOnlySpan data) => + nodeStorage.Set(address, path, hash, data); + + public void FinalizeSync(BlockHeader pivotHeader) => + // Patricia trie doesn't need block header info, just flush + nodeStorage.Flush(onlyWal: false); + + public ITreeSyncVerificationContext CreateVerificationContext(byte[] rootNodeData) => + new PatriciaVerificationContext(nodeStorage, rootNodeData, logManager); + + private class PatriciaVerificationContext( + INodeStorage nodeStorage, + byte[] rootNodeData, + ILogManager logManager) : ITreeSyncVerificationContext + { + private readonly StateTree _stateTree = CreateStateTree(nodeStorage, rootNodeData, logManager); + + private static StateTree CreateStateTree(INodeStorage nodeStorage, byte[] rootNodeData, ILogManager logManager) + { + StateTree stateTree = new(new RawScopedTrieStore(nodeStorage, null), logManager); + stateTree.RootRef = new TrieNode(NodeType.Unknown, rootNodeData); + return stateTree; + } + + public Account? GetAccount(Hash256 addressHash) => + _stateTree.Get(addressHash); + } +} diff --git a/src/Nethermind/Nethermind.Synchronization/FastSync/PendingSyncItems.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/PendingSyncItems.cs index 6704499fe158..da1923c80e7a 100644 --- a/src/Nethermind/Nethermind.Synchronization/FastSync/PendingSyncItems.cs +++ b/src/Nethermind/Nethermind.Synchronization/FastSync/PendingSyncItems.cs @@ -40,16 +40,19 @@ internal class PendingSyncItems : IPendingSyncItems private decimal _lastSyncProgress; private uint _maxStorageRightness; // for priority calculation (prefer left) private uint _maxRightness; // for priority calculation (prefer left) + private readonly bool _isSnapSync; public byte MaxStorageLevel { get; set; } public byte MaxStateLevel { get; set; } - public PendingSyncItems() + public PendingSyncItems(bool isSnapSync) { for (int i = 0; i < _allStacks.Length; i++) { _allStacks[i] = new ConcurrentStack(); } + + _isSnapSync = isSnapSync; } public void PushToSelectedStream(StateSyncItem stateSyncItem, decimal progress) @@ -158,10 +161,23 @@ private bool TryTake(out StateSyncItem? node) public List TakeBatch(int maxSize) { - // the limitation is to prevent an early explosion of request sizes with low level nodes - // the moment we find the first leaf we will know something more about the tree structure and hence - // prevent lot of Stream2 entries to stay in memory for a long time - int length = MaxStateLevel == 64 ? maxSize : Math.Max(1, (int)(maxSize * ((decimal)MaxStateLevel / 64) * ((decimal)MaxStateLevel / 64))); + int length; + + if (!_isSnapSync) + { + // the limitation is to prevent an early explosion of request sizes with low level nodes + // the moment we find the first leaf we will know something more about the tree structure and hence + // prevent lot of Stream2 entries to stay in memory for a long time + length = MaxStateLevel == 64 + ? maxSize + : Math.Max(1, (int)(maxSize * ((decimal)MaxStateLevel / 64) * ((decimal)MaxStateLevel / 64))); + } + else + { + // With snap sync, we want the top-level nodes. Low-level nodes are mostly snap-synced. + length = maxSize; + } + List requestItems = new(length); // Codes have priority over State Nodes diff --git a/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncItem.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncItem.cs index 26dddeabc476..97c558ddec06 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/FastSync/StateSyncPivot.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncPivot.cs index 338ed46cc9bc..16e8dcc9db3c 100644 --- a/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncPivot.cs +++ b/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncPivot.cs @@ -2,14 +2,12 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; using ConcurrentCollections; using Nethermind.Blockchain; using Nethermind.Blockchain.Synchronization; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Logging; -using Nethermind.Synchronization.ParallelSync; namespace Nethermind.Synchronization.FastSync { diff --git a/src/Nethermind/Nethermind.Synchronization/FastSync/TreeSync.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/TreeSync.cs index 161ec86f54da..f8cc34a28df2 100644 --- a/src/Nethermind/Nethermind.Synchronization/FastSync/TreeSync.cs +++ b/src/Nethermind/Nethermind.Synchronization/FastSync/TreeSync.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Autofac.Features.AttributeFilters; using Nethermind.Blockchain; +using Nethermind.Blockchain.Synchronization; using Nethermind.Core; using Nethermind.Core.Caching; using Nethermind.Core.Crypto; @@ -17,7 +18,6 @@ using Nethermind.Db; using Nethermind.Logging; using Nethermind.Serialization.Rlp; -using Nethermind.State; using Nethermind.Synchronization.ParallelSync; using Nethermind.Synchronization.Peers; using Nethermind.Trie; @@ -58,7 +58,7 @@ public class TreeSync : ITreeSync private readonly ILogger _logger; private readonly IDb _codeDb; - private readonly INodeStorage _nodeStorage; + private readonly ITreeSyncStore _store; private readonly IBlockTree _blockTree; private readonly StateSyncPivot _stateSyncPivot; @@ -70,6 +70,8 @@ public class TreeSync : ITreeSync private Dictionary> _dependencies = new(); private readonly LruKeyCache _alreadySavedNode = new(AlreadySavedCapacity, "saved nodes"); private readonly LruKeyCache _alreadySavedCode = new(AlreadySavedCapacity, "saved nodes"); + private ConcurrentDictionary _previouslyPendingItems = new(); + private ConcurrentDictionary _newPendingItems = new(); private BranchProgress _branchProgress; private int _hintsToResetRoot; @@ -78,11 +80,11 @@ public class TreeSync : ITreeSync public event EventHandler? SyncCompleted; - public TreeSync([KeyFilter(DbNames.Code)] IDb codeDb, INodeStorage nodeStorage, IBlockTree blockTree, StateSyncPivot stateSyncPivot, ILogManager logManager) + public TreeSync([KeyFilter(DbNames.Code)] IDb codeDb, ITreeSyncStore store, IBlockTree blockTree, StateSyncPivot stateSyncPivot, ISyncConfig syncConfig, ILogManager logManager) { _syncMode = SyncMode.StateNodes; _codeDb = codeDb ?? throw new ArgumentNullException(nameof(codeDb)); - _nodeStorage = nodeStorage ?? throw new ArgumentNullException(nameof(nodeStorage)); + _store = store ?? throw new ArgumentNullException(nameof(store)); _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); _stateSyncPivot = stateSyncPivot; @@ -90,7 +92,7 @@ public TreeSync([KeyFilter(DbNames.Code)] IDb codeDb, INodeStorage nodeStorage, byte[] progress = _codeDb.Get(_fastSyncProgressKey); _data = new DetailedProgress(_blockTree.NetworkId, progress); - _pendingItems = new PendingSyncItems(); + _pendingItems = new PendingSyncItems(syncConfig.SnapSync); _branchProgress = new BranchProgress(0, _logger); } @@ -370,7 +372,7 @@ shorter than the request */ try { // it finished downloading - rootNodeKeyExists = _nodeStorage.KeyExists(null, TreePath.Empty, _rootNode); + rootNodeKeyExists = _store.NodeExists(null, TreePath.Empty, _rootNode); } catch (ObjectDisposedException) { @@ -448,7 +450,13 @@ private void ResetStateRoot(long blockNumber, Hash256 stateRoot, SyncFeedState c _branchProgress = new BranchProgress(blockNumber, _logger); _blockNumber = blockNumber; _rootNode = stateRoot; - lock (_dependencies) _dependencies.Clear(); + lock (_dependencies) + { + // Swap the pending items + _previouslyPendingItems.Clear(); + (_newPendingItems, _previouslyPendingItems) = (_previouslyPendingItems, _newPendingItems); + _dependencies.Clear(); + } if (_logger.IsDebug) _logger.Debug($"Clearing node stacks ({_pendingItems.Description})"); _pendingItems.Clear(); @@ -501,9 +509,9 @@ public DetailedProgress GetDetailedProgress() return _data; } - private AddNodeResult AddNodeToPending(StateSyncItem syncItem, DependentItem? dependentItem, string reason, bool missing = false) + private AddNodeResult AddNodeToPending(StateSyncItem syncItem, DependentItem? dependentItem, string reason, bool retry = false) { - if (!missing) + if (!retry) { if (syncItem.Level <= 2) { @@ -538,7 +546,7 @@ private AddNodeResult AddNodeToPending(StateSyncItem syncItem, DependentItem? de } else { - keyExists = _nodeStorage.KeyExists(syncItem.Address, syncItem.Path, syncItem.Hash); + keyExists = _store.NodeExists(syncItem.Address, syncItem.Path, syncItem.Hash); } if (keyExists) @@ -588,6 +596,15 @@ private AddNodeResult AddNodeToPending(StateSyncItem syncItem, DependentItem? de } } + if (_previouslyPendingItems.TryRemove(syncItem.Key, out var responseBytes)) + { + if (_logger.IsTrace) _logger.Trace($"Using cache for key {syncItem.Key}"); + int invalidNodes = 0; + HandleTrieNode(syncItem, responseBytes, ref invalidNodes); + Debug.Assert(invalidNodes == 0); + return AddNodeResult.AlreadyRequested; + } + _pendingItems.PushToSelectedStream(syncItem, _branchProgress.LastProgress); if (_logger.IsTrace) _logger.Trace($"Added a node {syncItem.Hash} - {reason}"); return AddNodeResult.Added; @@ -635,6 +652,7 @@ private void PossiblySaveDependentNodes(StateSyncItem.NodeKey key) private void SaveNode(StateSyncItem syncItem, byte[] data) { + _newPendingItems.TryRemove(syncItem.Key, out var _); if (syncItem.IsRoot) { if (!VerifyStorageUpdated(syncItem, data)) @@ -658,7 +676,7 @@ private void SaveNode(StateSyncItem syncItem, byte[] data) Interlocked.Add(ref _data.DataSize, data.Length); Interlocked.Increment(ref Metrics.SyncedStateTrieNodes); - _nodeStorage.Set(syncItem.Address, syncItem.Path, syncItem.Hash, data); + _store.SaveNode(syncItem.Address, syncItem.Path, syncItem.Hash, data); } finally { @@ -676,7 +694,7 @@ private void SaveNode(StateSyncItem syncItem, byte[] data) { Interlocked.Add(ref _data.DataSize, data.Length); Interlocked.Increment(ref Metrics.SyncedStorageTrieNodes); - _nodeStorage.Set(syncItem.Address, syncItem.Path, syncItem.Hash, data); + _store.SaveNode(syncItem.Address, syncItem.Path, syncItem.Hash, data); } finally { @@ -708,7 +726,10 @@ private void SaveNode(StateSyncItem syncItem, byte[] data) { if (_logger.IsInfo) _logger.Info($"Saving root {syncItem.Hash} of {_branchProgress.CurrentSyncBlock}"); - _nodeStorage.Flush(onlyWal: false); + if (_stateSyncPivot.GetPivotHeader() is { } pivotHeader) + { + _store.FinalizeSync(pivotHeader); + } _codeDb.Flush(); Interlocked.Exchange(ref _rootSaved, 1); @@ -722,16 +743,13 @@ private bool VerifyStorageUpdated(StateSyncItem item, byte[] value) { DependentItem dependentItem = new DependentItem(item, value, _stateSyncPivot.UpdatedStorages.Count); - // Need complete state tree as the correct storage root may be different at this point. - StateTree stateTree = new StateTree(new RawScopedTrieStore(_nodeStorage, null), LimboLogs.Instance); - // The root is not persisted at this point yet, so we set it as root ref here. - stateTree.RootRef = new TrieNode(NodeType.Unknown, value); + ITreeSyncVerificationContext verificationContext = _store.CreateVerificationContext(value); if (_logger.IsDebug) _logger.Debug($"Checking {_stateSyncPivot.UpdatedStorages.Count} updated storages"); foreach (Hash256 updatedAddress in _stateSyncPivot.UpdatedStorages) { - Account? account = stateTree.Get(updatedAddress); + Account? account = verificationContext.GetAccount(updatedAddress); if (account?.StorageRoot is not null && AddNodeToPending(new StateSyncItem(account.StorageRoot, updatedAddress, TreePath.Empty, NodeDataType.Storage), dependentItem, "incomplete storage") == AddNodeResult.Added) @@ -740,6 +758,7 @@ private bool VerifyStorageUpdated(StateSyncItem item, byte[] value) } else { + if (_logger.IsDebug) _logger.Debug($"Storage {updatedAddress} is ok"); dependentItem.Counter--; } } @@ -790,6 +809,11 @@ private void CleanupMemory() _dependencies.Clear(); _alreadySavedNode.Clear(); _alreadySavedCode.Clear(); + if (_rootSaved == 1) + { + _previouslyPendingItems.Clear(); + _newPendingItems.Clear(); + } } finally { @@ -876,6 +900,10 @@ private void HandleTrieNode(StateSyncItem currentStateSyncItem, byte[] currentRe { SaveNode(currentStateSyncItem, currentResponseItem); } + else + { + _newPendingItems.TryAdd(currentStateSyncItem.Key, currentResponseItem); + } break; case NodeType.Extension: @@ -903,6 +931,10 @@ private void HandleTrieNode(StateSyncItem currentStateSyncItem, byte[] currentRe { SaveNode(currentStateSyncItem, currentResponseItem); } + else + { + _newPendingItems.TryAdd(currentStateSyncItem.Key, currentResponseItem); + } } else { @@ -954,6 +986,10 @@ private void HandleTrieNode(StateSyncItem currentStateSyncItem, byte[] currentRe Interlocked.Increment(ref _data.SavedAccounts); SaveNode(currentStateSyncItem, currentResponseItem); } + else + { + _newPendingItems.TryAdd(currentStateSyncItem.Key, currentResponseItem); + } } else { diff --git a/src/Nethermind/Nethermind.Synchronization/IPivot.cs b/src/Nethermind/Nethermind.Synchronization/IPivot.cs index 2243704016da..ce8902976365 100644 --- a/src/Nethermind/Nethermind.Synchronization/IPivot.cs +++ b/src/Nethermind/Nethermind.Synchronization/IPivot.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core.Crypto; -using Nethermind.Int256; namespace Nethermind.Synchronization { diff --git a/src/Nethermind/Nethermind.Synchronization/ISyncServer.cs b/src/Nethermind/Nethermind.Synchronization/ISyncServer.cs index 3ee241c79eac..72f5d6295fea 100644 --- a/src/Nethermind/Nethermind.Synchronization/ISyncServer.cs +++ b/src/Nethermind/Nethermind.Synchronization/ISyncServer.cs @@ -26,5 +26,6 @@ public interface ISyncServer : IDisposable ulong NetworkId { get; } BlockHeader Genesis { get; } BlockHeader? Head { get; } + long LowestBlock { get; } } } diff --git a/src/Nethermind/Nethermind.Synchronization/Metrics.cs b/src/Nethermind/Nethermind.Synchronization/Metrics.cs index 6eb17051077f..c383389c8246 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/ActivatedSyncFeed.cs b/src/Nethermind/Nethermind.Synchronization/ParallelSync/ActivatedSyncFeed.cs index 39b2e66d3005..936a140f84e7 100644 --- a/src/Nethermind/Nethermind.Synchronization/ParallelSync/ActivatedSyncFeed.cs +++ b/src/Nethermind/Nethermind.Synchronization/ParallelSync/ActivatedSyncFeed.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Threading.Tasks; namespace Nethermind.Synchronization.ParallelSync { diff --git a/src/Nethermind/Nethermind.Synchronization/ParallelSync/FullStateFinder.cs b/src/Nethermind/Nethermind.Synchronization/ParallelSync/FullStateFinder.cs index 94edf0922891..4e0e3be80cf4 100644 --- a/src/Nethermind/Nethermind.Synchronization/ParallelSync/FullStateFinder.cs +++ b/src/Nethermind/Nethermind.Synchronization/ParallelSync/FullStateFinder.cs @@ -3,6 +3,7 @@ using System; using Nethermind.Blockchain; +using Nethermind.Blockchain.Find; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.State; @@ -17,6 +18,7 @@ public class FullStateFinder : IFullStateFinder private const int MaxLookupBack = 128; private readonly IStateReader _stateReader; private readonly IBlockTree _blockTree; + private long _lastKnownState = 0; public FullStateFinder( IBlockTree blockTree, @@ -65,13 +67,24 @@ public long FindBestFullState() } } + if (bestFullState != 0) + { + _lastKnownState = bestFullState; + } + return bestFullState; } private long SearchForFullState(BlockHeader startHeader) { long bestFullState = 0; - for (int i = 0; i < MaxLookupBack; i++) + long maxLookupBack = MaxLookupBack; + if (_lastKnownState != 0) + { + maxLookupBack = long.Max(maxLookupBack, startHeader.Number - _lastKnownState + 1); + } + + for (int i = 0; i < maxLookupBack; i++) { if (startHeader is null) { @@ -84,7 +97,7 @@ private long SearchForFullState(BlockHeader startHeader) break; } - startHeader = _blockTree.FindHeader(startHeader.ParentHash!, BlockTreeLookupOptions.TotalDifficultyNotNeeded); + startHeader = _blockTree.FindParentHeader(startHeader!, BlockTreeLookupOptions.TotalDifficultyNotNeeded); } return bestFullState; diff --git a/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs b/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs index 6c5683ef153b..3eb5e9e616aa 100644 --- a/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs +++ b/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs @@ -201,6 +201,7 @@ public void Update() best.IsInFullSync = ShouldBeInFullSyncMode(best); best.IsInDisconnected = ShouldBeInDisconnectedMode(best); best.IsInWaitingForBlock = ShouldBeInWaitingForBlockMode(best); + if (_logger.IsTrace) _logger.Trace($"Snapshot: {BuildStateStringDebug(best)}"); newModes = SyncMode.None; CheckAddFlag(best.IsInUpdatingPivot, SyncMode.UpdatingPivot, ref newModes); @@ -283,7 +284,7 @@ private void UpdateSyncModes(SyncMode newModes, string? reason = null) /// Snapshot of the best known states /// A string describing the state of sync private static string BuildStateString(Snapshot best) => - $"pivot: {best.PivotNumber} | header: {best.Header} | header: {best.Header} | target: {best.TargetBlock} | peer: {best.Peer.Block} | state: {best.State}"; + $"pivot: {best.PivotNumber} | header: {best.Header} | target: {best.TargetBlock} | peer: {best.Peer.Block} | state: {best.State}"; private static string BuildStateStringDebug(Snapshot best) => $"processed: {best.Processed} | state: {best.State} | block: {best.Block} | header: {best.Header} | chain difficulty: {best.ChainDifficulty} | target block: {best.TargetBlock} | peer block: {best.Peer.Block} | peer total difficulty: {best.Peer.TotalDifficulty}"; @@ -392,7 +393,7 @@ private bool ShouldBeInFastSyncMode(Snapshot best) // We stop `FastSyncLag` block before the highest known block in case the highest known block is non-canon // and we need to sync away from it. - // Note: its ok if target block height is not accurate as long as long full sync downloader does not stop + // Note: its ok if target block height is not accurate as long as full sync downloader does not stop // earlier than this condition below which would cause a hang. bool notReachedFullSyncTransition = best.Header < best.TargetBlock - TotalSyncLag; @@ -420,8 +421,7 @@ private bool ShouldBeInFastSyncMode(Snapshot best) (nameof(postPivotPeerAvailable), postPivotPeerAvailable), (nameof(notReachedFullSyncTransition), notReachedFullSyncTransition), (nameof(notInAStickyFullSync), notInAStickyFullSync), - (nameof(stateNotDownloadedYet), stateNotDownloadedYet), - (nameof(longRangeCatchUp), longRangeCatchUp), + ($"{nameof(stateNotDownloadedYet)} || ${longRangeCatchUp}", stateNotDownloadedYet || longRangeCatchUp), (nameof(notNeedToWaitForHeaders), notNeedToWaitForHeaders)); } @@ -607,9 +607,8 @@ private bool ShouldBeInStateSyncMode(Snapshot best) (nameof(notInUpdatingPivot), notInUpdatingPivot), (nameof(hasFastSyncBeenActive), hasFastSyncBeenActive), (nameof(hasAnyPostPivotPeer), hasAnyPostPivotPeer), - ($"{nameof(notInFastSync)}||{nameof(stickyStateNodes)}", notInFastSync || stickyStateNodes), - (nameof(stateNotDownloadedYet), stateNotDownloadedYet), - (nameof(longRangeCatchUp), longRangeCatchUp), + ($"{nameof(notInFastSync)} || {nameof(stickyStateNodes)}", notInFastSync || stickyStateNodes), + ($"{nameof(stateNotDownloadedYet)} || {nameof(longRangeCatchUp)}", stateNotDownloadedYet || longRangeCatchUp), (nameof(notInAStickyFullSync), notInAStickyFullSync), (nameof(notNeedToWaitForHeaders), notNeedToWaitForHeaders)); } @@ -629,7 +628,7 @@ private bool ShouldBeInStateNodesMode(Snapshot best) { LogDetailedSyncModeChecks("STATE_NODES", (nameof(isInStateSync), isInStateSync), - ($"{nameof(snapSyncDisabled)}||{nameof(snapRangesFinished)}", snapSyncDisabled || snapRangesFinished)); + ($"{nameof(snapSyncDisabled)} || {nameof(snapRangesFinished)}", snapSyncDisabled || snapRangesFinished)); } return result; @@ -762,15 +761,15 @@ private void LogDetailedSyncModeChecks(string syncType, params (string Name, boo List matched = new(); List failed = new(); - foreach ((string Name, bool IsSatisfied) in checks) + foreach ((string name, bool isSatisfied) in checks) { - if (IsSatisfied) + if (isSatisfied) { - matched.Add(Name); + matched.Add(name); } else { - failed.Add(Name); + failed.Add(name); } } @@ -847,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 a62c8a3f699b..9770595b696c 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/ParallelSync/SyncProgressResolver.cs b/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs index 0f2c9cd0b1a2..1eaf7d0d9cd1 100644 --- a/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs +++ b/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs @@ -8,7 +8,6 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Int256; -using Nethermind.Logging; using Nethermind.Synchronization.FastBlocks; using Nethermind.Synchronization.SnapSync; @@ -21,9 +20,6 @@ public class SyncProgressResolver : ISyncProgressResolver private readonly ISyncConfig _syncConfig; private readonly IFullStateFinder _fullStateFinder; - // ReSharper disable once NotAccessedField.Local - private readonly ILogger _logger; - private readonly ISyncFeed _headersSyncFeed; private readonly ISyncFeed _bodiesSyncFeed; private readonly ISyncFeed _receiptsSyncFeed; @@ -36,10 +32,8 @@ public SyncProgressResolver( [KeyFilter(nameof(HeadersSyncFeed))] ISyncFeed headersSyncFeed, ISyncFeed bodiesSyncFeed, ISyncFeed receiptsSyncFeed, - ISyncFeed snapSyncFeed, - ILogManager logManager) + ISyncFeed snapSyncFeed) { - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); _fullStateFinder = fullStateFinder ?? throw new ArgumentNullException(nameof(fullStateFinder)); _syncConfig = syncConfig ?? throw new ArgumentNullException(nameof(syncConfig)); diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/ClientTypeStrategy.cs b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/ClientTypeStrategy.cs deleted file mode 100644 index 142c2ff819c8..000000000000 --- a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/ClientTypeStrategy.cs +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using System.Linq; -using Nethermind.Blockchain; -using Nethermind.Stats; -using Nethermind.Stats.Model; - -namespace Nethermind.Synchronization.Peers.AllocationStrategies; - -public class ClientTypeStrategy : IPeerAllocationStrategy -{ - private readonly IPeerAllocationStrategy _strategy; - private readonly bool _allowOtherIfNone; - private readonly HashSet _supportedClientTypes; - - public ClientTypeStrategy(IPeerAllocationStrategy strategy, bool allowOtherIfNone, params NodeClientType[] supportedClientTypes) - : this(strategy, allowOtherIfNone, (IEnumerable)supportedClientTypes) - { - } - - public ClientTypeStrategy(IPeerAllocationStrategy strategy, bool allowOtherIfNone, IEnumerable supportedClientTypes) - { - _strategy = strategy; - _allowOtherIfNone = allowOtherIfNone; - _supportedClientTypes = new HashSet(supportedClientTypes); - } - - public PeerInfo? Allocate(PeerInfo? currentPeer, IEnumerable peers, INodeStatsManager nodeStatsManager, IBlockTree blockTree) - { - IEnumerable originalPeers = peers; - peers = peers.Where(p => _supportedClientTypes.Contains(p.PeerClientType)); - - if (_allowOtherIfNone) - { - if (!peers.Any()) - { - peers = originalPeers; - } - } - return _strategy.Allocate(currentPeer, peers, nodeStatsManager, blockTree); - } -} diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/NullStrategy.cs b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/NullStrategy.cs deleted file mode 100644 index fcf28b1a4a45..000000000000 --- a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/NullStrategy.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Nethermind.Blockchain; -using Nethermind.Stats; - -namespace Nethermind.Synchronization.Peers.AllocationStrategies -{ - /// - /// used only for failed allocations - /// - public class NullStrategy : IPeerAllocationStrategy - { - private NullStrategy() - { - } - - public static IPeerAllocationStrategy Instance { get; } = new NullStrategy(); - - public PeerInfo? Allocate(PeerInfo? currentPeer, IEnumerable peers, INodeStatsManager nodeStatsManager, IBlockTree blockTree) - { - return null; - } - } -} diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StaticStrategy.cs b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StaticStrategy.cs deleted file mode 100644 index 7d9a5b4de624..000000000000 --- a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StaticStrategy.cs +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Nethermind.Blockchain; -using Nethermind.Stats; - -namespace Nethermind.Synchronization.Peers.AllocationStrategies -{ - public class StaticStrategy : IPeerAllocationStrategy - { - private readonly PeerInfo _peerInfo; - - public StaticStrategy(PeerInfo peerInfo) - { - _peerInfo = peerInfo; - } - - public PeerInfo Allocate(PeerInfo? currentPeer, IEnumerable peers, INodeStatsManager nodeStatsManager, IBlockTree blockTree) - { - return _peerInfo; - } - } -} diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StrategySelectionType.cs b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StrategySelectionType.cs deleted file mode 100644 index c20046181c27..000000000000 --- a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StrategySelectionType.cs +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Synchronization.Peers.AllocationStrategies; - -public enum StrategySelectionType -{ - Better = 1, - AtLeastTheSame = 0, - CanBeSlightlyWorse = -1 -} diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/ISyncPeerPool.cs b/src/Nethermind/Nethermind.Synchronization/Peers/ISyncPeerPool.cs index 596bd8800183..8a36f71fed29 100644 --- a/src/Nethermind/Nethermind.Synchronization/Peers/ISyncPeerPool.cs +++ b/src/Nethermind/Nethermind.Synchronization/Peers/ISyncPeerPool.cs @@ -11,7 +11,6 @@ using Nethermind.Core.Crypto; using Nethermind.Stats; using Nethermind.Stats.Model; -using Nethermind.Synchronization.FastBlocks; using Nethermind.Synchronization.Peers.AllocationStrategies; namespace Nethermind.Synchronization.Peers diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeerAllocation.cs b/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeerAllocation.cs index 8e1801dc20b2..8ff8678650a3 100644 --- a/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeerAllocation.cs +++ b/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeerAllocation.cs @@ -1,13 +1,8 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; -using Nethermind.Blockchain; -using Nethermind.Stats; -using Nethermind.Synchronization.Peers.AllocationStrategies; namespace Nethermind.Synchronization.Peers { diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeerExtensions.cs b/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeerExtensions.cs index e477c1b0b5ad..7dabd1b0680b 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 25d3d60e5d30..413104c075ad 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; @@ -57,10 +58,11 @@ public class SyncPeerPool : ISyncPeerPool, IPeerDifficultyRefreshPool private readonly CancellationTokenSource _refreshLoopCancellation = new(); private Task? _refreshLoopTask; - private readonly ManualResetEvent _signals = new(true); + private volatile TaskCompletionSource _signal = new(TaskCreationOptions.RunContinuationsAsynchronously); private readonly TimeSpan _timeBeforeWakingShallowSleepingPeerUp = TimeSpan.FromMilliseconds(DefaultUpgradeIntervalInMs); private Timer? _upgradeTimer; + [UseConstructorForDependencyInjection] public SyncPeerPool(IBlockTree blockTree, INodeStatsManager nodeStatsManager, IBetterPeerStrategy betterPeerStrategy, @@ -331,6 +333,10 @@ public async Task Allocate( SyncPeerAllocation allocation = new(allocationContexts, _isAllocatedChecks); while (true) { + // Snapshot the signal task before attempting allocation so that any + // signal fired during or after TryAllocateOnce is already on this task. + Task signal = _signal.Task; + if (TryAllocateOnce(peerAllocationStrategy, allocationContexts, allocation)) { return allocation; @@ -345,23 +351,13 @@ public async Task Allocate( int waitTime = 10 * tryCount++; waitTime = Math.Min(waitTime, timeoutMilliseconds - (int)elapsedMilliseconds); - if (waitTime > 0 && !_signals.SafeWaitHandle.IsClosed) + if (waitTime > 0) { - try - { - await _signals.WaitOneAsync(waitTime, cts.Token); - } - catch (OperationCanceledException) + await Task.WhenAny(signal, Task.Delay(waitTime, cts.Token)); + if (cts.IsCancellationRequested) { return SyncPeerAllocation.FailedAllocation; } - finally - { - if (!_signals.SafeWaitHandle.IsClosed) - { - _signals.Reset(); // without this we have no delay - } - } } } } @@ -446,7 +442,6 @@ private async Task RunRefreshPeerLoop() } if (_logger.IsInfo) _logger.Info("Exiting sync peer refresh loop"); - await Task.CompletedTask; } private void StartUpgradeTimer() @@ -540,16 +535,19 @@ 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; } public void SignalPeersChanged() { - if (!_signals.SafeWaitHandle.IsClosed) - { - _signals.Set(); - } + Interlocked.Exchange(ref _signal, new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously)) + .TrySetResult(); } private async Task ExecuteRefreshTask(RefreshTotalDiffTask refreshTotalDiffTask, CancellationToken token) @@ -713,7 +711,6 @@ public async ValueTask DisposeAsync() _peerRefreshQueue.Writer.TryComplete(); _refreshLoopCancellation.Dispose(); _refreshLoopTask?.Dispose(); - _signals.Dispose(); _upgradeTimer?.Dispose(); } } diff --git a/src/Nethermind/Nethermind.Synchronization/Reporting/SnapshotReport.cs b/src/Nethermind/Nethermind.Synchronization/Reporting/SnapshotReport.cs index a882403ea4e5..232e1a495b07 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/Reporting/SyncReport.cs b/src/Nethermind/Nethermind.Synchronization/Reporting/SyncReport.cs index 871232804e6c..48cddfc837f6 100644 --- a/src/Nethermind/Nethermind.Synchronization/Reporting/SyncReport.cs +++ b/src/Nethermind/Nethermind.Synchronization/Reporting/SyncReport.cs @@ -28,7 +28,7 @@ public class SyncReport : ISyncReport private const int NoProgressStateSyncReportFrequency = 30; private const int SyncAllocatedPeersReportFrequency = 30; private const int SyncFullPeersReportFrequency = 120; - private static readonly TimeSpan _defaultReportingIntervals = TimeSpan.FromSeconds(10); + private readonly TimeSpan _defaultReportingIntervals; public SyncReport(ISyncPeerPool syncPeerPool, INodeStatsManager nodeStatsManager, ISyncConfig syncConfig, IPivot pivot, ILogManager logManager, ITimerFactory? timerFactory = null, double tickTime = 1000) { @@ -37,6 +37,7 @@ public SyncReport(ISyncPeerPool syncPeerPool, INodeStatsManager nodeStatsManager _syncConfig = syncConfig ?? throw new ArgumentNullException(nameof(syncConfig)); _pivot = pivot ?? throw new ArgumentNullException(nameof(pivot)); _syncPeersReport = new SyncPeersReport(syncPeerPool, nodeStatsManager, logManager); + _defaultReportingIntervals = TimeSpan.FromSeconds(_logger.IsDebug ? 1 : 10); _timer = (timerFactory ?? TimerFactory.Default).CreateTimer(_defaultReportingIntervals); FastBlocksHeaders = new("Old Headers", logManager); diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/AddRangeResult.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/AddRangeResult.cs index 639e4e9fb8a5..55d88adaa11f 100644 --- a/src/Nethermind/Nethermind.Synchronization/SnapSync/AddRangeResult.cs +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/AddRangeResult.cs @@ -11,6 +11,6 @@ public enum AddRangeResult ExpiredRootHash, InvalidOrder, OutOfBounds, - EmptySlots + EmptyRange } } diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/ISnapTree.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/ISnapTree.cs new file mode 100644 index 000000000000..a16dfd5ab4e6 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/ISnapTree.cs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Trie; + +namespace Nethermind.Synchronization.SnapSync; + +/// +/// Base interface for snap sync tree operations used in FillBoundaryTree. +/// +public interface ISnapTree : IDisposable +{ + Hash256 RootHash { get; } + + void SetRootFromProof(TrieNode root); + bool IsPersisted(in TreePath path, in ValueHash256 keccak); + void BulkSetAndUpdateRootHash(in ArrayPoolListRef entries); + void Commit(ValueHash256 upperBound); +} diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/ISnapTrieFactory.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/ISnapTrieFactory.cs new file mode 100644 index 000000000000..9c11eb2224f4 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/ISnapTrieFactory.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; + +namespace Nethermind.Synchronization.SnapSync; + +public interface ISnapTrieFactory +{ + ISnapTree CreateStateTree(); + ISnapTree CreateStorageTree(in ValueHash256 accountPath); +} diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/PatriciaSnapStateTree.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/PatriciaSnapStateTree.cs new file mode 100644 index 000000000000..f6b7d346dd39 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/PatriciaSnapStateTree.cs @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.State; +using Nethermind.Trie; + +namespace Nethermind.Synchronization.SnapSync; + +public class PatriciaSnapStateTree(StateTree tree, SnapUpperBoundAdapter adapter, INodeStorage nodeStorage) : ISnapTree +{ + public Hash256 RootHash => tree.RootHash; + + public void SetRootFromProof(TrieNode root) => tree.RootRef = root; + + public bool IsPersisted(in TreePath path, in ValueHash256 keccak) => + nodeStorage.KeyExists(null, path, keccak); + + public void BulkSetAndUpdateRootHash(in ArrayPoolListRef entries) + { + tree.BulkSet(entries, PatriciaTree.Flags.WasSorted); + tree.UpdateRootHash(); + } + + public void Commit(ValueHash256 upperBound) + { + adapter.UpperBound = upperBound; + tree.Commit(skipRoot: true, WriteFlags.DisableWAL); + } + + public void Dispose() { } // No-op - Patricia doesn't own resources +} diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/PatriciaSnapStorageTree.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/PatriciaSnapStorageTree.cs new file mode 100644 index 000000000000..8a67000be334 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/PatriciaSnapStorageTree.cs @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.State; +using Nethermind.Trie; + +namespace Nethermind.Synchronization.SnapSync; + +public class PatriciaSnapStorageTree(StorageTree tree, SnapUpperBoundAdapter adapter, INodeStorage nodeStorage, Hash256 address) : ISnapTree +{ + public Hash256 RootHash => tree.RootHash; + + public void SetRootFromProof(TrieNode root) => tree.RootRef = root; + + public bool IsPersisted(in TreePath path, in ValueHash256 keccak) => + nodeStorage.KeyExists(address, path, keccak); + + public void BulkSetAndUpdateRootHash(in ArrayPoolListRef entries) + { + tree.BulkSet(entries, PatriciaTree.Flags.WasSorted); + tree.UpdateRootHash(); + } + + public void Commit(ValueHash256 upperBound) + { + adapter.UpperBound = upperBound; + tree.Commit(writeFlags: WriteFlags.DisableWAL); + } + + public void Dispose() { } // No-op - Patricia doesn't own resources +} diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/PatriciaSnapTrieFactory.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/PatriciaSnapTrieFactory.cs new file mode 100644 index 000000000000..327646253944 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/PatriciaSnapTrieFactory.cs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; +using Nethermind.Logging; +using Nethermind.State; +using Nethermind.Trie.Pruning; + +namespace Nethermind.Synchronization.SnapSync; + +public class PatriciaSnapTrieFactory(INodeStorage nodeStorage, ILogManager logManager) : ISnapTrieFactory +{ + private readonly RawScopedTrieStore _stateTrieStore = new(nodeStorage, null); + + public ISnapTree CreateStateTree() + { + var adapter = new SnapUpperBoundAdapter(_stateTrieStore); + return new PatriciaSnapStateTree(new StateTree(adapter, logManager), adapter, nodeStorage); + } + + public ISnapTree CreateStorageTree(in ValueHash256 accountPath) + { + Hash256 address = accountPath.ToCommitment(); + var storageTrieStore = new RawScopedTrieStore(nodeStorage, address); + var adapter = new SnapUpperBoundAdapter(storageTrieStore); + return new PatriciaSnapStorageTree(new StorageTree(adapter, logManager), adapter, nodeStorage, address); + } + +} diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/ProgressTracker.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/ProgressTracker.cs index e3f4715ced9d..11a7de8c12bb 100644 --- a/src/Nethermind/Nethermind.Synchronization/SnapSync/ProgressTracker.cs +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/ProgressTracker.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Threading; using Autofac.Features.AttributeFilters; -using MathNet.Numerics.Statistics.Mcmc; using Nethermind.Blockchain.Synchronization; using Nethermind.Core; using Nethermind.Core.Collections; @@ -105,7 +104,7 @@ private void SetupAccountRangePartition() curStartingPath += partitionSize; - Hash256 limitPath; + ValueHash256 limitPath; // Special case for the last partition if (i == _accountRangePartitionCount - 1) @@ -115,7 +114,8 @@ private void SetupAccountRangePartition() else { limitPath = new Hash256(Keccak.Zero.Bytes); - BinaryPrimitives.WriteUInt32BigEndian(limitPath.Bytes, curStartingPath); + BinaryPrimitives.WriteUInt32BigEndian(limitPath.BytesAsSpan, curStartingPath); + limitPath = limitPath.DecrementPath(); // Limit is inclusive } partition.AccountPathLimit = limitPath; @@ -332,7 +332,7 @@ public void EnqueueAccountStorage(PathWithAccount pwa) public void EnqueueAccountRefresh(PathWithAccount pathWithAccount, in ValueHash256? startingHash, in ValueHash256? hashLimit) { - _pivot.UpdatedStorages.Add(pathWithAccount.Path.ToCommitment()); + TrackAccountToHeal(pathWithAccount.Path); AccountsToRefresh.Enqueue(new AccountWithStorageStartingHash() { PathAndAccount = pathWithAccount, StorageStartingHash = startingHash.GetValueOrDefault(), StorageHashLimit = hashLimit ?? Keccak.MaxValue }); } @@ -357,7 +357,7 @@ public void EnqueueNextSlot(StorageRange? storageRange) } } - public void EnqueueNextSlot(StorageRange parentRequest, int accountIndex, ValueHash256 lastProcessedHash) + public void EnqueueNextSlot(StorageRange parentRequest, int accountIndex, ValueHash256 lastProcessedHash, int slotCount) { ValueHash256 limitHash = parentRequest.LimitHash ?? Keccak.MaxValue; if (lastProcessedHash > limitHash) @@ -369,23 +369,30 @@ public void EnqueueNextSlot(StorageRange parentRequest, int accountIndex, ValueH UInt256 lastProcessed = new UInt256(lastProcessedHash.Bytes, true); UInt256 start = startingHash.HasValue ? new UInt256(startingHash.Value.Bytes, true) : UInt256.Zero; + // Splitting storage will cause the storage proof to not get stitched completely, causing more healing time and + // causes it to be tracked for healing, also, one more slot range to keep in memory. + // So we only split if the estimated remaining slot count is large enough. This is recursive, so large + // contract will continue getting split until the remaining slot count is low enough. + double slotSize = lastProcessed == start ? 0 : (double)(lastProcessed - start) / slotCount; + int estimatedRemainingSlotCount = slotSize == 0 ? 0 : (int)((double)(limit - lastProcessed) / slotSize); + UInt256 fullRange = limit - start; - if (_enableStorageRangeSplit && lastProcessed < fullRange / StorageRangeSplitFactor + start) + if (estimatedRemainingSlotCount > 10_000_000 && _enableStorageRangeSplit && lastProcessed < fullRange / StorageRangeSplitFactor + start) { ValueHash256 halfOfLeftHash = ((limit - lastProcessed) / 2 + lastProcessed).ToValueHash(); NextSlotRange.Enqueue(new StorageRange { Accounts = new ArrayPoolList(1) { account }, - StartingHash = lastProcessedHash, + StartingHash = lastProcessedHash.IncrementPath(), LimitHash = halfOfLeftHash }); NextSlotRange.Enqueue(new StorageRange { Accounts = new ArrayPoolList(1) { account }, - StartingHash = halfOfLeftHash, + StartingHash = halfOfLeftHash.IncrementPath(), LimitHash = limitHash }); @@ -398,7 +405,7 @@ public void EnqueueNextSlot(StorageRange parentRequest, int accountIndex, ValueH var storageRange = new StorageRange { Accounts = new ArrayPoolList(1) { account }, - StartingHash = lastProcessedHash, + StartingHash = lastProcessedHash.IncrementPath(), LimitHash = limitHash }; NextSlotRange.Enqueue(storageRange); @@ -407,6 +414,7 @@ public void EnqueueNextSlot(StorageRange parentRequest, int accountIndex, ValueH public void RetryStorageRange(StorageRange storageRange) { + bool dispose = false; if (storageRange.Accounts.Count == 1) { EnqueueNextSlot(storageRange); @@ -417,9 +425,12 @@ public void RetryStorageRange(StorageRange storageRange) { EnqueueAccountStorage(account); } + + dispose = true; } Interlocked.Add(ref _activeStorageRequests, -(storageRange?.Accounts.Count ?? 0)); + if (dispose) storageRange.Dispose(); } public void ReportAccountRangePartitionFinished(in ValueHash256 hashLimit) @@ -485,6 +496,12 @@ private void FinishRangePhase() _db.Flush(); } + public void TrackAccountToHeal(ValueHash256 path) + { + if (_logger.IsDebug) _logger.Debug($"Tracked {path} for healing"); + _pivot.UpdatedStorages.Add(path.ToCommitment()); + } + private void LogRequest(string reqType) { if (_reqCount % 100 == 0 || _lastLogTime < DateTimeOffset.Now - _maxTimeBetweenLog) @@ -492,9 +509,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; } @@ -521,7 +538,9 @@ private void LogRequest(string reqType) if (storagesToRetrieve > 0 && !_shouldStartLoggingLargeStorage) { - progress = (float)((_estimatedStorageRemaining - storagesToRetrieve) / (float)_estimatedStorageRemaining); + progress = _estimatedStorageRemaining != 0 + ? (float)((_estimatedStorageRemaining - storagesToRetrieve) / (float)_estimatedStorageRemaining) + : 1; stateRangesReport = $"Snap Remaining storage: ({progress,8:P2}) {Progress.GetMeter(progress, 1)}"; } @@ -529,14 +548,16 @@ private void LogRequest(string reqType) { double totalAllLargeStorageProgress = 0; // totalLargeStorage changes over time, but thats fine. - double totalLargeStorage = queuedStorage; + long totalLargeStorage = queuedStorage; foreach (var keyValuePair in _largeStorageProgress) { totalAllLargeStorageProgress += keyValuePair.Value.CalculateProgress(); totalLargeStorage++; } - progress = (float)(totalAllLargeStorageProgress / totalLargeStorage); + progress = totalLargeStorage != 0 + ? (float)totalAllLargeStorageProgress / totalLargeStorage + : 1; stateRangesReport = $"Snap Large storage left: {totalLargeStorage} ({progress,8:P2}) {Progress.GetMeter(progress, 1)}"; } diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs index eee17b694d85..23a1ab5d32f7 100644 --- a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs @@ -15,34 +15,26 @@ using Nethermind.Db; using Nethermind.Int256; using Nethermind.Logging; -using Nethermind.State; using Nethermind.State.Snap; -using Nethermind.Trie; -using Nethermind.Trie.Pruning; namespace Nethermind.Synchronization.SnapSync { public class SnapProvider : ISnapProvider { private readonly IDb _codeDb; - private readonly ILogManager _logManager; private readonly ILogger _logger; private readonly ProgressTracker _progressTracker; - private readonly INodeStorage _nodeStorage; + private readonly ISnapTrieFactory _trieFactory; // This is actually close to 97% effective. private readonly ClockKeyCache _codeExistKeyCache = new(1024 * 16); - private readonly RawScopedTrieStore _stateTrieStore; - public SnapProvider(ProgressTracker progressTracker, [KeyFilter(DbNames.Code)] IDb codeDb, INodeStorage nodeStorage, ILogManager logManager) + public SnapProvider(ProgressTracker progressTracker, [KeyFilter(DbNames.Code)] IDb codeDb, ISnapTrieFactory trieFactory, ILogManager logManager) { _codeDb = codeDb; _progressTracker = progressTracker; - _nodeStorage = nodeStorage; - _stateTrieStore = new RawScopedTrieStore(_nodeStorage, null); - - _logManager = logManager; + _trieFactory = trieFactory; _logger = logManager.GetClassLogger(); } @@ -93,12 +85,10 @@ public AddRangeResult AddAccountRange( { if (accounts.Count == 0) throw new ArgumentException("Cannot be empty.", nameof(accounts)); - StateTree tree = new(_stateTrieStore, _logManager); - ValueHash256 effectiveHashLimit = hashLimit ?? ValueKeccak.MaxValue; - (AddRangeResult result, bool moreChildrenToRight, List accountsWithStorage, List codeHashes) = - SnapProviderHelper.AddAccountRange(tree, blockNumber, expectedRootHash, startingHash, effectiveHashLimit, accounts, proofs); + (AddRangeResult result, bool moreChildrenToRight, List accountsWithStorage, List codeHashes, Hash256 actualRootHash) = + SnapProviderHelper.AddAccountRange(_trieFactory, blockNumber, expectedRootHash, startingHash, effectiveHashLimit, accounts, proofs); if (result == AddRangeResult.OK) { @@ -118,25 +108,24 @@ public AddRangeResult AddAccountRange( _progressTracker.EnqueueCodeHashes(filteredCodeHashes.AsSpan()); - UInt256 nextPath = accounts[^1].Path.ToUInt256(); - nextPath += UInt256.One; - _progressTracker.UpdateAccountRangePartitionProgress(effectiveHashLimit, nextPath.ToValueHash(), moreChildrenToRight); - } - else if (result == AddRangeResult.MissingRootHashInProofs) - { - _logger.Trace($"SNAP - AddAccountRange failed, missing root hash {tree.RootHash} in the proofs, startingHash:{startingHash}"); - } - else if (result == AddRangeResult.DifferentRootHash) - { - _logger.Trace($"SNAP - AddAccountRange failed, expected {blockNumber}:{expectedRootHash} but was {tree.RootHash}, startingHash:{startingHash}"); + ValueHash256 nextPath = accounts[^1].Path.IncrementPath(); + _progressTracker.UpdateAccountRangePartitionProgress(effectiveHashLimit, nextPath, moreChildrenToRight); } - else if (result == AddRangeResult.InvalidOrder) + if (_logger.IsTrace) { - if (_logger.IsTrace) _logger.Trace($"SNAP - AddAccountRange failed, accounts are not in sorted order, startingHash:{startingHash}"); - } - else if (result == AddRangeResult.OutOfBounds) - { - if (_logger.IsTrace) _logger.Trace($"SNAP - AddAccountRange failed, accounts are out of bounds, startingHash:{startingHash}"); + string message = result switch + { + AddRangeResult.MissingRootHashInProofs => $"SNAP - AddAccountRange failed, missing root hash {actualRootHash} in the proofs, startingHash:{startingHash}", + AddRangeResult.DifferentRootHash => $"SNAP - AddAccountRange failed, expected {blockNumber}:{expectedRootHash} but was {actualRootHash}, startingHash:{startingHash}", + AddRangeResult.InvalidOrder => $"SNAP - AddAccountRange failed, accounts are not in sorted order, startingHash:{startingHash}", + AddRangeResult.OutOfBounds => $"SNAP - AddAccountRange failed, accounts are out of bounds, startingHash:{startingHash}", + AddRangeResult.EmptyRange => $"SNAP - AddAccountRange failed, empty accounts, startingHash:{startingHash}", + _ => null + }; + if (message is not null) + { + _logger.Trace(message); + } } return result; @@ -199,68 +188,70 @@ public AddRangeResult AddStorageRange(StorageRange request, SlotsAndProofs respo public AddRangeResult AddStorageRangeForAccount(StorageRange request, int accountIndex, IReadOnlyList slots, IReadOnlyList? proofs = null) { PathWithAccount pathWithAccount = request.Accounts[accountIndex]; - StorageTree tree = new(new RawScopedTrieStore(_nodeStorage, pathWithAccount.Path.ToCommitment()), _logManager); - - (AddRangeResult result, bool moreChildrenToRight) = SnapProviderHelper.AddStorageRange(tree, pathWithAccount, slots, request.StartingHash, request.LimitHash, proofs); - if (result == AddRangeResult.OK) + try { - if (moreChildrenToRight) + (AddRangeResult result, bool moreChildrenToRight, Hash256 actualRootHash, bool isRootPersisted) = SnapProviderHelper.AddStorageRange(_trieFactory, pathWithAccount, slots, request.StartingHash, request.LimitHash, proofs); + if (result == AddRangeResult.OK) { - _progressTracker.EnqueueNextSlot(request, accountIndex, slots[^1].Path); + if (moreChildrenToRight) + { + _progressTracker.EnqueueNextSlot(request, accountIndex, slots[^1].Path, slots.Count); + } + else if (accountIndex == 0 && request.Accounts.Count == 1) + { + _progressTracker.OnCompletedLargeStorage(pathWithAccount); + } + + if (!moreChildrenToRight && (request.LimitHash == null || request.LimitHash == ValueKeccak.MaxValue) && !isRootPersisted) + { + // Sometimes the stitching does not work. Likely because part of the storage is using different + // pivot, sometimes the proof is in a form that we cannot cleanly verify if it should persist or not, + // but also because of stitching bug. So we just force trigger healing and continue on with our lives. + _progressTracker.TrackAccountToHeal(request.Accounts[accountIndex].Path); + } + + return result; } - else if (accountIndex == 0 && request.Accounts.Count == 1) + + if (_logger.IsTrace) { - _progressTracker.OnCompletedLargeStorage(pathWithAccount); + string message = result switch + { + AddRangeResult.MissingRootHashInProofs => $"SNAP - AddStorageRange failed, missing root hash {actualRootHash} in the proofs, startingHash:{request.StartingHash}", + AddRangeResult.DifferentRootHash => $"SNAP - AddStorageRange failed, expected storage root hash:{pathWithAccount.Account.StorageRoot} but was {actualRootHash}, startingHash:{request.StartingHash}", + AddRangeResult.InvalidOrder => $"SNAP - AddStorageRange failed, slots are not in sorted order, startingHash:{request.StartingHash}", + AddRangeResult.OutOfBounds => $"SNAP - AddStorageRange failed, slots are out of bounds, startingHash:{request.StartingHash}", + AddRangeResult.EmptyRange => $"SNAP - AddStorageRange failed, slots list is empty, startingHash:{request.StartingHash}", + _ => null + }; + if (message is not null) + { + _logger.Trace(message); + } } + _progressTracker.EnqueueAccountRefresh(pathWithAccount, request.StartingHash, request.LimitHash); return result; - } - if (result == AddRangeResult.MissingRootHashInProofs) - { - _logger.Trace( - $"SNAP - AddStorageRange failed, missing root hash {pathWithAccount.Account.StorageRoot} in the proofs, startingHash:{request.StartingHash}"); - } - else if (result == AddRangeResult.DifferentRootHash) - { - _logger.Trace( - $"SNAP - AddStorageRange failed, expected storage root hash:{pathWithAccount.Account.StorageRoot} but was {tree.RootHash}, startingHash:{request.StartingHash}"); - } - else if (result == AddRangeResult.InvalidOrder) - { - if (_logger.IsTrace) - _logger.Trace( - $"SNAP - AddStorageRange failed, slots are not in sorted order, startingHash:{request.StartingHash}"); } - else if (result == AddRangeResult.OutOfBounds) + catch (Exception e) { - if (_logger.IsTrace) - _logger.Trace( - $"SNAP - AddStorageRange failed, slots are out of bounds, startingHash:{request.StartingHash}"); + if (_logger.IsWarn) _logger.Warn($"Error in storage {e}"); + throw; } - else if (result == AddRangeResult.EmptySlots) - { - if (_logger.IsTrace) - _logger.Trace( - $"SNAP - AddStorageRange failed, slots list is empty, startingHash:{request.StartingHash}"); - } - - _progressTracker.EnqueueAccountRefresh(pathWithAccount, request.StartingHash, request.LimitHash); - return result; } public void RefreshAccounts(AccountsToRefreshRequest request, IOwnedReadOnlyList response) { 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) { @@ -269,35 +260,22 @@ public void RefreshAccounts(AccountsToRefreshRequest request, IOwnedReadOnlyList continue; } - try - { - TreePath emptyTreePath = TreePath.Empty; - TrieNode node = new(NodeType.Unknown, nodeData, isDirty: true); - node.ResolveNode(stateStore, emptyTreePath); - node.ResolveKey(stateStore, ref emptyTreePath); - - requestedPath.PathAndAccount.Account = requestedPath.PathAndAccount.Account.WithChangedStorageRoot(node.Keccak); + requestedPath.PathAndAccount.Account = requestedPath.PathAndAccount.Account.WithChangedStorageRoot(Keccak.Compute(nodeData)); - if (requestedPath.StorageStartingHash > ValueKeccak.Zero) - { - StorageRange range = new() - { - Accounts = new ArrayPoolList(1) { requestedPath.PathAndAccount }, - StartingHash = requestedPath.StorageStartingHash, - LimitHash = requestedPath.StorageHashLimit - }; - - _progressTracker.EnqueueNextSlot(range); - } - else + if (requestedPath.StorageStartingHash > ValueKeccak.Zero) + { + StorageRange range = new() { - _progressTracker.EnqueueAccountStorage(requestedPath.PathAndAccount); - } + Accounts = new ArrayPoolList(1) { requestedPath.PathAndAccount }, + StartingHash = requestedPath.StorageStartingHash, + LimitHash = requestedPath.StorageHashLimit + }; + + _progressTracker.EnqueueNextSlot(range); } - catch (Exception exc) + else { - RetryAccountRefresh(requestedPath); - _logger.Warn($"SNAP - {exc.Message}:{requestedPath.PathAndAccount.Path}:{Bytes.ToHexString(nodeData)}"); + _progressTracker.EnqueueAccountStorage(requestedPath.PathAndAccount); } } else @@ -369,5 +347,6 @@ public void Dispose() { _codeExistKeyCache.Clear(); } + } } diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProviderHelper.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProviderHelper.cs index f72011435e22..4050c2e2e057 100644 --- a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProviderHelper.cs +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProviderHelper.cs @@ -20,8 +20,8 @@ public static class SnapProviderHelper { private const int ExtensionRlpChildIndex = 1; - public static (AddRangeResult result, bool moreChildrenToRight, List storageRoots, List codeHashes) AddAccountRange( - StateTree tree, + public static (AddRangeResult result, bool moreChildrenToRight, List storageRoots, List codeHashes, Hash256 actualRootHash) AddAccountRange( + ISnapTrieFactory factory, long blockNumber, in ValueHash256 expectedRootHash, in ValueHash256 startingHash, @@ -30,103 +30,41 @@ public static (AddRangeResult result, bool moreChildrenToRight, List proofs = null ) { - // TODO: Check the accounts boundaries and sorting - if (accounts.Count == 0) - throw new ArgumentException("Cannot be empty.", nameof(accounts)); + using ISnapTree tree = factory.CreateStateTree(); - // Validate sorting order - for (int i = 1; i < accounts.Count; i++) + using ArrayPoolListRef entries = new(accounts.Count); + for (int index = 0; index < accounts.Count; index++) { - if (accounts[i - 1].Path.CompareTo(accounts[i].Path) >= 0) - { - return (AddRangeResult.InvalidOrder, true, null, null); - } + PathWithAccount account = accounts[index]; + Account accountValue = account.Account; + Rlp rlp = accountValue.IsTotallyEmpty ? StateTree.EmptyAccountRlp : Rlp.Encode(accountValue); + entries.Add(new PatriciaTree.BulkSetEntry(account.Path, rlp.Bytes)); + Interlocked.Add(ref Metrics.SnapStateSynced, rlp.Bytes.Length); } - ValueHash256 lastHash = accounts[^1].Path; - - (AddRangeResult result, List<(TrieNode, TreePath)> sortedBoundaryList, bool moreChildrenToRight) = - FillBoundaryTree(tree, startingHash, lastHash, limitHash, expectedRootHash, proofs); - + (AddRangeResult result, bool moreChildrenToRight, _) = CommitRange( + tree, entries, startingHash, limitHash, expectedRootHash, proofs); if (result != AddRangeResult.OK) - { - return (result, true, null, null); - } + return (result, true, null, null, tree.RootHash); List accountsWithStorage = new(); List codeHashes = new(); - bool hasExtraStorage = false; - - using ArrayPoolList entries = new ArrayPoolList(accounts.Count); - for (var index = 0; index < accounts.Count; index++) + for (int index = 0; index < accounts.Count; index++) { PathWithAccount account = accounts[index]; - if (account.Account.HasStorage) - { - if (account.Path >= limitHash || account.Path < startingHash) - { - hasExtraStorage = true; - } - else - { - accountsWithStorage.Add(account); - } - } + + if (account.Account.HasStorage && account.Path <= limitHash) + accountsWithStorage.Add(account); if (account.Account.HasCode) - { codeHashes.Add(account.Account.CodeHash); - } - - var account_ = account.Account; - Rlp rlp = account_ is null ? null : account_.IsTotallyEmpty ? StateTree.EmptyAccountRlp : Rlp.Encode(account_); - entries.Add(new PatriciaTree.BulkSetEntry(account.Path, rlp?.Bytes)); - if (account is not null) - { - Interlocked.Add(ref Metrics.SnapStateSynced, rlp.Bytes.Length); - } - } - - tree.BulkSet(entries, PatriciaTree.Flags.WasSorted); - tree.UpdateRootHash(); - - if (tree.RootHash.ValueHash256 != expectedRootHash) - { - return (AddRangeResult.DifferentRootHash, true, null, null); } - if (hasExtraStorage) - { - // The server will always give one node extra after limitpath if it can fit in the response. - // When we have extra storage, the extra storage must not be re-stored as it may have already been set - // by another top level partition. If the sync pivot moved and the storage was modified, it must not be saved - // here along with updated ancestor so that healing can detect that the storage need to be healed. - // - // Unfortunately, without introducing large change to the tree, the easiest way to - // exclude the extra storage is to just rebuild the whole tree and also skip stitching. - // Fortunately, this should only happen n-1 time where n is the number of top level - // partition count. - - tree.RootHash = Keccak.EmptyTreeHash; - for (var index = 0; index < accounts.Count; index++) - { - PathWithAccount account = accounts[index]; - if (account.Path >= limitHash || account.Path < startingHash) continue; - _ = tree.Set(account.Path, account.Account); - } - } - else - { - StitchBoundaries(sortedBoundaryList, tree.TrieStore); - } - - tree.Commit(skipRoot: true, writeFlags: WriteFlags.DisableWAL); - - return (AddRangeResult.OK, moreChildrenToRight, accountsWithStorage, codeHashes); + return (AddRangeResult.OK, moreChildrenToRight, accountsWithStorage, codeHashes, tree.RootHash); } - public static (AddRangeResult result, bool moreChildrenToRight) AddStorageRange( - StorageTree tree, + public static (AddRangeResult result, bool moreChildrenToRight, Hash256 actualRootHash, bool isRootPersisted) AddStorageRange( + ISnapTrieFactory factory, PathWithAccount account, IReadOnlyList slots, in ValueHash256? startingHash, @@ -134,59 +72,84 @@ public static (AddRangeResult result, bool moreChildrenToRight) AddStorageRange( IReadOnlyList? proofs = null ) { - if (slots.Count == 0) - return (AddRangeResult.EmptySlots, false); + using ISnapTree tree = factory.CreateStorageTree(account.Path); - // Validate sorting order - for (int i = 1; i < slots.Count; i++) + ValueHash256 effectiveLimitHash = limitHash ?? Keccak.MaxValue; + ValueHash256 effectiveStartingHash = startingHash ?? ValueKeccak.Zero; + + using ArrayPoolListRef entries = new(slots.Count); + for (int index = 0; index < slots.Count; index++) { - if (slots[i - 1].Path.CompareTo(slots[i].Path) >= 0) - { - return (AddRangeResult.InvalidOrder, true); - } + PathWithStorageSlot slot = slots[index]; + + Interlocked.Add(ref Metrics.SnapStateSynced, slot.SlotRlpValue.Length); + entries.Add(new PatriciaTree.BulkSetEntry(slot.Path, slot.SlotRlpValue)); } - ValueHash256 lastHash = slots[^1].Path; + (AddRangeResult result, bool moreChildrenToRight, bool isRootPersisted) = CommitRange( + tree, entries, effectiveStartingHash, effectiveLimitHash, account.Account.StorageRoot, proofs); + if (result != AddRangeResult.OK) + return (result, true, tree.RootHash, false); + return (AddRangeResult.OK, moreChildrenToRight, tree.RootHash, isRootPersisted); + } - (AddRangeResult result, List<(TrieNode, TreePath)> sortedBoundaryList, bool moreChildrenToRight) = FillBoundaryTree( - tree, startingHash, lastHash, limitHash ?? Keccak.MaxValue, account.Account.StorageRoot, proofs); + private static (AddRangeResult result, bool moreChildrenToRight, bool isRootPersisted) CommitRange( + ISnapTree tree, + in ArrayPoolListRef entries, + in ValueHash256 startingHash, + in ValueHash256 limitHash, + in ValueHash256 expectedRootHash, + IReadOnlyList? proofs) + { + if (entries.Count == 0) + return (AddRangeResult.EmptyRange, true, false); - if (result != AddRangeResult.OK) + // Validate sorting order + for (int i = 1; i < entries.Count; i++) { - return (result, true); + if (entries[i - 1].Path.CompareTo(entries[i].Path) >= 0) + return (AddRangeResult.InvalidOrder, true, false); } - using ArrayPoolList entries = new ArrayPoolList(slots.Count); - for (var index = 0; index < slots.Count; index++) - { - PathWithStorageSlot slot = slots[index]; - Interlocked.Add(ref Metrics.SnapStateSynced, slot.SlotRlpValue.Length); - entries.Add(new PatriciaTree.BulkSetEntry(slot.Path, slot.SlotRlpValue)); - } + if (entries[0].Path < startingHash) + return (AddRangeResult.InvalidOrder, true, false); + + ValueHash256 lastPath = entries[entries.Count - 1].Path; - tree.BulkSet(entries, PatriciaTree.Flags.WasSorted); - tree.UpdateRootHash(); + (AddRangeResult result, List<(TrieNode, TreePath)> sortedBoundaryList, bool moreChildrenToRight) = + FillBoundaryTree(tree, startingHash, lastPath, limitHash, expectedRootHash, proofs); + + if (result != AddRangeResult.OK) + return (result, true, false); - if (tree.RootHash.ValueHash256 != account.Account.StorageRoot) + tree.BulkSetAndUpdateRootHash(entries); + + if (tree.RootHash.ValueHash256 != expectedRootHash) + return (AddRangeResult.DifferentRootHash, true, false); + + StitchBoundaries(sortedBoundaryList, tree, startingHash); + + // The upper bound is used to prevent proof nodes that covers next range from being persisted, except if + // this is the last range. This prevent double node writes per path which break flat. It also prevent leaf o + // that is after the range from being persisted, which prevent double write again. + ValueHash256 upperBound = lastPath; + if (upperBound > limitHash) { - return (AddRangeResult.DifferentRootHash, true); + upperBound = limitHash; } - - // This will work if all StorageRange requests share the same AccountWithPath object which seems to be the case. - // If this is not true, StorageRange request should be extended with a lock object. - // That lock object should be shared between all other StorageRange requests for same account. - lock (account.Account) + else { - StitchBoundaries(sortedBoundaryList, tree.TrieStore); - tree.Commit(writeFlags: WriteFlags.DisableWAL); + if (!moreChildrenToRight) upperBound = ValueKeccak.MaxValue; } + tree.Commit(upperBound); - return (AddRangeResult.OK, moreChildrenToRight); + bool isRootPersisted = sortedBoundaryList is not { Count: > 0 } || sortedBoundaryList[0].Item1.IsPersisted; + return (AddRangeResult.OK, moreChildrenToRight, isRootPersisted); } [SkipLocalsInit] private static (AddRangeResult result, List<(TrieNode, TreePath)> sortedBoundaryList, bool moreChildrenToRight) FillBoundaryTree( - PatriciaTree tree, + ISnapTree tree, in ValueHash256? startingHash, in ValueHash256 endHash, in ValueHash256 limitHash, @@ -201,10 +164,10 @@ private static (AddRangeResult result, List<(TrieNode, TreePath)> sortedBoundary ArgumentNullException.ThrowIfNull(tree); - ValueHash256 effectiveStartingHAsh = startingHash ?? ValueKeccak.Zero; + ValueHash256 effectiveStartingHash = startingHash ?? ValueKeccak.Zero; List<(TrieNode, TreePath)> sortedBoundaryList = new(); - Dictionary dict = CreateProofDict(proofs, tree.TrieStore); + Dictionary dict = CreateProofDict(proofs); if (!dict.TryGetValue(expectedRootHash, out TrieNode root)) { @@ -215,16 +178,15 @@ private static (AddRangeResult result, List<(TrieNode, TreePath)> sortedBoundary { // Special case with some server sending proof where the root is the same as the only path. // Without this the proof's IsBoundaryNode flag will cause the key to not get saved. - var rootPath = TreePath.FromNibble(root.Key); + TreePath rootPath = TreePath.FromNibble(root.Key); if (rootPath.Length == 64 && rootPath.Path.Equals(endHash)) { return (AddRangeResult.OK, null, false); } } - TreePath leftBoundaryPath = TreePath.FromPath(effectiveStartingHAsh.Bytes); + TreePath leftBoundaryPath = TreePath.FromPath(effectiveStartingHash.Bytes); TreePath rightBoundaryPath = TreePath.FromPath(endHash.Bytes); - TreePath rightLimitPath = TreePath.FromPath(limitHash.Bytes); // For when in very-very unlikely case where the last remaining address is Keccak.MaxValue, (who knows why, // the chain have special handling for it maybe) and it is not included the returned account range, (again, @@ -236,7 +198,7 @@ private static (AddRangeResult result, List<(TrieNode, TreePath)> sortedBoundary // hash will not match. Stack<(TrieNode node, TreePath path)> proofNodesToProcess = new(); - tree.RootRef = root; + tree.SetRootFromProof(root); proofNodesToProcess.Push((root, TreePath.Empty)); sortedBoundaryList.Add((root, TreePath.Empty)); @@ -249,12 +211,14 @@ private static (AddRangeResult result, List<(TrieNode, TreePath)> sortedBoundary { if (node.GetChildHashAsValueKeccak(ExtensionRlpChildIndex, out ValueHash256 childKeccak)) { + TreePath childPath = path.Append(node.Key); + + moreChildrenToRight |= childPath.Path > limitHash; + if (dict.TryGetValue(childKeccak, out TrieNode child)) { node.SetChild(0, child); - TreePath childPath = path.Append(node.Key); - proofNodesToProcess.Push((child, childPath)); sortedBoundaryList.Add((child, childPath)); } @@ -264,15 +228,33 @@ private static (AddRangeResult result, List<(TrieNode, TreePath)> sortedBoundary { int left = leftBoundaryPath.CompareToTruncated(path, path.Length) == 0 ? leftBoundaryPath[path.Length] : 0; int right = rightBoundaryPath.CompareToTruncated(path, path.Length) == 0 ? rightBoundaryPath[path.Length] : 15; - int limit = rightLimitPath.CompareToTruncated(path, path.Length) == 0 ? rightLimitPath[path.Length] : 15; int maxIndex = moreChildrenToRight ? right : 15; for (int ci = left; ci <= maxIndex; ci++) { bool hasKeccak = node.GetChildHashAsValueKeccak(ci, out ValueHash256 childKeccak); + TrieNode? child = null; + if (hasKeccak) + { + dict.TryGetValue(childKeccak, out child); + } + + if (child is null) + { + // Note: be careful with inline node. Inline node is not set in the proof dictionary + byte[]? inlineRlp = node.GetInlineNodeRlp(ci); + if (inlineRlp is not null) + { + child = new TrieNode(NodeType.Unknown, inlineRlp); + child.ResolveNode(NullTrieNodeResolver.Instance, path.Append(ci)); + } + } - moreChildrenToRight |= hasKeccak && (ci > right && (ci <= limit || noLimit)); + // The limit may have lower nibble that is less than the path's current nibble, even if upper + // nibble is higher. So need to check whole path + TreePath childPath = path.Append(ci); + moreChildrenToRight |= (hasKeccak || child is not null) && (ci > right && (childPath.Path <= limitHash || noLimit)); if (ci >= left && ci <= right) { @@ -280,10 +262,19 @@ private static (AddRangeResult result, List<(TrieNode, TreePath)> sortedBoundary node.SetChild(ci, null); } - if (hasKeccak && (ci == left || ci == right) && dict.TryGetValue(childKeccak, out TrieNode child)) + if (child is not null && !hasKeccak && (ci == left || ci == right)) { - TreePath childPath = path.Append(ci); + // Inline node at boundary. Need to be set back or keccak will be incorrect. + // but must not be set as part of boundary list or break stitching. + TreePath wholePath = childPath.Append(child.Key); + if (leftBoundaryPath.CompareToTruncated(wholePath, wholePath.Length) > 0 || rightBoundaryPath.CompareToTruncated(wholePath, wholePath.Length) < 0) + { + node.SetChild(ci, child); + } + } + if (hasKeccak && (ci == left || ci == right) && child is not null) + { if (child.IsBranch) { node.SetChild(ci, child); @@ -321,7 +312,7 @@ private static (AddRangeResult result, List<(TrieNode, TreePath)> sortedBoundary return (AddRangeResult.OK, sortedBoundaryList, moreChildrenToRight); } - private static Dictionary CreateProofDict(IReadOnlyList proofs, IScopedTrieStore store) + private static Dictionary CreateProofDict(IReadOnlyList proofs) { Dictionary dict = new(); @@ -332,8 +323,8 @@ private static Dictionary CreateProofDict(IReadOnlyList< node.IsBoundaryProofNode = true; TreePath emptyPath = TreePath.Empty; - node.ResolveNode(store, emptyPath); - node.ResolveKey(store, ref emptyPath); + node.ResolveNode(UnknownNodeResolver.Instance, emptyPath); + node.ResolveKey(UnknownNodeResolver.Instance, ref emptyPath); dict[node.Keccak] = node; } @@ -341,7 +332,7 @@ private static Dictionary CreateProofDict(IReadOnlyList< return dict; } - private static void StitchBoundaries(List<(TrieNode, TreePath)> sortedBoundaryList, IScopedTrieStore store) + private static void StitchBoundaries(List<(TrieNode, TreePath)>? sortedBoundaryList, ISnapTree tree, ValueHash256 startPath) { if (sortedBoundaryList is null || sortedBoundaryList.Count == 0) { @@ -351,13 +342,12 @@ private static void StitchBoundaries(List<(TrieNode, TreePath)> sortedBoundaryLi for (int i = sortedBoundaryList.Count - 1; i >= 0; i--) { (TrieNode node, TreePath path) = sortedBoundaryList[i]; - if (!node.IsPersisted) { INodeData nodeData = node.NodeData; if (nodeData is ExtensionData extensionData) { - if (IsChildPersisted(node, ref path, extensionData._value, ExtensionRlpChildIndex, store)) + if (IsChildPersisted(node, ref path, extensionData._value, ExtensionRlpChildIndex, tree, startPath)) { node.IsBoundaryProofNode = false; } @@ -368,7 +358,7 @@ private static void StitchBoundaries(List<(TrieNode, TreePath)> sortedBoundaryLi int ci = 0; foreach (object? o in branchData.Branches) { - if (!IsChildPersisted(node, ref path, o, ci, store)) + if (!IsChildPersisted(node, ref path, o, ci, tree, startPath)) { isBoundaryProofNode = true; break; @@ -386,14 +376,14 @@ private static void StitchBoundaries(List<(TrieNode, TreePath)> sortedBoundaryLi //leading to TrieNodeException after sync (as healing may not get to heal the particular storage trie) if (node.IsLeaf) { - node.IsPersisted = store.IsPersisted(path, node.Keccak); + node.IsPersisted = tree.IsPersisted(path, node.Keccak); node.IsBoundaryProofNode = !node.IsPersisted; } } } } - private static bool IsChildPersisted(TrieNode node, ref TreePath nodePath, object? child, int childIndex, IScopedTrieStore store) + private static bool IsChildPersisted(TrieNode node, ref TreePath nodePath, object? child, int childIndex, ISnapTree tree, ValueHash256 startPath) { if (child is TrieNode childNode) { @@ -413,7 +403,7 @@ private static bool IsChildPersisted(TrieNode node, ref TreePath nodePath, objec int previousPathLength = node.AppendChildPath(ref nodePath, childIndex); try { - return store.IsPersisted(nodePath, childKeccak); + return tree.IsPersisted(nodePath, childKeccak); } finally { diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapSyncAllocationStrategyFactory.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapSyncAllocationStrategyFactory.cs index 414d267e9baf..075d50fe6a4d 100644 --- a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapSyncAllocationStrategyFactory.cs +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapSyncAllocationStrategyFactory.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Blockchain.Synchronization; -using Nethermind.Consensus; using Nethermind.Stats; using Nethermind.Synchronization.ParallelSync; using Nethermind.Synchronization.Peers.AllocationStrategies; diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapUpperBoundAdapter.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapUpperBoundAdapter.cs new file mode 100644 index 000000000000..89f50a307d75 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapUpperBoundAdapter.cs @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Trie; +using Nethermind.Trie.Pruning; + +namespace Nethermind.Synchronization.SnapSync; + +/// +/// A wrapper to trie store that prevent committing boundary proof node and nodes whose subtree extend beyond +/// UpperBound. This is to prevent double writes on partitioned snap ranges. +/// +/// +public class SnapUpperBoundAdapter(IScopedTrieStore baseTrieStore) : IScopedTrieStore +{ + public ValueHash256 UpperBound = ValueKeccak.MaxValue; + + public TrieNode FindCachedOrUnknown(in TreePath path, Hash256 hash) => baseTrieStore.FindCachedOrUnknown(in path, hash); + + public byte[]? LoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => 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) => throw new NotSupportedException("Get storage trie node resolver not supported"); + + public INodeStorage.KeyScheme Scheme => baseTrieStore.Scheme; + + public ICommitter BeginCommit(TrieNode? root, WriteFlags writeFlags = WriteFlags.None) => new BoundedSnapCommitter(baseTrieStore.BeginCommit(root, writeFlags), UpperBound); + + private sealed class BoundedSnapCommitter(ICommitter baseCommitter, ValueHash256 subtreeLimit) : ICommitter + { + public void Dispose() => baseCommitter.Dispose(); + + public TrieNode CommitNode(ref TreePath path, TrieNode node) + { + if (node.IsBoundaryProofNode) return node; + + ValueHash256 subtreeUpperRange = node.IsBranch ? path.ToUpperBoundPath() : path.Append(node.Key).ToUpperBoundPath(); + if (subtreeUpperRange > subtreeLimit) return node; + + node = baseCommitter.CommitNode(ref path, node); + node.IsPersisted = true; + return node; + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/UnknownNodeResolver.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/UnknownNodeResolver.cs new file mode 100644 index 000000000000..462749a62101 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/UnknownNodeResolver.cs @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Trie; +using Nethermind.Trie.Pruning; + +namespace Nethermind.Synchronization.SnapSync; + +/// +/// A simple ITrieNodeResolver that creates unknown nodes from hashes. +/// Used for proof resolution where RLP is already provided. +/// +internal sealed class UnknownNodeResolver : ITrieNodeResolver +{ + public static readonly UnknownNodeResolver Instance = new(); + + private UnknownNodeResolver() { } + + public TrieNode FindCachedOrUnknown(in TreePath path, Hash256 hash) => + new(NodeType.Unknown, hash); + + public byte[]? LoadRlp(in TreePath path, Hash256 hash, ReadFlags flags) => + throw new NotSupportedException("Proof nodes have RLP embedded"); + + public byte[]? TryLoadRlp(in TreePath path, Hash256 hash, ReadFlags flags) => null; + + public ITrieNodeResolver GetStorageTrieNodeResolver(Hash256? address) => this; + + public INodeStorage.KeyScheme Scheme => INodeStorage.KeyScheme.Hash; +} diff --git a/src/Nethermind/Nethermind.Synchronization/StateSync/StateSyncDownloader.cs b/src/Nethermind/Nethermind.Synchronization/StateSync/StateSyncDownloader.cs index 1829128bcdc2..d9d24369264f 100644 --- a/src/Nethermind/Nethermind.Synchronization/StateSync/StateSyncDownloader.cs +++ b/src/Nethermind/Nethermind.Synchronization/StateSync/StateSyncDownloader.cs @@ -9,7 +9,6 @@ using Nethermind.Blockchain.Synchronization; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; using Nethermind.Logging; using Nethermind.Network.Contract.P2P; using Nethermind.State.Snap; @@ -40,21 +39,21 @@ public async Task Dispatch(PeerInfo peerInfo, StateSyncBatch batch, Cancellation Task> 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/SyncPeerEventArgs.cs b/src/Nethermind/Nethermind.Synchronization/SyncPeerEventArgs.cs deleted file mode 100644 index 3d5fbf168612..000000000000 --- a/src/Nethermind/Nethermind.Synchronization/SyncPeerEventArgs.cs +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Synchronization.Peers; - -namespace Nethermind.Synchronization -{ - public class AllocationChangeEventArgs - { - public AllocationChangeEventArgs(PeerInfo? previous, PeerInfo? current) - { - Previous = previous; - Current = current; - } - - public PeerInfo? Previous { get; } - - public PeerInfo? Current { get; } - } -} diff --git a/src/Nethermind/Nethermind.Synchronization/SyncServer.cs b/src/Nethermind/Nethermind.Synchronization/SyncServer.cs index 689a09b53e7c..a66429588841 100644 --- a/src/Nethermind/Nethermind.Synchronization/SyncServer.cs +++ b/src/Nethermind/Nethermind.Synchronization/SyncServer.cs @@ -124,6 +124,8 @@ public BlockHeader? Head } } + public long LowestBlock => Math.Min(Head?.Number ?? 0, _blockTree.GetLowestBlock()); + public int GetPeerCount() => _pool.PeerCount; private readonly Guid _sealValidatorUserGuid = Guid.NewGuid(); @@ -556,6 +558,7 @@ public void StopNotifyingPeersAboutNewBlocks() private void StopNotifyingPeersAboutBlockRangeUpdates() { _blockTree.NewHeadBlock -= OnNewRange; + _historyPruner.NewOldestBlock -= OnNewRange; } public void Dispose() diff --git a/src/Nethermind/Nethermind.Synchronization/Synchronizer.cs b/src/Nethermind/Nethermind.Synchronization/Synchronizer.cs index 9eb2b50f1892..849c6f5946a9 100644 --- a/src/Nethermind/Nethermind.Synchronization/Synchronizer.cs +++ b/src/Nethermind/Nethermind.Synchronization/Synchronizer.cs @@ -13,7 +13,6 @@ using Nethermind.Core.Extensions; using Nethermind.Logging; using Nethermind.Specs.ChainSpecStyle; -using Nethermind.State; using Nethermind.State.Healing; using Nethermind.Stats; using Nethermind.Stats.Model; @@ -312,7 +311,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() @@ -413,6 +412,7 @@ private void ConfigureSnapComponent(ContainerBuilder serviceCollection) { serviceCollection .AddSingleton() + .AddSingleton() .AddSingleton(); ConfigureSingletonSyncFeed(serviceCollection); @@ -451,6 +451,7 @@ private void ConfigureStateSyncComponent(ContainerBuilder serviceCollection) { serviceCollection .AddSingleton() + .AddSingleton() .AddSingleton(); ConfigureSingletonSyncFeed(serviceCollection); diff --git a/src/Nethermind/Nethermind.Synchronization/Trie/NodeDataRecovery.cs b/src/Nethermind/Nethermind.Synchronization/Trie/NodeDataRecovery.cs index 2753c8be3edd..60064565b8f5 100644 --- a/src/Nethermind/Nethermind.Synchronization/Trie/NodeDataRecovery.cs +++ b/src/Nethermind/Nethermind.Synchronization/Trie/NodeDataRecovery.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; diff --git a/src/Nethermind/Nethermind.Synchronization/Trie/PathNodeRecovery.cs b/src/Nethermind/Nethermind.Synchronization/Trie/PathNodeRecovery.cs index 5ae0ce0c9c01..ec8c7c8f9184 100644 --- a/src/Nethermind/Nethermind.Synchronization/Trie/PathNodeRecovery.cs +++ b/src/Nethermind/Nethermind.Synchronization/Trie/PathNodeRecovery.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Nethermind.Core.Collections; diff --git a/src/Nethermind/Nethermind.Synchronization/Trie/SnapRangeRecovery.cs b/src/Nethermind/Nethermind.Synchronization/Trie/SnapRangeRecovery.cs index b041e90779b3..3bd89467cb01 100644 --- a/src/Nethermind/Nethermind.Synchronization/Trie/SnapRangeRecovery.cs +++ b/src/Nethermind/Nethermind.Synchronization/Trie/SnapRangeRecovery.cs @@ -25,6 +25,7 @@ using Nethermind.Trie.Pruning; namespace Nethermind.Synchronization.Trie; + public class SnapRangeRecovery(ISyncPeerPool peerPool, ILogManager logManager) : IPathRecovery { // Pick by reduced latency instead of throughput diff --git a/src/Nethermind/Nethermind.Taiko.Test/L1OriginStoreTests.cs b/src/Nethermind/Nethermind.Taiko.Test/L1OriginStoreTests.cs new file mode 100644 index 000000000000..4c0870ecc5e9 --- /dev/null +++ b/src/Nethermind/Nethermind.Taiko.Test/L1OriginStoreTests.cs @@ -0,0 +1,224 @@ +// 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}*"); + } + + [Test] + public void Encode_produces_RLP_with_correct_sequence_length( + [Values(false, true)] bool withBuildPayload, + [Values(false, true)] bool withForcedInclusion, + [Values(false, true)] bool withSignature) + { + int[]? buildPayloadArgsId = withBuildPayload ? Enumerable.Range(0, 8).ToArray() : null; + int[]? signature = withSignature ? Enumerable.Range(0, 65).ToArray() : null; + L1Origin origin = new(123, Hash256.Zero, 456, Hash256.Zero, buildPayloadArgsId, withForcedInclusion, signature); + + Rlp encoded = _decoder.Encode(origin); + RlpStream stream = new(encoded.Bytes); + (int prefixLength, int contentLength) = stream.ReadPrefixAndContentLength(); + + contentLength.Should().Be(encoded.Bytes.Length - prefixLength, + "StartSequence must receive content length, not total length"); + } +} + diff --git a/src/Nethermind/Nethermind.Taiko.Test/SurgeGasPriceOracleTests.cs b/src/Nethermind/Nethermind.Taiko.Test/SurgeGasPriceOracleTests.cs index adfa00daaa51..cf4ea2c8d727 100644 --- a/src/Nethermind/Nethermind.Taiko.Test/SurgeGasPriceOracleTests.cs +++ b/src/Nethermind/Nethermind.Taiko.Test/SurgeGasPriceOracleTests.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using NSubstitute; using NUnit.Framework; +using Nethermind.Blockchain; using Nethermind.Blockchain.Find; using Nethermind.Core; using Nethermind.Core.Specs; @@ -12,7 +13,6 @@ using Nethermind.Int256; using Nethermind.JsonRpc.Client; using Nethermind.Logging; -using Nethermind.Serialization.Json; using Nethermind.Taiko.Config; using Nethermind.Taiko.Rpc; @@ -29,7 +29,36 @@ public class SurgeGasPriceOracleTests private static readonly UInt256 MinGasPrice = UInt256.Parse("1000000000"); // 1 Gwei private static string CreatePaddedHex(UInt256 value, int padding = 64) => - value.ToString("x64").PadLeft(padding, '0'); + value.ToString("x").PadLeft(padding, '0'); + + /// + /// Creates a mock CoreState response. + /// CoreState: nextProposalId (word 0), lastProposalBlockId (word 1), lastFinalizedProposalId (word 2), ... + /// + private static string CreateCoreStateResponse(ulong nextProposalId, ulong lastFinalizedProposalId) + { + return "0x" + + CreatePaddedHex(nextProposalId) + // word 0: nextProposalId + CreatePaddedHex(0) + // word 1: lastProposalBlockId + CreatePaddedHex(lastFinalizedProposalId) + // word 2: lastFinalizedProposalId + CreatePaddedHex(0) + // word 3: lastFinalizedTimestamp + CreatePaddedHex(0) + // word 4: lastCheckpointTimestamp + CreatePaddedHex(0); // word 5: lastFinalizedBlockHash + } + + /// + /// Creates a mock Config response with ringBufferSize at word 10. + /// + private static string CreateConfigResponse(ulong ringBufferSize) + { + // Config has 18 fields, ringBufferSize is at word 10 + string response = "0x"; + for (int i = 0; i < 18; i++) + { + response += i == 10 ? CreatePaddedHex(ringBufferSize) : CreatePaddedHex(0); + } + return response; + } [SetUp] public void Setup() @@ -41,7 +70,8 @@ public void Setup() _surgeConfig = new SurgeConfig { TaikoInboxAddress = "0x06a9Ab27c7e2255df1815E6CC0168d7755Feb19a", - GasPriceRefreshTimeoutSeconds = 2 + GasPriceRefreshTimeoutSeconds = 2, + L2GasUsageWindowSize = 5 }; _gasPriceOracle = new SurgeGasPriceOracle( @@ -53,6 +83,31 @@ public void Setup() _surgeConfig); } + private void SetupBlockFinderWithBlocks(long headBlockNumber, long gasUsed = 1000000) + { + Block headBlock = Build.A.Block.WithNumber(headBlockNumber).WithGasUsed(gasUsed).TestObject; + _blockFinder.Head.Returns(headBlock); + + for (long i = headBlockNumber; i >= Math.Max(0, headBlockNumber - _surgeConfig.L2GasUsageWindowSize + 1); i--) + { + _blockFinder.FindBlock(i, BlockTreeLookupOptions.RequireCanonical) + .Returns(Build.A.Block.WithNumber(i).WithGasUsed(gasUsed).TestObject); + } + } + + private void SetupInboxContractMocks(ulong ringBufferSize = 100, ulong nextProposalId = 10, ulong lastFinalizedProposalId = 5) + { + // getConfig() selector without 0x prefix + _l1RpcClient.Post("eth_call", Arg.Is(o => + o.ToString()!.ToLowerInvariant().Contains("c3f909d4")), "latest") + .Returns(CreateConfigResponse(ringBufferSize)); + + // getCoreState() selector without 0x prefix + _l1RpcClient.Post("eth_call", Arg.Is(o => + o.ToString()!.ToLowerInvariant().Contains("6aa6a01a")), "latest") + .Returns(CreateCoreStateResponse(nextProposalId, lastFinalizedProposalId)); + } + [Test] public async ValueTask GetGasPriceEstimate_WhenNoHeadBlock_ReturnsMinGasPrice() { @@ -66,8 +121,7 @@ public async ValueTask GetGasPriceEstimate_WhenNoHeadBlock_ReturnsMinGasPrice() [Test] public async ValueTask GetGasPriceEstimate_WhenL1FeeHistoryFails_ReturnsMinGasPrice() { - Block headBlock = Build.A.Block.WithNumber(1).WithGasUsed(1000000).TestObject; - _blockFinder.Head.Returns(headBlock); + SetupBlockFinderWithBlocks(10); _l1RpcClient.Post("eth_feeHistory", _surgeConfig.FeeHistoryBlockCount, BlockParameter.Latest, null) .Returns(Task.FromResult(null)); @@ -79,8 +133,7 @@ public async ValueTask GetGasPriceEstimate_WhenL1FeeHistoryFails_ReturnsMinGasPr [Test] public async ValueTask GetGasPriceEstimate_WithEmptyFeeHistory_ReturnsMinGasPrice() { - Block headBlock = Build.A.Block.WithNumber(1).WithGasUsed(1000000).TestObject; - _blockFinder.Head.Returns(headBlock); + SetupBlockFinderWithBlocks(10); var feeHistory = new L1FeeHistoryResults { @@ -99,55 +152,47 @@ public async ValueTask GetGasPriceEstimate_WithEmptyFeeHistory_ReturnsMinGasPric [Test] public async ValueTask GetGasPriceEstimate_WithValidL1FeeHistory_CalculatesCorrectGasPrice() { - Block headBlock = Build.A.Block.WithNumber(1).WithGasUsed(1000000).TestObject; - _blockFinder.Head.Returns(headBlock); + SetupInboxContractMocks(); - // Dummy Ethereum L1 fee history - var feeHistory = new L1FeeHistoryResults + // Scenario 1: Low L1 base fee (10 Gwei average) + SetupBlockFinderWithBlocks(10, 1000000); + var lowFeeHistory = new L1FeeHistoryResults { - BaseFeePerGas = - [ - UInt256.Parse("15000000000"), - UInt256.Parse("25000000000"), - UInt256.Parse("35000000000") - ], - BaseFeePerBlobGas = - [ - UInt256.Parse("1000000000"), - UInt256.Parse("1500000000") - ] + BaseFeePerGas = [UInt256.Parse("10000000000")], + BaseFeePerBlobGas = [UInt256.Parse("1000000000")] }; - // Set up the mock to match the exact parameters that will be passed _l1RpcClient.Post("eth_feeHistory", _surgeConfig.FeeHistoryBlockCount, BlockParameter.Latest, null) - .Returns(Task.FromResult(feeHistory)); + .Returns(Task.FromResult(lowFeeHistory)); - // Mock Stats2 returned by getStats2() call to have 2 batches (numBatches=2) - var stats2Response = "0x" + CreatePaddedHex(2) + CreatePaddedHex(0, 192); - _l1RpcClient.Post("eth_call", Arg.Is(o => - o.ToString()!.ToLowerInvariant().Contains("0x26baca1c")), "latest") - .Returns(stats2Response); + var oracleLowFee = new SurgeGasPriceOracle( + _blockFinder, _logManager, _specProvider, MinGasPrice, _l1RpcClient, _surgeConfig); + UInt256 gasPriceLowL1Fee = await oracleLowFee.GetGasPriceEstimate(); - // Mock Batch returned by getBatch(1) call to have lastBlockId=1 - var batchResponse = "0x" + CreatePaddedHex(0) + CreatePaddedHex(1) + CreatePaddedHex(0, 576); - _l1RpcClient.Post("eth_call", Arg.Is(o => - o.ToString()!.ToLowerInvariant().Contains("0x888775d9")), "latest") - .Returns(batchResponse); + // Scenario 2: High L1 base fee (50 Gwei average) + SetupBlockFinderWithBlocks(20, 1000000); + var highFeeHistory = new L1FeeHistoryResults + { + BaseFeePerGas = [UInt256.Parse("50000000000")], + BaseFeePerBlobGas = [UInt256.Parse("1000000000")] + }; - // Mock block finder to return block with gas usage - _blockFinder.FindBlock(1, Arg.Any()) - .Returns(Build.A.Block.WithNumber(1).WithGasUsed(1000000).TestObject); + _l1RpcClient.Post("eth_feeHistory", _surgeConfig.FeeHistoryBlockCount, BlockParameter.Latest, null) + .Returns(Task.FromResult(highFeeHistory)); - UInt256 gasPrice = await _gasPriceOracle.GetGasPriceEstimate(); + var oracleHighFee = new SurgeGasPriceOracle( + _blockFinder, _logManager, _specProvider, MinGasPrice, _l1RpcClient, _surgeConfig); + UInt256 gasPriceHighL1Fee = await oracleHighFee.GetGasPriceEstimate(); - Assert.That(gasPrice, Is.GreaterThan(MinGasPrice)); + // Higher L1 base fee should result in higher L2 gas price + Assert.That(gasPriceHighL1Fee, Is.GreaterThan(gasPriceLowL1Fee)); } [Test] - public async ValueTask GetGasPriceEstimate_WithZeroGasUsed_ReturnsAtleastMinGasPrice() + public async ValueTask GetGasPriceEstimate_WithZeroGasUsed_UsesL2BlockGasTarget() { - Block headBlock = Build.A.Block.WithNumber(1).WithGasUsed(0).TestObject; - _blockFinder.Head.Returns(headBlock); + SetupBlockFinderWithBlocks(10, 0); + SetupInboxContractMocks(); var feeHistory = new L1FeeHistoryResults { @@ -169,25 +214,19 @@ public async ValueTask GetGasPriceEstimate_WithZeroGasUsed_ReturnsAtleastMinGasP UInt256 gasPrice = await _gasPriceOracle.GetGasPriceEstimate(); - Assert.That(gasPrice, Is.GreaterThanOrEqualTo(MinGasPrice)); + Assert.That(gasPrice, Is.GreaterThan(UInt256.Zero)); } [Test] public async ValueTask GetGasPriceEstimate_WithCachedPrice_ReturnsCachedPrice() { - Block headBlock = Build.A.Block.WithNumber(1).WithGasUsed(1000000).TestObject; - _blockFinder.Head.Returns(headBlock); + SetupBlockFinderWithBlocks(10); + SetupInboxContractMocks(); var feeHistory = new L1FeeHistoryResults { - BaseFeePerGas = - [ - UInt256.Parse("20000000000") - ], - BaseFeePerBlobGas = - [ - UInt256.Parse("1000000000") - ] + BaseFeePerGas = [UInt256.Parse("20000000000")], + BaseFeePerBlobGas = [UInt256.Parse("1000000000")] }; _l1RpcClient.Post("eth_feeHistory", _surgeConfig.FeeHistoryBlockCount, BlockParameter.Latest, null) @@ -198,7 +237,6 @@ public async ValueTask GetGasPriceEstimate_WithCachedPrice_ReturnsCachedPrice() // Change the fee history to ensure we're using cache feeHistory.BaseFeePerGas[0] = UInt256.Parse("40000000000"); - // Second call should use cached value UInt256 secondGasPrice = await _gasPriceOracle.GetGasPriceEstimate(); Assert.That(secondGasPrice, Is.EqualTo(firstGasPrice)); @@ -207,90 +245,103 @@ public async ValueTask GetGasPriceEstimate_WithCachedPrice_ReturnsCachedPrice() [Test] public async ValueTask GetGasPriceEstimate_WithTimeout_RefreshesGasPrice() { - Block headBlock = Build.A.Block.WithNumber(1).WithGasUsed(1000000).TestObject; - _blockFinder.Head.Returns(headBlock); + SetupBlockFinderWithBlocks(10); + SetupInboxContractMocks(); var feeHistory = new L1FeeHistoryResults { - BaseFeePerGas = - [ - UInt256.Parse("20000000000") - ], - BaseFeePerBlobGas = - [ - UInt256.Parse("1000000000") - ] + BaseFeePerGas = [UInt256.Parse("20000000000")], + BaseFeePerBlobGas = [UInt256.Parse("1000000000")] }; _l1RpcClient.Post("eth_feeHistory", _surgeConfig.FeeHistoryBlockCount, BlockParameter.Latest, null) .Returns(Task.FromResult(feeHistory)); - // Mock Stats2 returned by getStats2() call to have 2 batches (numBatches=2) - var stats2Response = "0x" + CreatePaddedHex(2) + CreatePaddedHex(0, 192); - _l1RpcClient.Post("eth_call", Arg.Is(o => - o.ToString()!.ToLowerInvariant().Contains("0x26baca1c")), "latest") - .Returns(stats2Response); - - // Mock Batch returned by getBatch(1) call to have lastBlockId=1 - var batchResponse = "0x" + CreatePaddedHex(0) + CreatePaddedHex(1) + CreatePaddedHex(0, 576); - _l1RpcClient.Post("eth_call", Arg.Is(o => - o.ToString()!.ToLowerInvariant().Contains("0x888775d9")), "latest") - .Returns(batchResponse); - - // Mock block finder to return block with gas usage - _blockFinder.FindBlock(1, Arg.Any()) - .Returns(Build.A.Block.WithNumber(1).WithGasUsed(1000000).TestObject); - UInt256 firstGasPrice = await _gasPriceOracle.GetGasPriceEstimate(); - // Wait for the timeout to elapse await Task.Delay(_surgeConfig.GasPriceRefreshTimeoutSeconds * 1000 + 100); - // Change the fee history to a different value feeHistory.BaseFeePerGas[0] = UInt256.Parse("40000000000"); - // Second call should refresh due to timeout, even though head block hasn't changed UInt256 secondGasPrice = await _gasPriceOracle.GetGasPriceEstimate(); - // The gas price should be higher due to the changed L1 base fee Assert.That(secondGasPrice, Is.GreaterThan(firstGasPrice)); } [Test] - [Explicit("This test requires interacting with a live TaikoInbox contract")] - public async ValueTask GetGasPriceEstimate_WithLiveTaikoInboxContract_ReturnsValidGasPrice() + public async ValueTask GetGasPriceEstimate_WhenInboxBufferFull_UsesReducedProposalGas() { - // Create a real RPC client for L1 - var l1RpcClient = new BasicJsonRpcClient( - new Uri("https://eth.llamarpc.com"), - new EthereumJsonSerializer(), - _logManager); - - // Set up the block finder to return a valid block with gas usage for any block ID - _blockFinder.FindBlock(Arg.Any(), Arg.Any()) - .Returns(callInfo => Build.A.Block - .WithNumber(callInfo.Arg()) - .WithGasUsed(1000000) - .TestObject); - - // Create a gas price oracle with the live client - var liveGasPriceOracle = new SurgeGasPriceOracle( - _blockFinder, - _logManager, - _specProvider, - MinGasPrice, - l1RpcClient, - _surgeConfig); + // Create two separate oracles with different inbox buffer states + var feeHistory = new L1FeeHistoryResults + { + BaseFeePerGas = [UInt256.Parse("20000000000")], + BaseFeePerBlobGas = [UInt256.Parse("1000000000")] + }; - // Set up a head block with some gas used - Block headBlock = Build.A.Block.WithNumber(1).WithGasUsed(1000000).TestObject; - _blockFinder.Head.Returns(headBlock); + _l1RpcClient.Post("eth_feeHistory", _surgeConfig.FeeHistoryBlockCount, BlockParameter.Latest, null) + .Returns(Task.FromResult(feeHistory)); + + // First oracle: inbox buffer NOT full (uses FixedProposalGas = 75k) + SetupBlockFinderWithBlocks(10); + SetupInboxContractMocks(ringBufferSize: 100, nextProposalId: 50, lastFinalizedProposalId: 40); + + var oracleNotFull = new SurgeGasPriceOracle( + _blockFinder, _logManager, _specProvider, MinGasPrice, _l1RpcClient, _surgeConfig); + UInt256 gasPriceNotFull = await oracleNotFull.GetGasPriceEstimate(); + + // Second oracle: inbox buffer IS full (uses FixedProposalGasWithFullInboxBuffer = 50k) + SetupBlockFinderWithBlocks(11); + SetupInboxContractMocks(ringBufferSize: 100, nextProposalId: 150, lastFinalizedProposalId: 50); + + var oracleFull = new SurgeGasPriceOracle( + _blockFinder, _logManager, _specProvider, MinGasPrice, _l1RpcClient, _surgeConfig); + UInt256 gasPriceFull = await oracleFull.GetGasPriceEstimate(); + + // When buffer is full, lower proposal gas (50k vs 75k) should result in lower gas price + Assert.That(gasPriceFull, Is.LessThan(gasPriceNotFull)); + } + + [Test] + public async ValueTask GetGasPriceEstimate_ComputesAverageGasFromRecentBlocks() + { + var feeHistory = new L1FeeHistoryResults + { + BaseFeePerGas = [UInt256.Parse("20000000000")], + BaseFeePerBlobGas = [UInt256.Parse("1000000000")] + }; + + _l1RpcClient.Post("eth_feeHistory", _surgeConfig.FeeHistoryBlockCount, BlockParameter.Latest, null) + .Returns(Task.FromResult(feeHistory)); + + SetupInboxContractMocks(); + + // Scenario 1: Low gas usage blocks (average = 100k) + const long headBlockNumber1 = 10; + _blockFinder.Head.Returns(Build.A.Block.WithNumber(headBlockNumber1).WithGasUsed(100000).TestObject); + for (int i = 0; i < _surgeConfig.L2GasUsageWindowSize; i++) + { + _blockFinder.FindBlock(headBlockNumber1 - i, BlockTreeLookupOptions.RequireCanonical) + .Returns(Build.A.Block.WithNumber(headBlockNumber1 - i).WithGasUsed(100000).TestObject); + } + + var oracleLowGas = new SurgeGasPriceOracle( + _blockFinder, _logManager, _specProvider, MinGasPrice, _l1RpcClient, _surgeConfig); + UInt256 gasPriceLowUsage = await oracleLowGas.GetGasPriceEstimate(); + + // Scenario 2: High gas usage blocks (average = 500k) + const long headBlockNumber2 = 20; + _blockFinder.Head.Returns(Build.A.Block.WithNumber(headBlockNumber2).WithGasUsed(500000).TestObject); + for (int i = 0; i < _surgeConfig.L2GasUsageWindowSize; i++) + { + _blockFinder.FindBlock(headBlockNumber2 - i, BlockTreeLookupOptions.RequireCanonical) + .Returns(Build.A.Block.WithNumber(headBlockNumber2 - i).WithGasUsed(500000).TestObject); + } - // Get the gas price estimate - UInt256 gasPrice = await liveGasPriceOracle.GetGasPriceEstimate(); + var oracleHighGas = new SurgeGasPriceOracle( + _blockFinder, _logManager, _specProvider, MinGasPrice, _l1RpcClient, _surgeConfig); + UInt256 gasPriceHighUsage = await oracleHighGas.GetGasPriceEstimate(); - // Verify the gas price is valid - Assert.That(gasPrice, Is.GreaterThan(MinGasPrice)); - Assert.That(gasPrice, Is.LessThan(UInt256.Parse("100000000000"))); // Less than 100 Gwei + // Higher gas usage should result in lower gas price (cost spread over more gas) + Assert.That(gasPriceHighUsage, Is.LessThan(gasPriceLowUsage)); } } diff --git a/src/Nethermind/Nethermind.Taiko.Test/TaikoEngineApiTests.cs b/src/Nethermind/Nethermind.Taiko.Test/TaikoEngineApiTests.cs index 4765b12036bc..eb9a581d29b8 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 7d73311c7355..922f1e9c268f 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(); @@ -78,4 +80,37 @@ public void TestL1OriginById_WithBuildPayloadArgsId() rpc.taiko_l1OriginByID(0).Result.Data.Should().Be(origin); } + + [Test] + public void TestL1OriginById_ValueHash256_EvenLengthHex() + { + IL1OriginStore originStore = Substitute.For(); + TaikoExtendedEthModule rpc = new TaikoExtendedEthModule(new SyncConfig(), originStore); + int expectedLengthInChars = ValueHash256.Length * 2 + 2; + + // Create odd length hash values + var l2BlockHash = new ValueHash256("0x35a48c5b3ee5b1b2a365fcd1aa68c738d1c06474578087a78fa79dd45de6214"); + var l1BlockHash = new ValueHash256("0x2daf7e4b06ca2d3a82c775d9e9ad0c973545a608684146cda0df5f7d71188a5"); + + L1Origin origin = new L1Origin(0, l2BlockHash, 1, l1BlockHash, null); + originStore.ReadL1Origin((UInt256)0).Returns(origin); + + var result = rpc.taiko_l1OriginByID(0).Result.Data; + result.Should().Be(origin); + + // Serialize the RPC result and verify hash values have even-length hex string + var serializer = new Serialization.Json.EthereumJsonSerializer(); + var json = serializer.Serialize(result); + + // Parse the JSON to extract the hash values + var jsonDoc = System.Text.Json.JsonDocument.Parse(json); + var l2BlockHashString = jsonDoc.RootElement.GetProperty("l2BlockHash").GetString(); + var l1BlockHashString = jsonDoc.RootElement.GetProperty("l1BlockHash").GetString(); + + l2BlockHashString.Should().NotBeNull(); + l2BlockHashString!.Length.Should().Be(expectedLengthInChars); + + l1BlockHashString.Should().NotBeNull(); + l1BlockHashString!.Length.Should().Be(expectedLengthInChars); + } } 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 000000000000..f2dd7f08324e --- /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 000000000000..efa388973796 --- /dev/null +++ b/src/Nethermind/Nethermind.Taiko.Test/Tdx/TdxServiceTests.cs @@ -0,0 +1,172 @@ +// 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; + +[Explicit] +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 5c69b7240f8d..b42e5e54ce9e 100644 --- a/src/Nethermind/Nethermind.Taiko.Test/TransactionProcessorTests.cs +++ b/src/Nethermind/Nethermind.Taiko.Test/TransactionProcessorTests.cs @@ -17,7 +17,6 @@ using Nethermind.Blockchain; using Nethermind.Core.Test; using Nethermind.Evm; -using Nethermind.State; using Nethermind.Taiko.TaikoSpec; using FluentAssertions; using Nethermind.Evm.TransactionProcessing; @@ -51,7 +50,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 +61,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 +73,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 +86,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 +94,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 +175,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 +193,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 +219,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 f34533be574c..3ace773efba6 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,30 +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, IEnumerable>>(), - Substitute.For(), - Substitute.For>(), - Substitute.For, IEnumerable>>(), - 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, @@ -156,4 +132,81 @@ 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, IEnumerable>>(), + Substitute.For(), + Substitute.For>(), + Substitute.For, IEnumerable>>(), + 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 30bb9d95bafa..aa0e44fe736b 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/BlockTransactionExecutors/BlockInvalidTxExecutor.cs b/src/Nethermind/Nethermind.Taiko/BlockTransactionExecutors/BlockInvalidTxExecutor.cs index ec2bb591edcb..a5c00d14471f 100644 --- a/src/Nethermind/Nethermind.Taiko/BlockTransactionExecutors/BlockInvalidTxExecutor.cs +++ b/src/Nethermind/Nethermind.Taiko/BlockTransactionExecutors/BlockInvalidTxExecutor.cs @@ -32,7 +32,7 @@ public TxReceipt[] ProcessTransactions(Block block, ProcessingOptions processing block.Transactions[0].IsAnchorTx = true; - using ArrayPoolList correctTransactions = new(block.Transactions.Length); + using ArrayPoolListRef correctTransactions = new(block.Transactions.Length); for (int i = 0; i < block.Transactions.Length; i++) { diff --git a/src/Nethermind/Nethermind.Taiko/BlockTransactionExecutors/TaikoBlockValidationTransactionExecutor.cs b/src/Nethermind/Nethermind.Taiko/BlockTransactionExecutors/TaikoBlockValidationTransactionExecutor.cs index c1645ef8a54b..e27acec4b779 100644 --- a/src/Nethermind/Nethermind.Taiko/BlockTransactionExecutors/TaikoBlockValidationTransactionExecutor.cs +++ b/src/Nethermind/Nethermind.Taiko/BlockTransactionExecutors/TaikoBlockValidationTransactionExecutor.cs @@ -5,7 +5,6 @@ using Nethermind.Consensus.Processing; using Nethermind.Core; using Nethermind.Evm.State; -using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; namespace Nethermind.Taiko.BlockTransactionExecutors; diff --git a/src/Nethermind/Nethermind.Taiko/Config/ISurgeConfig.cs b/src/Nethermind/Nethermind.Taiko/Config/ISurgeConfig.cs index a326d9e1356e..cf4a5ad45ed6 100644 --- a/src/Nethermind/Nethermind.Taiko/Config/ISurgeConfig.cs +++ b/src/Nethermind/Nethermind.Taiko/Config/ISurgeConfig.cs @@ -10,27 +10,33 @@ public interface ISurgeConfig : IConfig [ConfigItem(Description = "The URL of the L1 execution node JSON-RPC API.", DefaultValue = "null")] string? L1EthApiEndpoint { get; set; } - [ConfigItem(Description = "L2 gas per L2 batch for gas price calculation.", DefaultValue = "1000000")] - ulong L2GasPerL2Batch { get; set; } + [ConfigItem(Description = "Number of L2 blocks per batch.", DefaultValue = "1800")] + ulong BlocksPerBatch { get; set; } - [ConfigItem(Description = "Proving cost per L2 batch in wei.", DefaultValue = "800000000000000")] - ulong ProvingCostPerL2Batch { get; set; } + [ConfigItem(Description = "Target blob count per batch.", DefaultValue = "3")] + ulong TargetBlobCount { get; set; } - [ConfigItem(Description = "L1 gas needed for posting a batch as a blob.", DefaultValue = "180000")] - ulong BatchPostingGasWithoutCallData { get; set; } + [ConfigItem(Description = "Target gas per L2 block.", DefaultValue = "40000")] + ulong L2BlockGasTarget { get; set; } - [ConfigItem(Description = "L1 gas needed for posting a batch as calldata.", DefaultValue = "260000")] - ulong BatchPostingGasWithCallData { get; set; } + [ConfigItem(Description = "L1 gas for batch proposal.", DefaultValue = "75000")] + ulong FixedProposalGas { get; set; } - [ConfigItem(Description = "L1 gas needed to post and verify proof.", DefaultValue = "750000")] - ulong ProofPostingGas { get; set; } + [ConfigItem(Description = "L1 gas for batch proposal with full inbox buffer.", DefaultValue = "50000")] + ulong FixedProposalGasWithFullInboxBuffer { get; set; } + + [ConfigItem(Description = "L1 gas for proof verification.", DefaultValue = "30000")] + ulong FixedProvingGas { get; set; } [ConfigItem(Description = "Number of blocks to consider for computing the L1 average base fee.", DefaultValue = "200")] int FeeHistoryBlockCount { get; set; } - [ConfigItem(Description = "Number of recent L2 batches to consider for computing the moving average of gas usage.", DefaultValue = "20")] + [ConfigItem(Description = "Number of recent L2 blocks to consider for computing the moving average of gas usage.", DefaultValue = "20")] int L2GasUsageWindowSize { get; set; } + [ConfigItem(Description = "Estimated offchain proving cost per batch in wei (~$5.5 @ $3000/ETH).", DefaultValue = "1833333333333333")] + ulong EstimatedOffchainProvingCost { get; set; } + [ConfigItem(Description = "The address of the TaikoInbox contract.", DefaultValue = "null")] string? TaikoInboxAddress { get; set; } @@ -45,4 +51,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 f9946e8abc02..c1ef0ebc7353 100644 --- a/src/Nethermind/Nethermind.Taiko/Config/SurgeConfig.cs +++ b/src/Nethermind/Nethermind.Taiko/Config/SurgeConfig.cs @@ -6,16 +6,20 @@ namespace Nethermind.Taiko.Config; public class SurgeConfig : ISurgeConfig { public string? L1EthApiEndpoint { get; set; } - public ulong L2GasPerL2Batch { get; set; } = 1_000_000; - public ulong ProvingCostPerL2Batch { get; set; } = 800_000_000_000_000; - public ulong BatchPostingGasWithoutCallData { get; set; } = 180_000; - public ulong BatchPostingGasWithCallData { get; set; } = 260_000; - public ulong ProofPostingGas { get; set; } = 750_000; + public ulong BlocksPerBatch { get; set; } = 1800; + public ulong TargetBlobCount { get; set; } = 3; + public ulong L2BlockGasTarget { get; set; } = 40_000; + public ulong FixedProposalGas { get; set; } = 75_000; + public ulong FixedProposalGasWithFullInboxBuffer { get; set; } = 50_000; + public ulong FixedProvingGas { get; set; } = 30_000; public int FeeHistoryBlockCount { get; set; } = 200; public int L2GasUsageWindowSize { get; set; } = 20; + public ulong EstimatedOffchainProvingCost { get; set; } = 1_833_333_333_333_333; public string? TaikoInboxAddress { get; set; } public int AverageGasUsagePercentage { get; set; } = 80; 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 63a3f0b0c072..b847f650e079 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/JsonRpcL1StorageProvider.cs b/src/Nethermind/Nethermind.Taiko/JsonRpcL1StorageProvider.cs index fa910552d875..da93254247ee 100644 --- a/src/Nethermind/Nethermind.Taiko/JsonRpcL1StorageProvider.cs +++ b/src/Nethermind/Nethermind.Taiko/JsonRpcL1StorageProvider.cs @@ -2,8 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; -using System.Globalization; using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Int256; diff --git a/src/Nethermind/Nethermind.Taiko/L1Origin.cs b/src/Nethermind/Nethermind.Taiko/L1Origin.cs index c145af481bac..ac0d048e3a92 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, Hash256 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 Hash256 L1BlockHash { get; set; } = l1BlockHash; + 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 4429af90bb95..329a766b3b7a 100644 --- a/src/Nethermind/Nethermind.Taiko/L1OriginDecoder.cs +++ b/src/Nethermind/Nethermind.Taiko/L1OriginDecoder.cs @@ -8,22 +8,33 @@ namespace Nethermind.Taiko; -public class L1OriginDecoder : IRlpStreamDecoder +public sealed class L1OriginDecoder : RlpStreamDecoder { const int BuildPayloadArgsIdLength = 8; + internal const int SignatureLength = 65; - public L1Origin Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override L1Origin DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { (int _, int contentLength) = rlpStream.ReadPrefixAndContentLength(); int itemsCount = rlpStream.PeekNumberOfItemsRemaining(maxSearch: contentLength); 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) @@ -36,33 +47,77 @@ public Rlp Encode(L1Origin? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) return new(rlpStream.Data.ToArray()!); } - public void Encode(RlpStream stream, L1Origin item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, L1Origin item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { - stream.StartSequence(GetLength(item, rlpBehaviors)); + stream.StartSequence(GetContentLength(item, rlpBehaviors)); 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) + { + return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); } - public int GetLength(L1Origin item, RlpBehaviors rlpBehaviors) + private int GetContentLength(L1Origin item, RlpBehaviors rlpBehaviors) { - return Rlp.LengthOfSequence( - Rlp.LengthOf(item.BlockId) + 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.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 2ab8778f43f1..48751db1eef3 100644 --- a/src/Nethermind/Nethermind.Taiko/L1OriginStore.cs +++ b/src/Nethermind/Nethermind.Taiko/L1OriginStore.cs @@ -4,8 +4,6 @@ using System; using System.Buffers; using Autofac.Features.AttributeFilters; -using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Db; using Nethermind.Int256; using Nethermind.Serialization.Rlp; @@ -16,20 +14,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 +53,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 +75,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 9bb243d5810f..d289837db38c 100644 --- a/src/Nethermind/Nethermind.Taiko/Rpc/ITaikoEngineRpcModule.cs +++ b/src/Nethermind/Nethermind.Taiko/Rpc/ITaikoEngineRpcModule.cs @@ -77,4 +77,42 @@ 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(); + + [JsonRpcMethod( + Description = "Returns the L1 origin of the last block for the given batch.", + IsSharable = true, + IsImplemented = true)] + Task> taikoAuth_lastL1OriginByBatchID(UInt256 batchId); + + [JsonRpcMethod( + Description = "Returns the ID of the last block for the given batch.", + IsSharable = true, + IsImplemented = true)] + Task> taikoAuth_lastBlockIDByBatchID(UInt256 batchId); } diff --git a/src/Nethermind/Nethermind.Taiko/Rpc/SurgeGasPriceOracle.cs b/src/Nethermind/Nethermind.Taiko/Rpc/SurgeGasPriceOracle.cs index 691a1123374b..faa079752d87 100644 --- a/src/Nethermind/Nethermind.Taiko/Rpc/SurgeGasPriceOracle.cs +++ b/src/Nethermind/Nethermind.Taiko/Rpc/SurgeGasPriceOracle.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Linq; using System.Threading.Tasks; +using Nethermind.Abi; using Nethermind.Blockchain; using Nethermind.Blockchain.Find; using Nethermind.Core; @@ -15,26 +16,29 @@ using Nethermind.JsonRpc.Modules.Eth.GasPrice; using Nethermind.Logging; using Nethermind.Taiko.Config; -using Nethermind.Abi; namespace Nethermind.Taiko.Rpc; public class SurgeGasPriceOracle : GasPriceOracle { private const string ClassName = nameof(SurgeGasPriceOracle); - private const int BlobSize = (4 * 31 + 3) * 1024 - 4; + private const ulong BlobGasPerBlob = Eip4844Constants.GasPerBlob; - // ABI signatures and encoded function selectors for TaikoInbox - private static readonly AbiSignature GetStats2Signature = new("getStats2"); - private static readonly AbiSignature GetBatchSignature = new("getBatch", AbiType.UInt64); - private static readonly string GetStats2HexData = "0x" + Convert.ToHexString( - AbiEncoder.Instance.Encode(AbiEncodingStyle.IncludeSignature, GetStats2Signature)); + // ABI signatures and encoded function selectors for TaikoInbox contract. + private static readonly AbiSignature GetCoreStateSignature = new("getCoreState"); + private static readonly AbiSignature GetConfigSignature = new("getConfig"); + private static readonly string GetCoreStateHexData = "0x" + Convert.ToHexString( + AbiEncoder.Instance.Encode(AbiEncodingStyle.IncludeSignature, GetCoreStateSignature)); + private static readonly string GetConfigHexData = "0x" + Convert.ToHexString( + AbiEncoder.Instance.Encode(AbiEncodingStyle.IncludeSignature, GetConfigSignature)); private readonly IJsonRpcClient _l1RpcClient; private readonly ISurgeConfig _surgeConfig; - private readonly GasUsageRingBuffer _gasUsageBuffer; private DateTime _lastGasPriceCalculation = DateTime.MinValue; + private ulong _cachedAverageGasUsage; + private ulong _inboxRingBufferSize; + private bool _isInboxRingBufferFull; public SurgeGasPriceOracle( IBlockFinder blockFinder, @@ -46,8 +50,14 @@ public SurgeGasPriceOracle( { _l1RpcClient = l1RpcClient; _surgeConfig = surgeConfig; - _gasUsageBuffer = new GasUsageRingBuffer(_surgeConfig.L2GasUsageWindowSize); - _gasUsageBuffer.Add(_surgeConfig.L2GasPerL2Batch); + _cachedAverageGasUsage = surgeConfig.L2BlockGasTarget; + + if (_logger.IsInfo) + { + _logger.Info($"[{ClassName}] Initialized with L1 endpoint: {surgeConfig.L1EthApiEndpoint}, " + + $"TaikoInbox: {surgeConfig.TaikoInboxAddress}, L2BlockGasTarget: {surgeConfig.L2BlockGasTarget}, " + + $"MinGasPrice: {minGasPrice}"); + } } private UInt256 FallbackGasPrice() => _gasPriceEstimation.LastPrice ?? _minGasPrice; @@ -63,7 +73,7 @@ public override async ValueTask GetGasPriceEstimate() Hash256 headBlockHash = headBlock.Hash!; bool forceRefresh = ForceRefreshGasPrice(); - ulong averageGasUsage; + ulong averageGasUsage = _cachedAverageGasUsage; // Check if the cached price exists. if (_gasPriceEstimation.TryGetPrice(headBlockHash, out UInt256? price)) @@ -74,13 +84,18 @@ public override async ValueTask GetGasPriceEstimate() if (_logger.IsTrace) _logger.Trace($"[{ClassName}] Using cached gas price estimate: {price}"); return price!.Value; } - - // Since the head block has not changed, we can reuse the existing average gas usage (even with force refresh) - averageGasUsage = _gasUsageBuffer.Average; } else { - averageGasUsage = await GetAverageGasUsageAcrossBatches(); + // Since the head block has changed, we need to re-compute the average gas usage and + // update the inbox ring buffer full status + _cachedAverageGasUsage = GetAverageGasUsagePerBlock(); + averageGasUsage = _cachedAverageGasUsage; + + if (!_isInboxRingBufferFull) + { + await UpdateInboxRingBufferFullAsync(); + } } // Get the fee history from the L1 client with RPC @@ -91,27 +106,42 @@ public override async ValueTask GetGasPriceEstimate() return FallbackGasPrice(); } - // Get the latest base fee and blob base fee from the fee history - UInt256 l1BaseFee = feeHistory.BaseFeePerGas[^1]; - UInt256 l1BlobBaseFee = feeHistory.BaseFeePerBlobGas.Length > 0 ? feeHistory.BaseFeePerBlobGas[^1] : UInt256.Zero; - UInt256 l1AverageBaseFee = (UInt256)feeHistory.BaseFeePerGas.Average(fee => (decimal)fee); + // Compute TWAP for L1 base fee and blob base fee + UInt256 twapL1BaseFee = (UInt256)feeHistory.BaseFeePerGas.Average(fee => (decimal)fee); + UInt256 twapBlobBaseFee = feeHistory.BaseFeePerBlobGas.Length > 0 + ? (UInt256)feeHistory.BaseFeePerBlobGas.Average(fee => (decimal)fee) + : UInt256.Zero; + + // Based on the inbox ring buffer status, use the appropriate fixed proposal gas. + UInt256 gasRequiredForProposal = _isInboxRingBufferFull ? + _surgeConfig.FixedProposalGasWithFullInboxBuffer + : _surgeConfig.FixedProposalGas; - // Compute the gas cost to post a batch on L1 - UInt256 costWithCallData = _surgeConfig.BatchPostingGasWithCallData * l1BaseFee; - UInt256 costWithBlobs = _surgeConfig.BatchPostingGasWithoutCallData * l1BaseFee + BlobSize * l1BlobBaseFee; - UInt256 minProposingCost = UInt256.Min(costWithCallData, costWithBlobs); + // Compute submission cost per batch + UInt256 submissionCostPerBatch = (gasRequiredForProposal + _surgeConfig.FixedProvingGas) * twapL1BaseFee + + _surgeConfig.TargetBlobCount * BlobGasPerBlob * twapBlobBaseFee + + _surgeConfig.EstimatedOffchainProvingCost; - UInt256 proofPostingCost = _surgeConfig.ProofPostingGas * UInt256.Max(l1BaseFee, l1AverageBaseFee); + // Compute submission cost per block + UInt256 submissionCostPerBlock = submissionCostPerBatch / _surgeConfig.BlocksPerBatch; // Reduce the average gas usage to prevent upward trend averageGasUsage = averageGasUsage * (ulong)_surgeConfig.AverageGasUsagePercentage / 100; - UInt256 gasPriceEstimate = (minProposingCost + proofPostingCost + _surgeConfig.ProvingCostPerL2Batch) / - Math.Max(averageGasUsage, _surgeConfig.L2GasPerL2Batch); + UInt256 gasPriceEstimate = submissionCostPerBlock / + Math.Max(averageGasUsage, _surgeConfig.L2BlockGasTarget); // 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); @@ -120,8 +150,8 @@ public override async ValueTask GetGasPriceEstimate() if (_logger.IsDebug) { _logger.Debug($"[{ClassName}] Calculated new gas price estimate: {adjustedGasPriceEstimate}, " + - $"L1 Base Fee: {l1BaseFee}, L1 Blob Base Fee: {l1BlobBaseFee}, " + - $"L1 Average Base Fee: {l1AverageBaseFee}, Average Gas Usage: {averageGasUsage}, " + + $"TWAP L1 Base Fee: {twapL1BaseFee}, TWAP Blob Base Fee: {twapBlobBaseFee}, " + + $"Submission Cost Per Block: {submissionCostPerBlock}, Average Gas Usage: {averageGasUsage}, " + $"Adjusted with boost base fee percentage of {_surgeConfig.BoostBaseFeePercentage}% " + $"and sharing percentage of {_surgeConfig.SharingPercentage}%"); } @@ -154,83 +184,103 @@ private bool ForceRefreshGasPrice() } /// - /// Get the average gas usage across L2GasUsageWindowSize batches. - /// It uses the TaikoInbox contract to get the total number of blocks in the latest proposed batch. + /// Get the average gas usage per block across L2GasUsageWindowSize recent blocks. /// - private async ValueTask GetAverageGasUsageAcrossBatches() + private ulong GetAverageGasUsagePerBlock() { - // Get the current batch information - ulong? numBatches = await GetNumBatches(); - if (numBatches is null or < 1) - { - if (_logger.IsTrace) _logger.Trace($"[{ClassName}] Failed to get numBatches"); - return 0; - } - - // Get the latest proposed batch and the previous batch to compute the start and end block ids - ulong? currentBatchLastBlockId = numBatches > 1 ? await GetLastBlockId(numBatches.Value - 1) : 0; - ulong? previousBatchLastBlockId = numBatches > 2 ? await GetLastBlockId(numBatches.Value - 2) : 0; - - if (currentBatchLastBlockId == null || previousBatchLastBlockId == null) + Block? headBlock = _blockFinder.Head; + if (headBlock is null) { - if (_logger.IsTrace) _logger.Trace($"[{ClassName}] Failed to get batch lastBlockId"); - return 0; + if (_logger.IsTrace) _logger.Trace($"[{ClassName}] No head block available for gas usage tracking"); + return _surgeConfig.L2BlockGasTarget; } - ulong startBlockId = previousBatchLastBlockId.Value == 0 ? 0 : previousBatchLastBlockId.Value + 1; - ulong endBlockId = currentBatchLastBlockId.Value; - - // Calculate total gas used for the batch ulong totalGasUsed = 0; - for (ulong blockId = startBlockId; blockId <= endBlockId; blockId++) + int count = 0; + long currentBlockNumber = headBlock.Number; + + while (count < _surgeConfig.L2GasUsageWindowSize && currentBlockNumber >= 0) { - Block? block = _blockFinder.FindBlock((long)blockId, BlockTreeLookupOptions.RequireCanonical); + Block? block = _blockFinder.FindBlock(currentBlockNumber, BlockTreeLookupOptions.RequireCanonical); if (block != null) { totalGasUsed += (ulong)block.GasUsed; + count++; } + currentBlockNumber--; } - // Record the batch's gas usage and compute the average - _gasUsageBuffer.Add(totalGasUsed); + ulong average = totalGasUsed > 0 ? totalGasUsed / (ulong)count : _surgeConfig.L2BlockGasTarget; if (_logger.IsTrace) { - _logger.Trace($"[{ClassName}] Total gas used: {totalGasUsed}, " + - $"startBlockId: {startBlockId}, endBlockId: {endBlockId}, " + - $"Average gas usage: {_gasUsageBuffer.Average}, " + - $"L2GasUsageWindowSize: {_surgeConfig.L2GasUsageWindowSize}, " + - $"numBatches: {numBatches}"); + _logger.Trace($"[{ClassName}] Average gas usage: {average}, " + + $"Head block: {headBlock.Number}, Blocks sampled: {count}"); + } + + return average; + } + + /// + /// Update the inbox ring buffer full status. + /// Ring buffer is full when: nextProposalId - lastFinalizedProposalId >= ringBufferSize + /// + private async ValueTask UpdateInboxRingBufferFullAsync() + { + if (_inboxRingBufferSize == 0) + { + ulong? ringBufferSize = await GetInboxRingBufferSize(); + if (ringBufferSize is null) return; + _inboxRingBufferSize = ringBufferSize.Value; } - return _gasUsageBuffer.Average; + ulong? unfinalizedCount = await GetUnfinalizedProposalCount(); + if (unfinalizedCount is null) return; + + _isInboxRingBufferFull = unfinalizedCount.Value >= _inboxRingBufferSize; } /// - /// Get the number of batches from the TaikoInbox contract's getStats2() function. + /// Get ringBufferSize from the TaikoInbox contract's getConfig() function. + /// Config struct has ringBufferSize at word index 10 (0-indexed). /// - private async ValueTask GetNumBatches() + private async ValueTask GetInboxRingBufferSize() { - string? response = await CallTaikoInboxFunction(GetStats2HexData); + string? response = await CallTaikoInboxFunction(GetConfigHexData); - if (string.IsNullOrEmpty(response) || response.Length < 66) return null; + // ringBufferSize is at word 10: offset = 2 + 10*64 = 642, length = 64 + if (string.IsNullOrEmpty(response) || response.Length < 706) + { + if (_logger.IsDebug) _logger.Debug($"[{ClassName}] Failed to get config, response length: {response?.Length}"); + return null; + } - // Extract the first 32 bytes (64 hex chars) after "0x" which contains NumBatches - return ulong.Parse(response[2..66], NumberStyles.HexNumber); + return ulong.Parse(response[642..706], NumberStyles.HexNumber); } /// - /// Get the last block id from the TaikoInbox contract's getBatch(uint64) function. + /// Get the count of unfinalized proposals (nextProposalId - lastFinalizedProposalId) from getCoreState(). + /// CoreState struct layout: + /// Word 0: nextProposalId (uint48) + /// Word 2: lastFinalizedProposalId (uint48) /// - private async ValueTask GetLastBlockId(ulong batchId) + private async ValueTask GetUnfinalizedProposalCount() { - byte[] encodedData = AbiEncoder.Instance.Encode(AbiEncodingStyle.IncludeSignature, GetBatchSignature, batchId); - string? response = await CallTaikoInboxFunction("0x" + Convert.ToHexString(encodedData)); + string? response = await CallTaikoInboxFunction(GetCoreStateHexData); + + // Need at least 3 fields: 2 + 3*64 = 194 chars + if (string.IsNullOrEmpty(response) || response.Length < 194) + { + if (_logger.IsDebug) _logger.Debug($"[{ClassName}] Failed to get core state, response length: {response?.Length}"); + return null; + } - if (string.IsNullOrEmpty(response) || response.Length < 130) return null; + // nextProposalId at [2..66] + ulong nextProposalId = ulong.Parse(response[2..66], NumberStyles.HexNumber); + // lastFinalizedProposalId at [130..194] + ulong lastFinalizedProposalId = ulong.Parse(response[130..194], NumberStyles.HexNumber); - // Extract the second 32 bytes (64 hex chars) after "0x" which contains LastBlockId - return ulong.Parse(response[66..130], NumberStyles.HexNumber); + return nextProposalId - lastFinalizedProposalId; } /// @@ -248,47 +298,11 @@ private async ValueTask GetAverageGasUsageAcrossBatches() } catch (Exception ex) { - if (_logger.IsTrace) _logger.Trace($"[{ClassName}] Contract call to TaikoInbox with data: {data} failed: {ex.Message}"); + if (_logger.IsDebug) _logger.Debug($"[{ClassName}] Contract call to TaikoInbox with data: {data} failed: {ex.Message}"); return null; } } - /// - /// A fixed-size ring buffer for tracking gas usage and computing moving averages. - /// - /// +----+----+----+----+ - /// | A | B | C | D | - /// +----+----+----+----+ - /// ^ - /// insertAt = 1 (next insert location) - /// average = (A + B + C + D) / 4 - /// - private sealed class GasUsageRingBuffer(int capacity) - { - private readonly ulong[] _buffer = new ulong[capacity]; - private int _insertAt; - private int _numItems; - private ulong _sum; - - public ulong Average => _numItems == 0 ? 0 : _sum / (ulong)_numItems; - - public void Add(ulong gasUsed) - { - // If the buffer is full, overwrite the oldest value - if (_numItems == _buffer.Length) - { - _sum -= _buffer[_insertAt]; - } - else - { - _numItems++; - } - - _buffer[_insertAt] = gasUsed; - _sum += _buffer[_insertAt]; - _insertAt = (_insertAt + 1) % _buffer.Length; - } - } } /// diff --git a/src/Nethermind/Nethermind.Taiko/Rpc/TaikoEngineRpcModule.cs b/src/Nethermind/Nethermind.Taiko/Rpc/TaikoEngineRpcModule.cs index d50e5657eca2..6b187ffc0b32 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,8 @@ using Nethermind.Serialization.Rlp; using Nethermind.TxPool; using Nethermind.Evm.State; +using Nethermind.Taiko.Config; +using static Nethermind.Taiko.TaikoBlockValidator; namespace Nethermind.Taiko.Rpc; @@ -46,7 +47,7 @@ public class TaikoEngineRpcModule(IAsyncHandler getPa IHandler transitionConfigurationHandler, IHandler, IEnumerable> capabilitiesHandler, IAsyncHandler> getBlobsHandler, - IAsyncHandler?> getBlobsHandlerV2, + IAsyncHandler?> getBlobsHandlerV2, IEngineRequestsTracker engineRequestsTracker, ISpecProvider specProvider, GCKeeper gcKeeper, @@ -55,7 +56,8 @@ public class TaikoEngineRpcModule(IAsyncHandler getPa IBlockFinder blockFinder, IShareableTxProcessorSource txProcessorSource, IRlpStreamDecoder txDecoder, - IL1OriginStore l1OriginStore) : + IL1OriginStore l1OriginStore, + ISurgeConfig surgeConfig) : EngineRpcModule(getPayloadHandlerV1, getPayloadHandlerV2, getPayloadHandlerV3, @@ -74,6 +76,11 @@ public class TaikoEngineRpcModule(IAsyncHandler getPa gcKeeper, logManager), ITaikoEngineRpcModule { + private const int MaxBatchLookupBlocks = 192 * 1024; + + private static readonly ResultWrapper BlockIdNotFound = ResultWrapper.Fail("not found"); + private static readonly ResultWrapper BlockIdLookbackExceeded = ResultWrapper.Fail("lookback limit exceeded"); + public Task> engine_forkchoiceUpdatedV1(ForkchoiceStateV1 forkchoiceState, TaikoPayloadAttributes? payloadAttributes = null) { return base.engine_forkchoiceUpdatedV1(forkchoiceState, payloadAttributes); @@ -111,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) { @@ -207,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 { @@ -258,7 +275,7 @@ struct Batch(ulong maxBytes, int transactionsListCapacity, IRlpStreamDecoder Transactions { get; } = new ArrayPoolList(transactionsListCapacity); + public ArrayPoolList Transactions { get; } = new(transactionsListCapacity); public bool TryAddTx(Transaction tx) { @@ -346,4 +363,120 @@ 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 Task> taikoAuth_lastL1OriginByBatchID(UInt256 batchId) + { + UInt256? blockId = l1OriginStore.ReadBatchToLastBlockID(batchId); + if (blockId is null) + { + blockId = GetLastBlockByBatchId(batchId); + if (blockId is null) + { + return TaikoExtendedEthModule.L1OriginNotFound; + } + } + + L1Origin? origin = l1OriginStore.ReadL1Origin(blockId.Value); + + return origin is null ? TaikoExtendedEthModule.L1OriginNotFound : ResultWrapper.Success(origin); + } + + public Task> taikoAuth_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. + /// The block ID if found, or null if not found or lookback limit exceeded. + private UInt256? GetLastBlockByBatchId(UInt256 batchId) + { + Block? currentBlock = blockFinder.Head; + int lookbackCount = 0; + + while (currentBlock is not null && + currentBlock.Transactions.Length > 0 && + HasAnchorV4Prefix(currentBlock.Transactions[0].Data)) + { + if (currentBlock.Number == 0) + { + break; + } + + lookbackCount++; + if (lookbackCount > MaxBatchLookupBlocks) + { + return null; + } + + // Read L1Origin to check if this is a preconfirmation block + L1Origin? l1Origin = l1OriginStore.ReadL1Origin((UInt256)currentBlock.Number); + + // Skip preconfirmation blocks + if (l1Origin is not null && l1Origin.IsPreconfBlock) + { + currentBlock = blockFinder.FindBlock(currentBlock.Number - 1); + continue; + } + + 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]); + } + + /// + 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 d1348b4a2655..e88f7e39fb9d 100644 --- a/src/Nethermind/Nethermind.Taiko/Rpc/TaikoExtendedEthModule.cs +++ b/src/Nethermind/Nethermind.Taiko/Rpc/TaikoExtendedEthModule.cs @@ -12,7 +12,7 @@ public class TaikoExtendedEthModule( ISyncConfig syncConfig, IL1OriginStore l1OriginStore) : ITaikoExtendedEthRpcModule { - private static readonly ResultWrapper NotFound = ResultWrapper.Fail("not found"); + internal static readonly ResultWrapper L1OriginNotFound = ResultWrapper.Fail("not found"); public Task> taiko_getSyncMode() => ResultWrapper.Success(syncConfig switch { @@ -25,18 +25,18 @@ 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); } } diff --git a/src/Nethermind/Nethermind.Taiko/Rpc/TaikoForkchoiceUpdatedHandler.cs b/src/Nethermind/Nethermind.Taiko/Rpc/TaikoForkchoiceUpdatedHandler.cs index e03907f6a889..f8e6e0cef02b 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 6fb161f78486..605262865479 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 76801ec88418..8b4a63f9e996 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 0236e250e187..16665ad454fc 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,230 @@ 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) + { + // Skip EIP-4396 verification due to unknown ancestor + if (_logger.IsWarn) + { + _logger.Warn($"Skipping EIP-4396 verification due to unknown ancestor, parent: {parent.Hash}, number: {parent.Number}"); + } + return true; + } + 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 90a48489e412..e5344eaf684b 100644 --- a/src/Nethermind/Nethermind.Taiko/TaikoPayloadPreparationService.cs +++ b/src/Nethermind/Nethermind.Taiko/TaikoPayloadPreparationService.cs @@ -11,7 +11,6 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Evm.State; -using Nethermind.Evm.Tracing; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Merge.Plugin.BlockProduction; @@ -59,6 +58,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 +72,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; }); @@ -124,6 +144,7 @@ private Transaction[] BuildTransactions(TaikoPayloadAttributes payloadAttributes int transactionsCheck = rlpStream.Position + transactionsSequenceLength; int txCount = rlpStream.PeekNumberOfItemsRemaining(transactionsCheck); + rlpStream.GuardLimit(txCount); Transaction[] transactions = new Transaction[txCount]; int txIndex = 0; diff --git a/src/Nethermind/Nethermind.Taiko/TaikoPlugin.cs b/src/Nethermind/Nethermind.Taiko/TaikoPlugin.cs index ffab0b121f30..ea38ee57e1c4 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 e82cb75209f2..14ce81ac0a9b 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 47ed268fd4b2..240e17776a14 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 56d5e791c499..6e426f062005 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 127e790b9f0a..408b7b48ed46 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 7cbd49af3271..db9b697f23e2 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 b396bfaa51e4..fc8e49720380 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 000000000000..ac5942e1fd48 --- /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 000000000000..0c86bff97950 --- /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 000000000000..6b1c514c7f53 --- /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 000000000000..753f1dbde4b6 --- /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 000000000000..d05dc3b12a93 --- /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 000000000000..1edaaa04ad29 --- /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 000000000000..3e5d4b4d659a --- /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 000000000000..dcc8f6514443 --- /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 000000000000..38de08a61a7d --- /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 000000000000..01cda7282427 --- /dev/null +++ b/src/Nethermind/Nethermind.Taiko/Tdx/TdxService.cs @@ -0,0 +1,227 @@ +// 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; + +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 000000000000..999fabaa0f41 --- /dev/null +++ b/src/Nethermind/Nethermind.Taiko/Tdx/TdxsClient.cs @@ -0,0 +1,90 @@ +// 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; + +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) : ITdxsClient +{ + + 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.Test.Runner/BlockchainTestStreamingTracer.cs b/src/Nethermind/Nethermind.Test.Runner/BlockchainTestStreamingTracer.cs new file mode 100644 index 000000000000..d664f25e6b55 --- /dev/null +++ b/src/Nethermind/Nethermind.Test.Runner/BlockchainTestStreamingTracer.cs @@ -0,0 +1,204 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.IO; +using System.Text; +using System.Text.Json; +using Ethereum.Test.Base; +using Nethermind.Blockchain.Tracing.GethStyle; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; +using Nethermind.Evm.Tracing; +using Nethermind.Int256; + +namespace Nethermind.Test.Runner; + +/// +/// Streaming tracer for blockchain tests that writes all traces to stderr. +/// Compatible with go-ethereum's block test tracing output format. +/// Outputs consolidated traces across all blocks and transactions in a single stream. +/// +public class BlockchainTestStreamingTracer(GethTraceOptions options, Stream? output = null) : ITestBlockTracer, IDisposable +{ + private static readonly byte[] _newLine = Encoding.UTF8.GetBytes(Environment.NewLine); + private readonly Stream _output = output ?? Console.OpenStandardError(); + private readonly GethTraceOptions _options = options ?? throw new ArgumentNullException(nameof(options)); + private GethLikeTxFileTracer? _currentTxTracer; + + // Track metrics for test end marker + private int _transactionCount; + private int _blockCount; + private long _totalGasUsed; + + public bool IsTracingRewards => false; + + public void ReportReward(Address author, string rewardType, UInt256 rewardValue) + { + // Not tracing rewards in block test mode + } + + public void StartNewBlockTrace(Block block) + { + // No-op: we write continuously to the same stream across all blocks + } + + public ITxTracer StartNewTxTrace(Transaction? tx) + { + _currentTxTracer = new GethLikeTxFileTracer(WriteTraceEntry, _options); + return _currentTxTracer; + } + + public void EndTxTrace() + { + if (_currentTxTracer is null) return; + + try + { + GethLikeTxTrace? trace = _currentTxTracer.BuildResult(); + + // Write the final summary line for this transaction + using var writer = new Utf8JsonWriter(_output, new JsonWriterOptions { Indented = false }); + + writer.WriteStartObject(); + writer.WritePropertyName("output"); + writer.WriteStringValue(trace.ReturnValue.ToHexString(true)); + writer.WritePropertyName("gasUsed"); + writer.WriteStringValue($"0x{trace.Gas:x}"); + writer.WriteEndObject(); + + writer.Flush(); + + _output.Write(_newLine); + + // Track metrics for end marker + _transactionCount++; + _totalGasUsed += trace.Gas; + } + finally + { + _currentTxTracer = null; + } + } + + public void EndBlockTrace() + { + // Track block count for end marker + _blockCount++; + } + + /// + /// Writes a JSONL-compliant test end marker to the trace stream. + /// This MUST be the last line written to stderr for the test. + /// Format: {"testEnd":{"name":"...","pass":bool,"fork":"...","v":1,...}} + /// + public void TestFinished(string testName, bool pass, IReleaseSpec spec, TimeSpan? duration, Hash256? headStateRoot) + { + using var writer = new Utf8JsonWriter(_output, new JsonWriterOptions + { + Indented = false // Critical: Single line for JSONL compliance + }); + + writer.WriteStartObject(); + writer.WritePropertyName("testEnd"); + writer.WriteStartObject(); + + // Required fields + writer.WriteString("name", testName); + writer.WriteBoolean("pass", pass); + writer.WriteString("fork", spec.ToString()); + writer.WriteNumber("v", 1); + + // Optional fields (only if available) + if (duration.HasValue) + writer.WriteNumber("d", Math.Round(duration.Value.TotalSeconds, 3)); + + if (_totalGasUsed > 0) + writer.WriteString("gasUsed", $"0x{_totalGasUsed:x}"); + + if (_transactionCount > 0) + writer.WriteNumber("txs", _transactionCount); + + if (_blockCount > 0) + writer.WriteNumber("blocks", _blockCount); + + if (headStateRoot is not null) + writer.WriteString("root", headStateRoot.ToString()); + + writer.WriteEndObject(); // testEnd + writer.WriteEndObject(); // root + + writer.Flush(); + + // Write single line with newline - MUST be the last line in trace + _output.Write(_newLine); + _output.Flush(); // Critical: ensure written before process exit + } + + private void WriteTraceEntry(GethTxFileTraceEntry entry) + { + using var writer = new Utf8JsonWriter(_output, new JsonWriterOptions { Indented = false }); + + // Write trace entry (same format as GethLikeTxTraceJsonLinesConverter) + writer.WriteStartObject(); + + writer.WritePropertyName("pc"); + writer.WriteNumberValue(entry.ProgramCounter); + + writer.WritePropertyName("op"); + writer.WriteNumberValue((byte)entry.OpcodeRaw!); + + writer.WritePropertyName("gas"); + writer.WriteStringValue($"0x{entry.Gas:x}"); + + writer.WritePropertyName("gasCost"); + writer.WriteStringValue($"0x{entry.GasCost:x}"); + + writer.WritePropertyName("memSize"); + writer.WriteNumberValue(entry.MemorySize ?? 0UL); + + if ((entry.Memory?.Length ?? 0) != 0) + { + var memory = string.Concat(entry.Memory); + writer.WritePropertyName("memory"); + writer.WriteStringValue($"0x{memory}"); + } + + if (entry.Stack is not null) + { + writer.WritePropertyName("stack"); + writer.WriteStartArray(); + foreach (var s in entry.Stack) + writer.WriteStringValue(s); + writer.WriteEndArray(); + } + + writer.WritePropertyName("depth"); + writer.WriteNumberValue(entry.Depth); + + writer.WritePropertyName("refund"); + writer.WriteNumberValue(entry.Refund ?? 0L); + + writer.WritePropertyName("opName"); + writer.WriteStringValue(entry.Opcode); + + if (entry.Error is not null) + { + writer.WritePropertyName("error"); + writer.WriteStringValue(entry.Error); + } + + writer.WriteEndObject(); + writer.Flush(); + + _output.Write(_newLine); + } + + public void Dispose() + { + // Don't dispose of Console.Error, but flush it + _output.Flush(); + } +} diff --git a/src/Nethermind/Nethermind.Test.Runner/BlockchainTestsRunner.cs b/src/Nethermind/Nethermind.Test.Runner/BlockchainTestsRunner.cs index 0856b9002fce..a596548ec393 100644 --- a/src/Nethermind/Nethermind.Test.Runner/BlockchainTestsRunner.cs +++ b/src/Nethermind/Nethermind.Test.Runner/BlockchainTestsRunner.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Text.RegularExpressions; using System.Threading.Tasks; using Ethereum.Test.Base; @@ -11,28 +10,39 @@ namespace Nethermind.Test.Runner; -public class BlockchainTestsRunner : BlockchainTestBase, IBlockchainTestRunner +public class BlockchainTestsRunner( + ITestSourceLoader testsSource, + string? filter, + ulong chainId, + bool trace = false, + bool traceMemory = false, + bool traceNoStack = false) + : BlockchainTestBase, IBlockchainTestRunner { - private readonly ConsoleColor _defaultColour; - private readonly ITestSourceLoader _testsSource; - private readonly string? _filter; - private readonly ulong _chainId; - - public BlockchainTestsRunner(ITestSourceLoader testsSource, string? filter, ulong chainId) - { - _testsSource = testsSource ?? throw new ArgumentNullException(nameof(testsSource)); - _defaultColour = Console.ForegroundColor; - _filter = filter; - _chainId = chainId; - } + private readonly ConsoleColor _defaultColor = Console.ForegroundColor; + private readonly ITestSourceLoader _testsSource = testsSource ?? throw new ArgumentNullException(nameof(testsSource)); public async Task> RunTestsAsync() { - List testResults = new(); - IEnumerable tests = _testsSource.LoadTests(); - foreach (BlockchainTest test in tests) + List testResults = []; + IEnumerable tests = _testsSource.LoadTests(); + foreach (EthereumTest loadedTest in tests) { - if (_filter is not null && !Regex.Match(test.Name, $"^({_filter})").Success) + if (loadedTest as FailedToLoadTest is not null) + { + WriteRed(loadedTest.LoadFailure); + testResults.Add(new EthereumTestResult(loadedTest.Name, loadedTest.LoadFailure)); + continue; + } + + // Create a streaming tracer once for all tests if tracing is enabled + using BlockchainTestStreamingTracer? tracer = trace + ? new BlockchainTestStreamingTracer(new() { EnableMemory = traceMemory, DisableStack = traceNoStack }) + : null; + + BlockchainTest test = loadedTest as BlockchainTest; + + if (filter is not null && test.Name is not null && !Regex.Match(test.Name, $"^({filter})").Success) continue; Setup(); @@ -44,8 +54,9 @@ public async Task> RunTestsAsync() } else { - test.ChainId = _chainId; - EthereumTestResult result = await RunTest(test); + test.ChainId = chainId; + + EthereumTestResult result = await RunTest(test, tracer: tracer); testResults.Add(result); if (result.Pass) WriteGreen("PASS"); @@ -61,13 +72,13 @@ 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.Test.Runner/Dockerfile b/src/Nethermind/Nethermind.Test.Runner/Dockerfile index 95b46fea224f..056984638b8d 100644 --- a/src/Nethermind/Nethermind.Test.Runner/Dockerfile +++ b/src/Nethermind/Nethermind.Test.Runner/Dockerfile @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited # SPDX-License-Identifier: LGPL-3.0-only -FROM mcr.microsoft.com/dotnet/sdk:9.0-noble AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0-noble AS build COPY src/Nethermind src/Nethermind COPY Directory.*.props . @@ -9,7 +9,7 @@ COPY nuget.config . RUN dotnet publish src/Nethermind/Nethermind.Test.Runner -c release -o /publish --sc false -FROM mcr.microsoft.com/dotnet/aspnet:9.0-noble +FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble WORKDIR /nethermind diff --git a/src/Nethermind/Nethermind.Test.Runner/Program.cs b/src/Nethermind/Nethermind.Test.Runner/Program.cs index 2e5fad349949..8c5c6428d57d 100644 --- a/src/Nethermind/Nethermind.Test.Runner/Program.cs +++ b/src/Nethermind/Nethermind.Test.Runner/Program.cs @@ -28,16 +28,16 @@ public class Options public static Option EofTest { get; } = new("--eofTest", "-e") { Description = "Set test as eofTest. if not, it will be by default assumed a state test." }; public static Option TraceAlways { get; } = - new("--trace", "-t") { Description = "Set to always trace (by default traces are only generated for failing tests). [Only for State Test]" }; + new("--trace", "-t") { Description = "Set to always trace (by default traces are only generated for failing tests)." }; public static Option TraceNever { get; } = new("--neverTrace", "-n") { Description = "Set to never trace (by default traces are only generated for failing tests). [Only for State Test]" }; public static Option ExcludeMemory { get; } = - new("--memory", "-m") { Description = "Exclude memory trace. [Only for State Test]" }; + new("--memory", "-m") { Description = "Exclude memory trace." }; public static Option ExcludeStack { get; } = - new("--stack", "-s") { Description = "Exclude stack trace. [Only for State Test]" }; + new("--stack", "-s") { Description = "Exclude stack trace." }; public static Option Wait { get; } = new("--wait", "-w") { Description = "Wait for input after the test run." }; @@ -94,16 +94,32 @@ private static async Task Run(ParseResult parseResult, CancellationToken ca while (!string.IsNullOrWhiteSpace(input)) { if (parseResult.GetValue(Options.BlockTest)) - await RunBlockTest(input, source => new BlockchainTestsRunner(source, parseResult.GetValue(Options.Filter), chainId)); + { + await RunBlockTest(input, source => new BlockchainTestsRunner( + source, + parseResult.GetValue(Options.Filter), + chainId, + parseResult.GetValue(Options.TraceAlways), + !parseResult.GetValue(Options.ExcludeMemory), + parseResult.GetValue(Options.ExcludeStack))); + } else if (parseResult.GetValue(Options.EofTest)) - RunEofTest(input, source => new EofTestsRunner(source, parseResult.GetValue(Options.Filter))); + { + RunEofTest(input, source => new EofTestsRunner( + source, + parseResult.GetValue(Options.Filter))); + } else - RunStateTest(input, source => new StateTestsRunner(source, whenTrace, + { + RunStateTest(input, source => new StateTestsRunner( + source, + whenTrace, !parseResult.GetValue(Options.ExcludeMemory), !parseResult.GetValue(Options.ExcludeStack), chainId, parseResult.GetValue(Options.Filter), parseResult.GetValue(Options.EnableWarmup))); + } if (!parseResult.GetValue(Options.Stdin)) diff --git a/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs b/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs index f332f113489c..bc89ff38ecff 100644 --- a/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs +++ b/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs @@ -10,6 +10,7 @@ using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; +using Nethermind.Evm.CodeAnalysis; namespace Nethermind.Test.Runner; @@ -54,7 +55,7 @@ public void StartOperation(int pc, Instruction opcode, long gas, in ExecutionEnv { _gasAlreadySetForCurrentOp = false; _traceEntry = new StateTestTxTraceEntry(); - _traceEntry.Pc = pc + env.CodeInfo.PcOffset(); + _traceEntry.Pc = pc + (env.CodeInfo is EofCodeInfo eofCodeInfo ? eofCodeInfo.PcOffset() : 0); _traceEntry.Section = codeSection; _traceEntry.Operation = (byte)opcode; _traceEntry.OperationName = opcode.GetName(); diff --git a/src/Nethermind/Nethermind.Trie.Benchmark/CacheBenchmark.cs b/src/Nethermind/Nethermind.Trie.Benchmark/CacheBenchmark.cs index 1d17ea690862..ecd63cccdf24 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/CacheTests.cs b/src/Nethermind/Nethermind.Trie.Test/CacheTests.cs index 8891482ff7f4..02f361be5b42 100644 --- a/src/Nethermind/Nethermind.Trie.Test/CacheTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/CacheTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.Trie.Test { [TestFixture] + [Parallelizable(ParallelScope.All)] public class CacheTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs b/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs index 522e97765078..c8a8bc507a07 100644 --- a/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs @@ -1,11 +1,13 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Linq; using NUnit.Framework; namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class HexPrefixTests { [TestCase(false, (byte)3, (byte)19)] @@ -95,4 +97,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/NibbleTests.cs b/src/Nethermind/Nethermind.Trie.Test/NibbleTests.cs index 1338eafa0183..a575cb2ddbbd 100644 --- a/src/Nethermind/Nethermind.Trie.Test/NibbleTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/NibbleTests.cs @@ -6,6 +6,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class NibbleTests { private readonly byte[][] _hexEncoding = diff --git a/src/Nethermind/Nethermind.Trie.Test/NodeStorageFactoryTests.cs b/src/Nethermind/Nethermind.Trie.Test/NodeStorageFactoryTests.cs index 5e0308ea2a5c..740048565d78 100644 --- a/src/Nethermind/Nethermind.Trie.Test/NodeStorageFactoryTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/NodeStorageFactoryTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class NodeStorageFactoryTests { [TestCase(INodeStorage.KeyScheme.Hash)] diff --git a/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs b/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs index 72caff99b391..c212d15a8e8e 100644 --- a/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs @@ -13,31 +13,25 @@ namespace Nethermind.Trie.Test; [TestFixture(INodeStorage.KeyScheme.Hash)] [TestFixture(INodeStorage.KeyScheme.HalfPath)] -public class NodeStorageTests +[Parallelizable(ParallelScope.All)] +public class NodeStorageTests(INodeStorage.KeyScheme currentKeyScheme) { - private readonly INodeStorage.KeyScheme _currentKeyScheme; - - public NodeStorageTests(INodeStorage.KeyScheme currentKeyScheme) - { - _currentKeyScheme = currentKeyScheme; - } - [Test] public void Should_StoreAndRead() { TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); nodeStorage.KeyExists(null, TreePath.Empty, TestItem.KeccakA).Should().BeFalse(); nodeStorage.Set(null, TreePath.Empty, TestItem.KeccakA, TestItem.KeccakA.BytesToArray()); nodeStorage.Get(null, TreePath.Empty, TestItem.KeccakA).Should().BeEquivalentTo(TestItem.KeccakA.BytesToArray()); nodeStorage.KeyExists(null, TreePath.Empty, TestItem.KeccakA).Should().BeTrue(); - if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) + if (currentKeyScheme == INodeStorage.KeyScheme.Hash) { testDb[TestItem.KeccakA.Bytes].Should().NotBeNull(); } - else if (_currentKeyScheme == INodeStorage.KeyScheme.HalfPath) + else if (currentKeyScheme == INodeStorage.KeyScheme.HalfPath) { testDb[NodeStorage.GetHalfPathNodeStoragePath(null, TreePath.Empty, TestItem.KeccakA)].Should().NotBeNull(); } @@ -47,18 +41,18 @@ public void Should_StoreAndRead() public void Should_StoreAndRead_WithStorage() { TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); nodeStorage.KeyExists(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA).Should().BeFalse(); nodeStorage.Set(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA, TestItem.KeccakA.BytesToArray()); nodeStorage.Get(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA).Should().BeEquivalentTo(TestItem.KeccakA.BytesToArray()); nodeStorage.KeyExists(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA).Should().BeTrue(); - if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) + if (currentKeyScheme == INodeStorage.KeyScheme.Hash) { testDb[TestItem.KeccakA.Bytes].Should().NotBeNull(); } - else if (_currentKeyScheme == INodeStorage.KeyScheme.HalfPath) + else if (currentKeyScheme == INodeStorage.KeyScheme.HalfPath) { testDb[NodeStorage.GetHalfPathNodeStoragePath(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA)].Should().NotBeNull(); } @@ -68,7 +62,7 @@ public void Should_StoreAndRead_WithStorage() public void When_KeyNotExist_Should_TryBothKeyType() { TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); nodeStorage.Get(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA).Should().BeNull(); @@ -80,9 +74,9 @@ public void When_KeyNotExist_Should_TryBothKeyType() public void When_EntryOfDifferentScheme_Should_StillBeAbleToRead() { TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); - if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) + if (currentKeyScheme == INodeStorage.KeyScheme.Hash) { testDb[NodeStorage.GetHalfPathNodeStoragePath(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA)] = TestItem.KeccakA.BytesToArray(); @@ -107,9 +101,9 @@ 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; + if (currentKeyScheme == INodeStorage.KeyScheme.Hash) return; Hash256? address = null; if (hasAddress) @@ -130,10 +124,10 @@ public void Test_HalfPathEncodng(bool hasAddress, int pathLength, string expecte [TestCase(true, 3, ReadFlags.HintReadAhead | ReadFlags.HintReadAhead3)] public void Test_WhenReadaheadUseDifferentReadaheadOnDifferentSection(bool hasAddress, int pathLength, ReadFlags expectedReadFlags) { - if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) return; + if (currentKeyScheme == INodeStorage.KeyScheme.Hash) return; TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); Hash256? address = null; if (hasAddress) diff --git a/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs b/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs index 29cf8d7767fe..2c70a4944949 100644 --- a/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs @@ -15,6 +15,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class OverlayTrieStoreTests { [Test] @@ -37,36 +38,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/MaxBlockInCachePruneStrategyTests.cs b/src/Nethermind/Nethermind.Trie.Test/Pruning/MaxBlockInCachePruneStrategyTests.cs index f288a30f6e0e..93c61e44ed48 100644 --- a/src/Nethermind/Nethermind.Trie.Test/Pruning/MaxBlockInCachePruneStrategyTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/Pruning/MaxBlockInCachePruneStrategyTests.cs @@ -8,7 +8,8 @@ namespace Nethermind.Trie.Test.Pruning { [TestFixture] - [Parallelizable(ParallelScope.Self)] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class MaxBlockInCachePruneStrategyTests { private IPruningStrategy _baseStrategy; diff --git a/src/Nethermind/Nethermind.Trie.Test/Pruning/MinBlockInCachePruneStrategyTests.cs b/src/Nethermind/Nethermind.Trie.Test/Pruning/MinBlockInCachePruneStrategyTests.cs index 27814dd58fce..63930144e889 100644 --- a/src/Nethermind/Nethermind.Trie.Test/Pruning/MinBlockInCachePruneStrategyTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/Pruning/MinBlockInCachePruneStrategyTests.cs @@ -9,7 +9,8 @@ namespace Nethermind.Trie.Test.Pruning { [TestFixture] - [Parallelizable(ParallelScope.Self)] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class MinBlockInCachePruneStrategyTests { private IPruningStrategy _baseStrategy; diff --git a/src/Nethermind/Nethermind.Trie.Test/Pruning/TestPruningStrategy.cs b/src/Nethermind/Nethermind.Trie.Test/Pruning/TestPruningStrategy.cs index 846140d871bc..9a1b28f0bc49 100644 --- a/src/Nethermind/Nethermind.Trie.Test/Pruning/TestPruningStrategy.cs +++ b/src/Nethermind/Nethermind.Trie.Test/Pruning/TestPruningStrategy.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using Nethermind.Core; using Nethermind.Trie.Pruning; namespace Nethermind.Trie.Test.Pruning diff --git a/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs b/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs index f5267e2b0d22..b75849af8999 100644 --- a/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs @@ -1,8 +1,9 @@ -// 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.Threading; using System.Threading.Tasks; using FluentAssertions; using Nethermind.Core; @@ -27,38 +28,41 @@ namespace Nethermind.Trie.Test.Pruning { [TestFixture(INodeStorage.KeyScheme.HalfPath)] [TestFixture(INodeStorage.KeyScheme.Hash)] - public class TreeStoreTests + [Parallelizable(ParallelScope.All)] + public class TreeStoreTests(INodeStorage.KeyScheme scheme) { private readonly ILogManager _logManager = LimboLogs.Instance; // new OneLoggerLogManager(new NUnitLogger(LogLevel.Trace)); private readonly AccountDecoder _accountDecoder = new(); - private readonly INodeStorage.KeyScheme _scheme; - - public TreeStoreTests(INodeStorage.KeyScheme scheme) - { - _scheme = scheme; - } private TrieStore CreateTrieStore( IPruningStrategy? pruningStrategy = null, IKeyValueStoreWithBatching? kvStore = null, IPersistenceStrategy? persistenceStrategy = null, - IPruningConfig? pruningConfig = null + IPruningConfig? pruningConfig = null, + IFinalizedStateProvider? finalizedStateProvider = null ) { pruningStrategy ??= No.Pruning; kvStore ??= new TestMemDb(); persistenceStrategy ??= No.Persistence; - return new( - new NodeStorage(kvStore, _scheme, requirePath: _scheme == INodeStorage.KeyScheme.HalfPath), + pruningConfig ??= new PruningConfig() + { + TrackPastKeys = false // Default disable + }; + + finalizedStateProvider ??= new TestFinalizedStateProvider(pruningConfig.PruningBoundary); + TrieStore trieStore = new( + new NodeStorage(kvStore, scheme, requirePath: scheme == INodeStorage.KeyScheme.HalfPath), pruningStrategy, persistenceStrategy, - pruningConfig ?? new PruningConfig() - { - TrackPastKeys = false // Default disable - }, + finalizedStateProvider, + pruningConfig, _logManager); + if (finalizedStateProvider is TestFinalizedStateProvider testFinalizedStateProvider) testFinalizedStateProvider.TrieStore = trieStore; + + return trieStore; } [SetUp] @@ -139,7 +143,7 @@ public void Pruning_off_cache_should_change_commit_node() committer.CommitNode(ref emptyPath, trieNode3); } fullTrieStore.WaitForPruning(); - fullTrieStore.MemoryUsedByDirtyCache.Should().Be(_scheme == INodeStorage.KeyScheme.HalfPath ? 832 : 676); + fullTrieStore.MemoryUsedByDirtyCache.Should().Be(scheme == INodeStorage.KeyScheme.HalfPath ? 832 : 676); } [Test] @@ -168,7 +172,7 @@ public void Pruning_off_cache_should_find_cached_or_unknown() Assert.That(returnedNode2.NodeType, Is.EqualTo(NodeType.Unknown)); Assert.That(returnedNode3.NodeType, Is.EqualTo(NodeType.Unknown)); trieStore.WaitForPruning(); - trieStore.MemoryUsedByDirtyCache.Should().Be(_scheme == INodeStorage.KeyScheme.HalfPath ? 552 : 396); + trieStore.MemoryUsedByDirtyCache.Should().Be(scheme == INodeStorage.KeyScheme.HalfPath ? 552 : 396); } [Test] @@ -233,7 +237,7 @@ public void Memory_with_concurrent_commits_is_correct() tree.Commit(); } - fullTrieStore.MemoryUsedByDirtyCache.Should().Be(_scheme == INodeStorage.KeyScheme.Hash ? 545956 : 616104L); + fullTrieStore.MemoryUsedByDirtyCache.Should().Be(scheme == INodeStorage.KeyScheme.Hash ? 545956 : 616104L); fullTrieStore.CommittedNodesCount.Should().Be(1349); } @@ -559,52 +563,6 @@ public void Will_get_dropped_on_snapshot_if_it_was_a_transient_node() fullTrieStore.IsNodeCached(null, TreePath.Empty, a.Keccak).Should().BeTrue(); } - private class BadDb : IKeyValueStoreWithBatching - { - private readonly Dictionary _db = new(); - - public byte[]? this[ReadOnlySpan key] - { - get => Get(key); - set => Set(key, value); - } - - public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - { - _db[key.ToArray()] = value; - } - - public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - { - return _db[key.ToArray()]; - } - - public IWriteBatch StartWriteBatch() - { - return new BadWriteBatch(); - } - - private class BadWriteBatch : IWriteBatch - { - private readonly Dictionary _inBatched = new(); - - public void Dispose() - { - } - - public byte[]? this[ReadOnlySpan key] - { - set => Set(key, value); - } - - public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - { - _inBatched[key.ToArray()] = value; - } - } - } - - [Test] public void Trie_store_multi_threaded_scenario() { @@ -803,7 +761,7 @@ public void Will_combine_same_storage() fullTrieStore.WaitForPruning(); storage.Get(null, TreePath.FromNibble(new byte[] { 0 }), a.Keccak).Should().NotBeNull(); storage.Get(new Hash256(Nibbles.ToBytes(storage1Nib)), TreePath.Empty, storage1.Keccak).Should().NotBeNull(); - fullTrieStore.IsNodeCached(null, TreePath.Empty, a.Keccak).Should().BeTrue(); + fullTrieStore.IsNodeCached(null, TreePath.FromNibble(new byte[] { 0 }), a.Keccak).Should().BeTrue(); fullTrieStore.IsNodeCached(new Hash256(Nibbles.ToBytes(storage1Nib)), TreePath.Empty, storage1.Keccak).Should().BeTrue(); } @@ -908,7 +866,7 @@ public void ReadOnly_store_returns_copies(bool pruning) readOnlyNode.Key?.ToString().Should().Be(originalNode.Key?.ToString()); } - private long ExpectedPerNodeKeyMemorySize => (_scheme == INodeStorage.KeyScheme.Hash ? 0 : TrieStoreDirtyNodesCache.Key.MemoryUsage) + MemorySizes.ObjectHeaderMethodTable + MemorySizes.RefSize + 4 + MemorySizes.RefSize; + private long ExpectedPerNodeKeyMemorySize => (scheme == INodeStorage.KeyScheme.Hash ? 0 : TrieStoreDirtyNodesCache.Key.MemoryUsage) + MemorySizes.ObjectHeaderMethodTable + MemorySizes.RefSize + 4 + MemorySizes.RefSize; [Test] public void After_commit_should_have_has_root() @@ -939,13 +897,14 @@ public void After_commit_should_have_has_root() [Test] [Retry(3)] + [NonParallelizable] public async Task Will_RemovePastKeys_OnSnapshot() { MemDb memDb = new(); using TrieStore fullTrieStore = CreateTrieStore( kvStore: memDb, - pruningStrategy: new TestPruningStrategy(true, true), + pruningStrategy: new TestPruningStrategy(shouldPrune: true, deleteObsoleteKeys: true), persistenceStrategy: No.Persistence, pruningConfig: new PruningConfig() { @@ -967,7 +926,7 @@ public async Task Will_RemovePastKeys_OnSnapshot() await Task.Delay(TimeSpan.FromMilliseconds(10)); } - if (_scheme == INodeStorage.KeyScheme.Hash) + if (scheme == INodeStorage.KeyScheme.Hash) { memDb.Count.Should().NotBe(1); } @@ -978,6 +937,8 @@ public async Task Will_RemovePastKeys_OnSnapshot() } [Test] + [Retry(3)] + [NonParallelizable] public async Task Will_Trigger_ReorgBoundaryEvent_On_Prune() { // TODO: Check why slow @@ -1009,6 +970,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 @@ -1020,7 +982,7 @@ public async Task Will_Trigger_ReorgBoundaryEvent_On_Prune() } [Test] - public async Task Will_NotRemove_ReCommittedNode() + public void Will_NotRemove_ReCommittedNode() { MemDb memDb = new(); @@ -1044,55 +1006,10 @@ public async Task Will_NotRemove_ReCommittedNode() committer.CommitNode(ref emptyPath, node); } - // Pruning is done in background - await Task.Delay(TimeSpan.FromMilliseconds(10)); - } - - memDb.Count.Should().Be(4); - } - - [Test] - public Task Will_RePersist_PersistedReCommittedNode() - { - MemDb memDb = new(); - - using TrieStore fullTrieStore = CreateTrieStore( - kvStore: memDb, - pruningStrategy: new TestPruningStrategy(true, true), - persistenceStrategy: No.Persistence, - pruningConfig: new PruningConfig() - { - PruningBoundary = 3, - TrackPastKeys = true - }); - - PatriciaTree topTree = new PatriciaTree(fullTrieStore.GetTrieStore(null), LimboLogs.Instance); - - byte[] key1 = Bytes.FromHexString("0000000000000000000000000000000000000000000000000000000000000000"); - byte[] key2 = Bytes.FromHexString("0011000000000000000000000000000000000000000000000000000000000000"); - - BlockHeader? baseBlock = null; - for (int i = 0; i < 64; i++) - { - using (fullTrieStore.BeginScope(baseBlock)) - { - topTree.Set(key1, [1, 2]); - topTree.Set(key2, [4, (byte)(i % 4)]); - - using (fullTrieStore.BeginStateBlockCommit(i, topTree.Root)) - { - topTree.Commit(); - } - - baseBlock = Build.A.BlockHeader.WithParentOptional(baseBlock).WithStateRoot(topTree.RootHash).TestObject; - } - fullTrieStore.WaitForPruning(); } - memDb.Count.Should().Be(13); - memDb.WritesCount.Should().Be(184); - return Task.CompletedTask; + memDb.Count.Should().Be(4); } [Test] @@ -1102,7 +1019,7 @@ public void When_SomeKindOfNonResolvedNotInMainWorldState_OnPrune_DoNotDeleteNod Address address = TestItem.AddressA; UInt256 slot = 1; - INodeStorage nodeStorage = new NodeStorage(memDbProvider.StateDb, _scheme); + INodeStorage nodeStorage = new NodeStorage(memDbProvider.StateDb, scheme); (Hash256 stateRoot, ValueHash256 storageRoot) = SetupStartingState(); nodeStorage.Get(address.ToAccountPath.ToCommitment(), TreePath.Empty, storageRoot).Should().NotBeNull(); @@ -1117,8 +1034,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. @@ -1139,7 +1055,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()); @@ -1185,7 +1102,7 @@ public Task When_Prune_ClearRecommittedPersistedNode() } memDb.Count.Should().Be(1); - fullTrieStore.MemoryUsedByDirtyCache.Should().Be(_scheme == INodeStorage.KeyScheme.Hash ? 12032 : 15360); + fullTrieStore.MemoryUsedByDirtyCache.Should().Be(scheme == INodeStorage.KeyScheme.Hash ? 12032 : 15360); fullTrieStore.PersistCache(default); memDb.Count.Should().Be(64); @@ -1222,5 +1139,504 @@ public void OnDispose_PersistAtLeastOneCommitSet() fullTrieStore.Dispose(); memDb.Count.Should().Be(1); } + + [Test] + public void Will_NotPruneTopLevelNode() + { + 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 + }); + + PatriciaTree ptree = new PatriciaTree(fullTrieStore.GetTrieStore(null), LimboLogs.Instance); + + void WriteRandomData(int seed) + { + ptree.Set(Keccak.Compute(seed.ToBigEndianByteArray()).Bytes, Keccak.Compute(seed.ToBigEndianByteArray()).BytesToArray()); + ptree.Commit(); + } + + for (int i = 0; i < 10; i++) + { + using (fullTrieStore.BeginBlockCommit(i)) + { + if (i == 0) + { + ptree.Set(Keccak.Compute(10000.ToBigEndianByteArray()).Bytes, Keccak.Compute(i.ToBigEndianByteArray()).BytesToArray()); + } + WriteRandomData(i); + } + } + fullTrieStore.PersistAndPruneDirtyCache(); + + for (int i = 10; i < 15; i++) + { + using (fullTrieStore.BeginBlockCommit(i)) + { + WriteRandomData(i); + } + } + // Do a branch + for (int i = 10; i < 15; i++) + { + using (fullTrieStore.BeginBlockCommit(i)) + { + WriteRandomData(i * 10); + } + } + fullTrieStore.PersistAndPruneDirtyCache(); + + for (int i = 15; i < 20; i++) + { + using (fullTrieStore.BeginBlockCommit(i)) + { + WriteRandomData(i); + } + } + fullTrieStore.WaitForPruning(); + + fullTrieStore.PrunePersistedNodes(); + fullTrieStore.CachedNodesCount.Should().Be(65); + + fullTrieStore.PersistAndPruneDirtyCache(); + fullTrieStore.PrunePersistedNodes(); + 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) + { + MemDb memDb = new MemDb(writeDelay: 5, readDelay: 0); + TestPruningStrategy testPruningStrategy = new TestPruningStrategy( + shouldPrune: false, + deleteObsoleteKeys: true + ); + + TrieStore fullTrieStore = CreateTrieStore( + kvStore: memDb, + pruningStrategy: testPruningStrategy, + persistenceStrategy: No.Persistence, + pruningConfig: new PruningConfig() + { + PruningBoundary = 4, + PrunePersistedNodePortion = 0.1, + DirtyNodeShardBit = 8, // More shard this time + MaxBufferedCommitCount = 20, + PrunePersistedNodeMinimumTarget = 0, + TrackPastKeys = true + }); + + PatriciaTree ptree = new PatriciaTree(fullTrieStore.GetTrieStore(null), LimboLogs.Instance); + + void WriteRandomData(int seed) + { + ptree.Set(Keccak.Compute(seed.ToBigEndianByteArray()).Bytes, Keccak.Compute(seed.ToBigEndianByteArray()).BytesToArray()); + ptree.Set(Keccak.Compute((seed * 10000).ToBigEndianByteArray()).Bytes, Keccak.Compute(seed.ToBigEndianByteArray()).BytesToArray()); + ptree.Set(TestItem.KeccakA.Bytes, Keccak.Compute(seed.ToBigEndianByteArray()).BytesToArray()); + ptree.Commit(); + } + + HashSet rootsToTests = new HashSet(); + void VerifyAllTrie() + { + PatriciaTree readOnlyPTree = new PatriciaTree(fullTrieStore.AsReadOnly().GetTrieStore(null), LimboLogs.Instance); + MemDb stubCodeDb = new MemDb(); + foreach (Hash256 rootsToTest in rootsToTests) + { + if (!fullTrieStore.HasRoot(rootsToTest)) continue; + TrieStatsCollector collector = new TrieStatsCollector(stubCodeDb, LimboLogs.Instance, expectAccounts: false); + ptree.Accept(collector, rootHash: rootsToTest); + collector.Stats.MissingNodes.Should().Be(0); + + collector = new TrieStatsCollector(stubCodeDb, LimboLogs.Instance, expectAccounts: false); + readOnlyPTree.Accept(collector, rootHash: rootsToTest); + collector.Stats.MissingNodes.Should().Be(0); + } + } + + BlockHeader baseBlock = Build.A.BlockHeader.WithStateRoot(Keccak.EmptyTreeHash).TestObject; + for (int i = 0; i < totalBlock; i++) + { + int seed = i % possibleSeed; + Hash256 parentRoot = ptree.RootHash ?? Keccak.EmptyTreeHash; + using (fullTrieStore.BeginScope(baseBlock)) + { + using (fullTrieStore.BeginBlockCommit(i)) + { + ptree.RootHash = parentRoot; + WriteRandomData(seed); + rootsToTests.Add(ptree.RootHash); + } + } + + // Branches sometimes + if ((i / 20) % 2 == 0) + { + using (fullTrieStore.BeginScope(baseBlock)) + { + using (fullTrieStore.BeginBlockCommit(i)) + { + ptree.RootHash = parentRoot; + WriteRandomData(seed * 10); + rootsToTests.Add(ptree.RootHash); + } + } + } + + // Persist sometimes + testPruningStrategy.ShouldPruneEnabled = i % snapshotInterval == 0; + testPruningStrategy.ShouldPrunePersistedEnabled = i % prunePersistedInterval == 0; + fullTrieStore.SyncPruneQueue(); + testPruningStrategy.ShouldPruneEnabled = false; + testPruningStrategy.ShouldPrunePersistedEnabled = false; + + VerifyAllTrie(); + baseBlock = Build.A.BlockHeader.WithParent(baseBlock).WithStateRoot(ptree.RootHash).TestObject; + } + } + + [Test] + public async Task Will_Persist_ReCommittedPersistedNode_FromCommitBuffer() + { + int pruningBoundary = 4; + + ManualResetEvent writeBlocker = new ManualResetEvent(true); + TestMemDb memDb = new(); + memDb.WriteFunc = (k, v) => + { + writeBlocker.WaitOne(); + return true; + }; + TestPruningStrategy testPruningStrategy = new TestPruningStrategy( + shouldPrune: false, + deleteObsoleteKeys: true + ); + + TrieStore fullTrieStore = CreateTrieStore( + kvStore: memDb, + pruningStrategy: testPruningStrategy, + persistenceStrategy: No.Persistence, + pruningConfig: new PruningConfig() + { + PruningBoundary = pruningBoundary, + PrunePersistedNodePortion = 1.0, + DirtyNodeShardBit = 4, + MaxBufferedCommitCount = 1, + TrackPastKeys = true + }); + + PatriciaTree ptree = new PatriciaTree(fullTrieStore.GetTrieStore(null), LimboLogs.Instance); + + void WriteRandomData(int seed) + { + ptree.Set(TestItem.KeccakA.Bytes, Keccak.Compute(seed.ToBigEndianByteArray()).BytesToArray()); + ptree.Commit(); + } + + Hash256 persistedRootHash = null; + int persistedBlockNumber = 5; + + for (int i = 0; i < 10; i++) + { + using (fullTrieStore.BeginBlockCommit(i)) + { + if (i == 0) + { + ptree.Set(Keccak.Compute(10000.ToBigEndianByteArray()).Bytes, Keccak.Compute(i.ToBigEndianByteArray()).BytesToArray()); + } + WriteRandomData(i); + + if (i == persistedBlockNumber) + { + persistedRootHash = ptree.RootHash; + } + } + } + + // Persisted nodes should be from block 5 + fullTrieStore.PersistAndPruneDirtyCache(); + fullTrieStore.LastPersistedBlockNumber.Should().Be(persistedBlockNumber); + + // Write a bit more + for (int i = 10; i < 12; i++) + { + using (fullTrieStore.BeginBlockCommit(i)) + { + WriteRandomData(i); + } + } + + // Block writes + writeBlocker.Reset(); + + // Background pruning + Task persistTask = Task.Run(() => + { + testPruningStrategy.ShouldPruneEnabled = true; + fullTrieStore.SyncPruneQueue(); + testPruningStrategy.ShouldPruneEnabled = false; + }); + Thread.Sleep(100); + + // Bring block 5's node to block 12 + // This is done in commit buffer. + using (fullTrieStore.BeginScope(Build.A.BlockHeader.WithStateRoot(ptree.RootHash).TestObject)) + { + fullTrieStore.IsInCommitBufferMode.Should().BeTrue(); + using (fullTrieStore.BeginBlockCommit(12)) + { + WriteRandomData(5); + ptree.RootHash.Should().Be(persistedRootHash); + } + } + + writeBlocker.Set(); + + await persistTask; + + // Write a bit more + for (int i = 13; i < 13 + pruningBoundary; i++) + { + using (fullTrieStore.BeginBlockCommit(i)) + { + WriteRandomData(i); + } + } + + // Persisted nodes should be from block 12 + testPruningStrategy.ShouldPruneEnabled = true; + fullTrieStore.SyncPruneQueue(); + testPruningStrategy.ShouldPruneEnabled = false; + fullTrieStore.LastPersistedBlockNumber.Should().Be(12); + + fullTrieStore.PrunePersistedNodes(); + + TrieStatsCollector collector = new TrieStatsCollector(new MemDb(), SimpleConsoleLogManager.Instance, expectAccounts: false); + ptree.Accept(collector, rootHash: persistedRootHash); + collector.Stats.MissingNodes.Should().Be(0); + } + + [Test] + public void Will_KeepAllState_IfFinalizedLagsBehind() + { + MemDb memDb = new MemDb(writeDelay: 5, readDelay: 0); + TestPruningStrategy testPruningStrategy = new TestPruningStrategy( + shouldPrune: true, + deleteObsoleteKeys: true + ); + + int pruningBoundary = 4; + IPruningConfig pruningConfig = new PruningConfig() + { + PruningBoundary = pruningBoundary, + DirtyNodeShardBit = 4, + MaxBufferedCommitCount = 20, + TrackPastKeys = true + }; + + TestFinalizedStateProvider finalizedStateProvider = new TestFinalizedStateProvider(pruningConfig.PruningBoundary); + + TrieStore fullTrieStore = CreateTrieStore( + kvStore: memDb, + pruningStrategy: testPruningStrategy, + persistenceStrategy: No.Persistence, + pruningConfig: pruningConfig, + finalizedStateProvider: finalizedStateProvider); + testPruningStrategy.ShouldPruneEnabled = false; + testPruningStrategy.ShouldPrunePersistedEnabled = false; + + PatriciaTree ptree = new PatriciaTree(fullTrieStore.GetTrieStore(null), LimboLogs.Instance); + + void WriteRandomData(int seed) + { + ptree.Set(Keccak.Compute(seed.ToBigEndianByteArray()).Bytes, Keccak.Compute(seed.ToBigEndianByteArray()).BytesToArray()); + ptree.Set(Keccak.Compute((seed * 10000).ToBigEndianByteArray()).Bytes, Keccak.Compute(seed.ToBigEndianByteArray()).BytesToArray()); + ptree.Set(TestItem.KeccakA.Bytes, Keccak.Compute(seed.ToBigEndianByteArray()).BytesToArray()); + ptree.Commit(); + } + + List rootsToTests = new List(); + void VerifyAllTrieExceptGenesis() + { + PatriciaTree readOnlyPTree = new PatriciaTree(fullTrieStore.AsReadOnly().GetTrieStore(null), LimboLogs.Instance); + MemDb stubCodeDb = new MemDb(); + for (int i = 1; i < rootsToTests.Count; i++) + { + Console.Error.WriteLine($"Verify {i}"); + var rootsToTest = rootsToTests[i]; + TrieStatsCollector collector = new TrieStatsCollector(stubCodeDb, LimboLogs.Instance, expectAccounts: false); + ptree.Accept(collector, rootHash: rootsToTest); + collector.Stats.MissingNodes.Should().Be(0); + + collector = new TrieStatsCollector(stubCodeDb, LimboLogs.Instance, expectAccounts: false); + readOnlyPTree.Accept(collector, rootHash: rootsToTest); + collector.Stats.MissingNodes.Should().Be(0); + } + } + + // Start from genesis for simplicity + BlockHeader baseBlock = Build.A.BlockHeader.WithStateRoot(Keccak.EmptyTreeHash).TestObject; + int blockNum = 100; + int lastNRoots = 0; + for (int i = 1; i < blockNum; i++) + { + int seed = i; + Hash256 parentRoot = ptree.RootHash ?? Keccak.EmptyTreeHash; + using (fullTrieStore.BeginScope(baseBlock)) + { + using (fullTrieStore.BeginBlockCommit(i)) + { + ptree.RootHash = parentRoot; + WriteRandomData(seed); + rootsToTests.Add(ptree.RootHash); + if (i >= blockNum - pruningBoundary) lastNRoots++; + } + } + + // Branches sometimes + if ((i / 20) % 2 == 0) + { + using (fullTrieStore.BeginScope(baseBlock)) + { + using (fullTrieStore.BeginBlockCommit(i)) + { + ptree.RootHash = parentRoot; + WriteRandomData(seed * 1000); + rootsToTests.Add(ptree.RootHash); + if (i >= blockNum - pruningBoundary) lastNRoots++; + } + } + } + + baseBlock = Build.A.BlockHeader.WithParent(baseBlock).WithStateRoot(ptree.RootHash).TestObject; + if (i == 1) + { + finalizedStateProvider.SetFinalizedPoint(baseBlock); + } + + if (i % 10 == 0) + { + fullTrieStore.PersistAndPruneDirtyCache(); + } + if (i == blockNum - 1) + { + fullTrieStore.PersistAndPruneDirtyCache(); + fullTrieStore.PrunePersistedNodes(); + + long cachedPersistedNode = fullTrieStore.CachedNodesCount - fullTrieStore.DirtyCachedNodesCount; + long perStatePersistedNode = 20; + if (scheme == INodeStorage.KeyScheme.Hash) + { + cachedPersistedNode.Should().Be(perStatePersistedNode + 3); + } + else + { + cachedPersistedNode.Should().Be(rootsToTests.Count - lastNRoots + perStatePersistedNode); // The root is still kept + } + VerifyAllTrieExceptGenesis(); + } + } + } + + [Test] + public void BlockCommitSet_IsSealed_after_Seal_with_null_root() + { + // This test verifies the fix for networks that have an empty genesis state. + // When the state trie is empty, the root is null, but the commit set should still be sealed. + BlockCommitSet commitSet = new(0); + + commitSet.IsSealed.Should().BeFalse(); + + commitSet.Seal(null); + + commitSet.IsSealed.Should().BeTrue(); + commitSet.StateRoot.Should().Be(Keccak.EmptyTreeHash); + } + + [Test] + public void Consecutive_block_commits_work_when_first_has_null_root() + { + // This test simulates a scenario where the genesis block has an empty state (no allocations). + // Block 0 commits with null root, then block 1 should be able to commit without assertion failure. + using TrieStore fullTrieStore = CreateTrieStore(); + + // Block 0: empty state (genesis with no allocations) + using (ICommitter _ = fullTrieStore.BeginStateBlockCommit(0, null)) { } + + // Block 1: should not throw or assert, even though previous block had null root + TrieNode trieNode = new(NodeType.Leaf, Keccak.Zero); + Action commitBlock1 = () => + { + using (ICommitter _ = fullTrieStore.BeginStateBlockCommit(1, trieNode)) { } + }; + + commitBlock1.Should().NotThrow(); + } } } diff --git a/src/Nethermind/Nethermind.Trie.Test/PruningScenariosTests.cs b/src/Nethermind/Nethermind.Trie.Test/PruningScenariosTests.cs index 06a9ffcba960..cd0cd0d3ee19 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; @@ -26,13 +27,169 @@ namespace Nethermind.Trie.Test { [TestFixture] + [Parallelizable(ParallelScope.All)] public class PruningScenariosTests { + private ILogger _logger; + private ILogManager _logManager; + + [SetUp] + public void SetUp() + { + _logManager = LimboLogs.Instance; + _logger = _logManager.GetClassLogger(); + } + /* When analyzing the tests below please remember that the way we store accounts is by calculating a hash of Address bytes. Address bytes here are created from an UInt256 of the account index. Analysis of branch / extension might be more difficult because of the hashing of addresses.*/ - /* TODO: fuzz here with a single seed number */ + [Test] + public void Fuzz_pruning_scenarios_with_fixed_seed() + { + // Fixed seed for reproducible fuzzing + const int fuzzSeed = 12345; + var random = new Random(fuzzSeed); + + _logger.Info($"Fuzzing pruning scenarios with seed: {fuzzSeed}"); + + // Generate random scenario parameters + int accountsCount = random.Next(5, 20); + int blocksCount = random.Next(10, 50); + int maxDepth = random.Next(2, 8); + int storageOperationsPerBlock = random.Next(1, 5); + + _logger.Info($"Scenario: {accountsCount} accounts, {blocksCount} blocks, maxDepth: {maxDepth}, storageOps: {storageOperationsPerBlock}"); + + // Create pruning context with random configuration + var pruningContext = PruningContext.InMemory + .WithMaxDepth(maxDepth) + .TurnOnPrune(); + + // Randomly choose between different pruning strategies + var strategyChoice = random.Next(0, 3); + switch (strategyChoice) + { + case 0: + pruningContext = PruningContext.InMemory; + break; + case 1: + pruningContext = PruningContext.InMemoryWithPastKeyTracking; + break; + case 2: + pruningContext = PruningContext.InMemoryAlwaysPrune; + break; + } + + pruningContext = pruningContext.WithMaxDepth(maxDepth).TurnOnPrune(); + + // Generate random accounts and operations + var accounts = new List(); + for (int i = 0; i < accountsCount; i++) + { + accounts.Add(i); + } + + // Execute random operations across blocks + for (int blockNumber = 0; blockNumber < blocksCount; blockNumber++) + { + // Random account operations + int operationsInBlock = random.Next(1, Math.Max(1, accountsCount / 2)); + for (int op = 0; op < operationsInBlock; op++) + { + int accountIndex = accounts[random.Next(accounts.Count)]; + var operation = random.Next(0, 4); + + switch (operation) + { + case 0: // Create/Update account balance + var balance = (UInt256)random.Next(1, 1000); + pruningContext.SetAccountBalance(accountIndex, balance); + break; + + case 1: // Set storage + var storageKey = random.Next(1, 10); + var storageValue = random.Next(1, 100); + pruningContext.SetStorage(accountIndex, storageKey, storageValue); + break; + + case 2: // Delete storage + var deleteKey = random.Next(1, 10); + pruningContext.DeleteStorage(accountIndex, deleteKey); + break; + + case 3: // Read account + pruningContext.ReadAccount(accountIndex); + break; + } + } + + // Random storage operations + for (int storageOp = 0; storageOp < storageOperationsPerBlock; storageOp++) + { + int accountIndex = accounts[random.Next(accounts.Count)]; + var storageKey = random.Next(1, 10); + var storageValue = random.Next(1, 100); + + var storageOperation = random.Next(0, 3); + switch (storageOperation) + { + case 0: + pruningContext.SetStorage(accountIndex, storageKey, storageValue); + break; + case 1: + pruningContext.DeleteStorage(accountIndex, storageKey); + break; + case 2: + pruningContext.ReadStorage(accountIndex, storageKey); + break; + } + } + + // Commit with random pruning behavior + bool shouldWaitForPruning = random.Next(0, 3) == 0; // 33% chance + pruningContext.Commit(shouldWaitForPruning); + + // Random branching scenarios + if (random.Next(0, 10) == 0) // 10% chance + { + string branchName = $"branch_{blockNumber}"; + pruningContext.SaveBranchingPoint(branchName); + + // Do some operations on branch + for (int branchOp = 0; branchOp < random.Next(1, 3); branchOp++) + { + int accountIndex = accounts[random.Next(accounts.Count)]; + var balance = (UInt256)random.Next(1, 1000); + pruningContext.SetAccountBalance(accountIndex, balance); + } + + pruningContext.Commit(); + + // Sometimes restore the branch + if (random.Next(0, 2) == 0) + { + pruningContext.RestoreBranchingPoint(branchName); + } + } + } + + // Final verification - ensure all accounts are still accessible + for (int i = 0; i < accountsCount; i++) + { + try + { + pruningContext.ReadAccount(i); + } + catch + { + // Some accounts might be pruned, which is expected behavior + _logger.Info($"Account {i} not accessible after pruning (expected behavior)"); + } + } + + _logger.Info($"Fuzzing completed successfully with seed {fuzzSeed}"); + } public class PruningContext { @@ -50,6 +207,8 @@ public class PruningContext private readonly IPersistenceStrategy _persistenceStrategy; private readonly TestPruningStrategy _pruningStrategy; private readonly IPruningConfig _pruningConfig; + private readonly TestFinalizedStateProvider _finalizedStateProvider; + private readonly Random _random = new Random(0); [DebuggerStepThrough] private PruningContext(TestPruningStrategy pruningStrategy, IPersistenceStrategy persistenceStrategy, IPruningConfig? pruningConfig = null) @@ -68,8 +227,12 @@ private PruningContext(TestPruningStrategy pruningStrategy, IPersistenceStrategy _pruningStrategy = pruningStrategy; _pruningConfig = pruningConfig ?? new PruningConfig() { TrackPastKeys = false }; - _trieStore = new TrieStore(new NodeStorage(_stateDb), _pruningStrategy, _persistenceStrategy, _pruningConfig, _logManager); - _stateProvider = new WorldState(_trieStore, _codeDb, _logManager); + _finalizedStateProvider = new TestFinalizedStateProvider(_pruningConfig.PruningBoundary); + + _trieStore = new TrieStore(new NodeStorage(_stateDb), _pruningStrategy, _persistenceStrategy, _finalizedStateProvider, _pruningConfig, _logManager); + _finalizedStateProvider.TrieStore = _trieStore; + _stateProvider = new WorldState( + new TrieStoreScopeProvider(_trieStore, _codeDb, _logManager), _logManager); _stateReader = new StateReader(_trieStore, _codeDb, _logManager); _worldStateCloser = _stateProvider.BeginScope(IWorldState.PreGenesis); } @@ -176,6 +339,13 @@ public PruningContext TurnOnPrune() return this; } + public PruningContext TurnOffPrune() + { + _pruningStrategy.ShouldPruneEnabled = false; + _pruningStrategy.ShouldPrunePersistedEnabled = false; + return this; + } + public PruningContext TurnOffAlwaysPrunePersistedNode() { _pruningStrategy.ShouldPrunePersistedEnabled = false; @@ -260,12 +430,20 @@ public PruningContext CommitAndWaitForPruning() return Commit(waitForPruning: true); } + public PruningContext SyncPruneCheck() + { + _trieStore.SyncPruneQueue(); + return this; + } + public PruningContext DisposeAndRecreate() { _worldStateCloser!.Dispose(); _trieStore.Dispose(); - _trieStore = new TrieStore(new NodeStorage(_stateDb), _pruningStrategy, _persistenceStrategy, _pruningConfig, _logManager); - _stateProvider = new WorldState(_trieStore, _codeDb, _logManager); + TestFinalizedStateProvider finalizedStateProvider = new TestFinalizedStateProvider(_pruningConfig.PruningBoundary); + _trieStore = new TrieStore(new NodeStorage(_stateDb), _pruningStrategy, _persistenceStrategy, finalizedStateProvider, _pruningConfig, _logManager); + _stateProvider = new WorldState( + new TrieStoreScopeProvider(_trieStore, _codeDb, _logManager), _logManager); _stateReader = new StateReader(_trieStore, _codeDb, _logManager); return this; } @@ -282,6 +460,12 @@ public PruningContext VerifyPersisted(int i) return this; } + public PruningContext VerifyStateDbSize(int i) + { + _stateDb.Count.Should().Be(i); + return this; + } + public PruningContext VerifyAccountBalance(int account, int balance) { _stateProvider.GetBalance(Address.FromNumber((UInt256)account)) @@ -306,12 +490,24 @@ public PruningContext VerifyCached(int i) return this; } + public PruningContext VerifyCachedPersistedNode(int i) + { + (_trieStore.CachedNodesCount - _trieStore.DirtyCachedNodesCount).Should().Be(i); + return this; + } + public PruningContext AssertThatCachedNodeCountIs(long cachedNodeCount) { _trieStore.CachedNodesCount.Should().Be(cachedNodeCount); return this; } + public PruningContext AssertThatCachedPersistedNodeCountIs(long cachedNodeCount) + { + (_trieStore.CachedNodesCount - _trieStore.DirtyCachedNodesCount).Should().Be(cachedNodeCount); + return this; + } + public PruningContext AssertThatCachedNodeCountMoreThan(long cachedNodeCount) { _trieStore.CachedNodesCount.Should().BeGreaterThan(cachedNodeCount); @@ -345,7 +541,7 @@ public PruningContext RestoreBranchingPoint(string name) return this; } - public PruningContext WithPersistedMemoryLimit(long persistedMemoryLimit) + public PruningContext WithPersistedMemoryLimit(long? persistedMemoryLimit) { _pruningStrategy.WithPersistedMemoryLimit = persistedMemoryLimit; return this; @@ -380,6 +576,41 @@ public PruningContext UnblockDatabase() _stateDbBlocker.Set(); return this; } + + public PruningContext VerifyBranchingPointExist(string branch) + { + _trieStore.HasRoot(_branchingPoints[branch].StateRoot).Should().BeTrue(); + return this; + } + + public PruningContext VerifyBranchingPointDoesNotExists(string branch) + { + _trieStore.HasRoot(_branchingPoints[branch].StateRoot).Should().BeFalse(); + return this; + } + + public PruningContext SetFinalizedPoint() + { + _finalizedStateProvider.SetFinalizedPoint(_baseBlock); + return this; + } + + public PruningContext CommitRandomDataWorthNBlocks(int n) + { + for (int i = 0; i < n; i++) + { + CommitRandomData(); + } + + return this; + } + + public PruningContext CommitRandomData() + { + return SetAccountBalance(1, (UInt256)_random.NextInt64()) + .SetAccountBalance(_random.Next(0, 1024), (UInt256)_random.NextInt64()) + .CommitAndWaitForPruning(); + } } [Test] @@ -670,10 +901,11 @@ public void Persist_alternate_branch_commitset_of_length_2() } [Test] - public void Should_persist_reorg_depth_block_on_dispose() + public void Should_persist_finalized_block_only_on_dispose() { PruningContext.InMemory .WithMaxDepth(4) + .TurnOffPrune() .SetAccountBalance(1, 100) .Commit() .SetAccountBalance(2, 10) @@ -690,6 +922,7 @@ public void Should_persist_reorg_depth_block_on_dispose() .SetStorage(3, 1, 2) .Commit() .SaveBranchingPoint("branch_2") + .SetFinalizedPoint() .RestoreBranchingPoint("revert_main") .SetStorage(3, 1, 3) @@ -702,20 +935,36 @@ public void Should_persist_reorg_depth_block_on_dispose() .Commit() .Commit() - // The `TrieStoreBoundaryWatcher` only reports the block number, but previously TrieStore only persist one of the - // multiple possible block of the same number. So if the persisted block is not the same as main, - // you'll get trie exception on restart. .DisposeAndRecreate() - // This should pass because the last committed branch is branch 3. - .RestoreBranchingPoint("branch_3") - .VerifyStorageValue(3, 1, 3) + .VerifyBranchingPointDoesNotExists("branch_1") + .VerifyBranchingPointExist("branch_2") + .VerifyBranchingPointDoesNotExists("branch_3"); + } - // Previously this does not. - .RestoreBranchingPoint("branch_1") - .VerifyStorageValue(3, 1, 1) - .RestoreBranchingPoint("branch_2") - .VerifyStorageValue(3, 1, 2); + [Test] + public void Should_persist_all_block_when_finalized_state_is_behind() + { + PruningContext.InMemoryWithPastKeyTracking + .WithMaxDepth(4) + .TurnOnPrune() + + .CommitRandomData() + .SetFinalizedPoint() + + .CommitRandomDataWorthNBlocks(10) + .VerifyPersisted(27) + .VerifyStateDbSize(27) + .VerifyCachedPersistedNode(11) + + .SetFinalizedPoint() + .CommitRandomData() + + .VerifyCachedPersistedNode(4) + .VerifyPersisted(31) + .VerifyStateDbSize(23) + // Only some get removed as the persisted node not in are cache so it does not know if it is safe to remove + ; } [Test] @@ -808,7 +1057,7 @@ public void StorageRoot_reset_at_lower_level() public void StateRoot_reset_at_lower_level_and_accessed_at_just_the_right_time() { PruningContext.InMemory - .WithMaxDepth(2) + .WithMaxDepth(3) .SetAccountBalance(1, 100) .SetAccountBalance(2, 100) .Commit() @@ -820,7 +1069,7 @@ public void StateRoot_reset_at_lower_level_and_accessed_at_just_the_right_time() .SetAccountBalance(2, 101) .Commit() .SetAccountBalance(3, 101) - .Commit() + .CommitAndWaitForPruning() .SaveBranchingPoint("main") // This will result in the same state root, but it's `LastCommit` reduced. @@ -974,7 +1223,32 @@ public void Retain_Some_PersistedNodes() ctx .AssertThatDirtyNodeCountIs(9) - .AssertThatCachedNodeCountMoreThan(280); + .AssertThatCachedNodeCountMoreThan(275); + } + + [Test] + public void Can_Prune_AllPersistedNodeInOnePrune() + { + PruningContext ctx = PruningContext.InMemoryWithPastKeyTracking + .WithMaxDepth(2) + .WithPersistedMemoryLimit(null) + .WithPrunePersistedNodeParameter(0, 0.1) + .TurnOffAlwaysPrunePersistedNode(); + + for (int i = 0; i < 256; i++) + { + if (i == 256 - 1) + { + ctx.TurnOnPrune(); + } + + ctx + .SetAccountBalance(i, (UInt256)i) + .CommitAndWaitForPruning(); + } + + ctx + .AssertThatCachedPersistedNodeCountIs(3); } [TestCase(10)] @@ -1001,6 +1275,30 @@ public void Can_ContinueCommittingEvenWhenPruning(int maxDepth) } } + [TestCase(10)] + [TestCase(64)] + [TestCase(100)] + public void Can_ContinueCommittingEvenWhenPruning_WithKeyTracking(int maxDepth) + { + PruningContext ctx = PruningContext.InMemoryWithPastKeyTracking + .WithMaxDepth(maxDepth) + .TurnOnPrune(); + + using ArrayPoolList stateRoots = new ArrayPoolList(256); + for (int i = 0; i < 256; i++) + { + ctx + .SetAccountBalance(0, (UInt256)i) + .CommitAndWaitForPruning(); + stateRoots.Add(ctx.CurrentStateRoot); + } + + for (int i = 0; i < 256; i++) + { + ctx.VerifyNodeInCache(stateRoots[i], i >= 255 - maxDepth); + } + } + [TestCase(100, 50, false)] [TestCase(100, 150, true)] public async Task Can_ContinueEvenWhenPruningIsBlocked(int maxBufferedCommit, int blockCount, bool isBlocked) @@ -1058,5 +1356,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/RawTrieStoreTests.cs b/src/Nethermind/Nethermind.Trie.Test/RawTrieStoreTests.cs new file mode 100644 index 000000000000..2535489626fb --- /dev/null +++ b/src/Nethermind/Nethermind.Trie.Test/RawTrieStoreTests.cs @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Db; +using Nethermind.Logging; +using NUnit.Framework; + +namespace Nethermind.Trie.Test; + +[Parallelizable(ParallelScope.All)] +public class RawTrieStoreTests +{ + [Test] + public void SmokeTest() + { + MemDb db = new MemDb(); + PatriciaTree patriciaTree = new PatriciaTree(new RawTrieStore(db).GetTrieStore(null), LimboLogs.Instance); + + patriciaTree.Set(TestItem.KeccakA.Bytes, TestItem.KeccakA.BytesToArray()); + patriciaTree.Set(TestItem.KeccakB.Bytes, TestItem.KeccakB.BytesToArray()); + patriciaTree.Set(TestItem.KeccakC.Bytes, TestItem.KeccakC.BytesToArray()); + + patriciaTree.Commit(); + patriciaTree.RootHash.Should().NotBe(Keccak.EmptyTreeHash); + + Hash256 rootHash = patriciaTree.RootHash; + + // Recreate + patriciaTree = new PatriciaTree(new RawTrieStore(db).GetTrieStore(null), LimboLogs.Instance); + patriciaTree.RootHash = rootHash; + + patriciaTree.Get(TestItem.KeccakA.Bytes).ToArray().Should().BeEquivalentTo(TestItem.KeccakA.BytesToArray()); + patriciaTree.Get(TestItem.KeccakB.Bytes).ToArray().Should().BeEquivalentTo(TestItem.KeccakB.BytesToArray()); + patriciaTree.Get(TestItem.KeccakC.Bytes).ToArray().Should().BeEquivalentTo(TestItem.KeccakC.BytesToArray()); + } +} diff --git a/src/Nethermind/Nethermind.Trie.Test/TinyTreePathTests.cs b/src/Nethermind/Nethermind.Trie.Test/TinyTreePathTests.cs index a2d51d1a5a18..d6c93f2b3f96 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TinyTreePathTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TinyTreePathTests.cs @@ -8,6 +8,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class TinyTreePathTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/TrackingCappedArrayPoolTests.cs b/src/Nethermind/Nethermind.Trie.Test/TrackingCappedArrayPoolTests.cs index 322804621d13..bb30481d6f14 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrackingCappedArrayPoolTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrackingCappedArrayPoolTests.cs @@ -8,6 +8,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class TrackingCappedArrayPoolTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/TreePathTests.cs b/src/Nethermind/Nethermind.Trie.Test/TreePathTests.cs index b398b4b3b65e..da83b8a3da8f 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TreePathTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TreePathTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class TreePathTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs b/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs index 205677863402..6d28229901d0 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class TrieNodeResolverWithReadFlagsTests { [Test] @@ -29,7 +30,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 c63615da3a05..b260ba72636e 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrieNodeTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrieNodeTests.cs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Runtime.InteropServices; -using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Nethermind.Core; @@ -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)] @@ -993,6 +993,75 @@ public void Can_parallel_read_unresolved_children() }); } + [Test] + public void Do_Not_MarkUnpersistedChildAsPersisted() + { + InMemoryScopedTrieStore inMemoryScopedTrieStore = new InMemoryScopedTrieStore(); + + PatriciaTree tree = new PatriciaTree(inMemoryScopedTrieStore, LimboLogs.Instance); + tree.Set(Bytes.FromHexString("0000000000000000000000000000000000000000000000000000000000000000"), [1]); + tree.Set(Bytes.FromHexString("0000000000000000010000000000000000000000000000000000000000000000"), [1]); + tree.Set(Bytes.FromHexString("0000000000000000011000000000000000000000000000000000000000000000"), [1]); + tree.Commit(); + + TreePath path = TreePath.FromHexString("00000000000000000"); + TrieNode parentExtension = inMemoryScopedTrieStore.FindCachedOrUnknown(path, Keccak.EmptyTreeHash); + parentExtension.IsPersisted = true; + + // Mark child as persisted + TrieNode child = parentExtension.GetChild(inMemoryScopedTrieStore, ref path, 1); + child.IsPersisted = true; + + // Trigger unresolve + parentExtension.GetChild(inMemoryScopedTrieStore, ref path, 1); + + // Unmark persisted + child.IsPersisted = false; + + // Should stay unpersisted + child = parentExtension.GetChild(inMemoryScopedTrieStore, ref path, 1); + Assert.That(child.IsPersisted, Is.False); + } + + private class InMemoryScopedTrieStore : IScopedTrieStore + { + private readonly ConcurrentDictionary _nodes = new ConcurrentDictionary(); + + private TrieNode GetOrAddNode(in TreePath path, TrieNode node) + { + return _nodes.GetOrAdd(path, node); + } + + public TrieNode FindCachedOrUnknown(in TreePath path, Hash256 hash) + { + return _nodes.GetOrAdd(path, new TrieNode(NodeType.Unknown, hash)); + } + + public byte[]? LoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => null; + + public byte[]? TryLoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => null; + + public ITrieNodeResolver GetStorageTrieNodeResolver(Hash256? address) + { + throw new InvalidOperationException($"{nameof(GetStorageTrieNodeResolver)} not supported"); + } + + public INodeStorage.KeyScheme Scheme => INodeStorage.KeyScheme.HalfPath; + public ICommitter BeginCommit(TrieNode? root, WriteFlags writeFlags = WriteFlags.None) => new Committer(this); + + private class Committer(InMemoryScopedTrieStore trieStore) : ICommitter + { + public void Dispose() + { + } + + public TrieNode CommitNode(ref TreePath path, TrieNode node) + { + return trieStore.GetOrAddNode(path, node); + } + } + } + private class Context { public TrieNode TiniestLeaf { get; } diff --git a/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs b/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs index 3edbedd65843..39acb58ec63e 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs @@ -2,11 +2,14 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.IO; using System.Threading; +using System.Threading.Tasks; using FluentAssertions; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Test; @@ -18,11 +21,14 @@ using Nethermind.Evm.State; using Nethermind.State; using Nethermind.Trie.Pruning; +using Nethermind.Trie.Test.Pruning; using NUnit.Framework; namespace Nethermind.Trie.Test { [TestFixture] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class TrieTests { private ILogger _logger; @@ -749,16 +755,22 @@ public TrieStore CreateTrieStore() ? No.Pruning : Prune.WhenCacheReaches(dirtyNodeSize); - return new TrieStore( + IPruningConfig pruningConfig = new PruningConfig() + { + TrackPastKeys = TrackPastKeys, + PruningBoundary = LookupLimit, + }; + TestFinalizedStateProvider finalizedStateProvider = new TestFinalizedStateProvider(pruningConfig.PruningBoundary); + TrieStore trieStore = new TrieStore( new NodeStorage(new MemDb()), pruneStrategy, Persist.EveryNBlock(PersistEveryN), - new PruningConfig() - { - TrackPastKeys = TrackPastKeys, - PruningBoundary = LookupLimit, - }, + finalizedStateProvider, + pruningConfig, LimboLogs.Instance); + finalizedStateProvider.TrieStore = trieStore; + + return trieStore; } public override string ToString() { @@ -1071,6 +1083,8 @@ public void Fuzz_accounts_with_reorganizations( } [TestCaseSource(nameof(FuzzAccountsWithStorageScenarios))] + [Retry(3)] + [NonParallelizable] public void Fuzz_accounts_with_storage( (TrieStoreConfigurations trieStoreConfigurations, int accountsCount, @@ -1109,7 +1123,7 @@ public void Fuzz_accounts_with_storage( } else { - accounts[i] = TestItem.GenerateRandomAccount(); + accounts[i] = TestItem.GenerateRandomAccount(_random); } addresses[i] = TestItem.GetRandomAddress(_random); @@ -1218,5 +1232,57 @@ public void Fuzz_accounts_with_storage( verifiedBlocks++; } } + + [Test] + public void Can_parallel_read_trees() + { + int itemCount = 1024; + int repetition = 100; + + PruningConfig pruningConfig = new PruningConfig(); + TestFinalizedStateProvider finalizedStateProvider = new TestFinalizedStateProvider(pruningConfig.PruningBoundary); + using TrieStore trieStore = new TrieStore( + new NodeStorage(new MemDb()), + new TestPruningStrategy(shouldPrune: true), + Persist.EveryBlock, + finalizedStateProvider, + pruningConfig, + LimboLogs.Instance + ); + finalizedStateProvider.TrieStore = trieStore; + + PatriciaTree tree = new PatriciaTree(trieStore, LimboLogs.Instance); + + using ArrayPoolList<(Hash256, Hash256)> kv = new ArrayPoolList<(Hash256, Hash256)>(itemCount); + + Span buffer = stackalloc byte[32]; + for (int i = 0; i < itemCount; i++) + { + BinaryPrimitives.WriteInt32BigEndian(buffer, i); + Hash256 key = Keccak.Compute(buffer); + key.Bytes[..8].Fill(0); + kv.Add((key, Keccak.Compute(buffer))); + } + + foreach (var it in kv) + { + (Hash256 key, Hash256 value) = it; + tree.Set(key.Bytes, value.BytesToArray()); + } + + using (trieStore.BeginBlockCommit(0)) + { + tree.Commit(); + } + + Parallel.For(0, repetition, (index, _) => + { + foreach (var it in kv) + { + (Hash256 key, Hash256 value) = it; + tree.Get(key.Bytes).ToArray().Should().BeEquivalentTo(value.BytesToArray()); + } + }); + } } } diff --git a/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs b/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs index 69e9ae819878..7221240b00a8 100644 --- a/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs @@ -20,14 +20,15 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class VisitingTests { [TestCaseSource(nameof(GetOptions))] - public void Visitors_state(VisitingOptions options) + public void Visitors_state(VisitingOptions options, INodeStorage.KeyScheme scheme) { MemDb memDb = new(); - using TrieStore trieStore = TestTrieStoreFactory.Build(memDb, Prune.WhenCacheReaches(1.MB()), Persist.EveryBlock, LimboLogs.Instance); + using ITrieStore trieStore = TestTrieStoreFactory.Build(new NodeStorage(memDb, scheme), LimboLogs.Instance); PatriciaTree patriciaTree = new(trieStore, LimboLogs.Instance); Span raw = stackalloc byte[32]; @@ -62,21 +63,21 @@ public void Visitors_state(VisitingOptions options) } [TestCaseSource(nameof(GetOptions))] - public void Visitors_storage(VisitingOptions options) + public void Visitors_storage(VisitingOptions options, INodeStorage.KeyScheme scheme) { MemDb memDb = new(); - using TrieStore trieStore = TestTrieStoreFactory.Build(memDb, Prune.WhenCacheReaches(1.MB()), Persist.EveryBlock, LimboLogs.Instance); + using ITrieStore trieStore = TestTrieStoreFactory.Build(new NodeStorage(memDb, scheme), LimboLogs.Instance); byte[] value = Enumerable.Range(1, 32).Select(static i => (byte)i).ToArray(); Hash256 stateRootHash = Keccak.Zero; 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++) @@ -109,8 +110,11 @@ public void Visitors_storage(VisitingOptions options) stateTree.Accept(visitor, stateTree.RootHash, options); + int totalPath = 0; + foreach (var path in visitor.LeafPaths) { + totalPath++; if (path.Length == 64) { AssertPath(path); @@ -127,6 +131,8 @@ public void Visitors_storage(VisitingOptions options) } } + totalPath.Should().Be(4160); + return; static void AssertPath(ReadOnlySpan path) @@ -142,13 +148,23 @@ private static IEnumerable GetOptions() { yield return new TestCaseData(new VisitingOptions { - }).SetName("Default"); + }, INodeStorage.KeyScheme.HalfPath).SetName("Default"); + + yield return new TestCaseData(new VisitingOptions + { + }, INodeStorage.KeyScheme.Hash).SetName("Default Hash"); + + yield return new TestCaseData(new VisitingOptions + { + MaxDegreeOfParallelism = Environment.ProcessorCount, + FullScanMemoryBudget = 1.MiB(), + }, INodeStorage.KeyScheme.HalfPath).SetName("Parallel"); yield return new TestCaseData(new VisitingOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, FullScanMemoryBudget = 1.MiB(), - }).SetName("Parallel"); + }, INodeStorage.KeyScheme.Hash).SetName("Parallel Hash"); } public class AppendingVisitor(bool expectAccount) : ITreeVisitor diff --git a/src/Nethermind/Nethermind.Trie.Test/VisitorProgressTrackerTests.cs b/src/Nethermind/Nethermind.Trie.Test/VisitorProgressTrackerTests.cs new file mode 100644 index 000000000000..f22b68a1123b --- /dev/null +++ b/src/Nethermind/Nethermind.Trie.Test/VisitorProgressTrackerTests.cs @@ -0,0 +1,151 @@ +// 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; + +[Parallelizable(ParallelScope.All)] +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/BatchedTrieVisitor.cs b/src/Nethermind/Nethermind.Trie/BatchedTrieVisitor.cs index 45e9c9607d76..75fb1e3805c7 100644 --- a/src/Nethermind/Nethermind.Trie/BatchedTrieVisitor.cs +++ b/src/Nethermind/Nethermind.Trie/BatchedTrieVisitor.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; using System.Buffers.Binary; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -146,9 +145,9 @@ public void Start( try { - using ArrayPoolList tasks = Enumerable.Range(0, trieVisitContext.MaxDegreeOfParallelism) + using ArrayPoolListRef tasks = Enumerable.Range(0, trieVisitContext.MaxDegreeOfParallelism) .Select(_ => Task.Run(BatchedThread)) - .ToPooledList(trieVisitContext.MaxDegreeOfParallelism); + .ToPooledListRef(trieVisitContext.MaxDegreeOfParallelism); Task.WaitAll(tasks.AsSpan()); } @@ -169,7 +168,7 @@ public void Start( { Interlocked.Add(ref _currentPointer, -_partitionCount); - GC.Collect(); // Simulate GC collect of standard visitor + GC.Collect(); // Simulate GC collect of a standard visitor } partitionIdx %= _partitionCount; @@ -256,7 +255,7 @@ public void Start( } - void QueueNextNodes(ArrayPoolList<(TrieNode, TNodeContext, SmallTrieVisitContext)> batchResult) + void QueueNextNodes(ref ArrayPoolListRef<(TrieNode, TNodeContext, SmallTrieVisitContext)> batchResult) { // Reverse order is important so that higher level appear at the end of the stack. TreePath emptyPath = TreePath.Empty; @@ -267,11 +266,19 @@ void QueueNextNodes(ArrayPoolList<(TrieNode, TNodeContext, SmallTrieVisitContext { // Inline node. Seems rare, so its fine to create new list for this. Does not have a keccak // to queue, so we'll just process it inline. - using ArrayPoolList<(TrieNode, TNodeContext, SmallTrieVisitContext)> recursiveResult = new(1); - trieNode.ResolveNode(_resolver, emptyPath); - Interlocked.Increment(ref _activeJobs); - AcceptResolvedNode(trieNode, nodeContext, _resolver, ctx, recursiveResult); - QueueNextNodes(recursiveResult); + ArrayPoolListRef<(TrieNode, TNodeContext, SmallTrieVisitContext)> recursiveResult = new(1); + try + { + trieNode.ResolveNode(_resolver, emptyPath); + Interlocked.Increment(ref _activeJobs); + AcceptResolvedNode(trieNode, nodeContext, _resolver, ctx, ref recursiveResult); + QueueNextNodes(ref recursiveResult); + } + finally + { + recursiveResult.Dispose(); + } + continue; } @@ -290,87 +297,93 @@ void QueueNextNodes(ArrayPoolList<(TrieNode, TNodeContext, SmallTrieVisitContext Interlocked.Decrement(ref _activeJobs); } - private void BatchedThread() { - using ArrayPoolList<(TrieNode, TNodeContext, SmallTrieVisitContext)> nextToProcesses = new(_maxBatchSize); - using ArrayPoolList resolveOrdering = new(_maxBatchSize); - ArrayPoolList<(TrieNode, TNodeContext, SmallTrieVisitContext)>? currentBatch; - TreePath emptyPath = TreePath.Empty; - while ((currentBatch = GetNextBatch()) is not null) + ArrayPoolListRef<(TrieNode, TNodeContext, SmallTrieVisitContext)> nextToProcesses = new(_maxBatchSize); + try { - // Storing the idx separately as the ordering is important to reduce memory (approximate dfs ordering) - // but the path ordering is important for read amplification - resolveOrdering.Clear(); - for (int i = 0; i < currentBatch.Count; i++) + using ArrayPoolListRef resolveOrdering = new(_maxBatchSize); + TreePath emptyPath = TreePath.Empty; + while (GetNextBatch() is { } currentBatch) { - (TrieNode? cur, TNodeContext _, SmallTrieVisitContext ctx) = currentBatch[i]; - - cur.ResolveKey(_resolver, ref emptyPath); - - if (cur.FullRlp.IsNotNull) continue; - if (cur.Keccak is null) - ThrowUnableToResolve(ctx); + // Storing the idx separately as the ordering is important to reduce memory (approximate dfs ordering) + // but the path ordering is important for read amplification + resolveOrdering.Clear(); + for (int i = 0; i < currentBatch.Count; i++) + { + (TrieNode? cur, TNodeContext _, SmallTrieVisitContext ctx) = currentBatch[i]; - resolveOrdering.Add(i); - } + cur.ResolveKey(_resolver, ref emptyPath); - // This innocent looking sort is surprisingly effective when batch size is large enough. The sort itself - // take about 0.1% of the time, so not very cpu intensive in this case. - resolveOrdering - .AsSpan() - .Sort((item1, item2) => currentBatch[item1].Item1.Keccak.CompareTo(currentBatch[item2].Item1.Keccak)); + if (cur.FullRlp.IsNotNull) continue; + if (cur.Keccak is null) + ThrowUnableToResolve(ctx); - ReadFlags flags = ReadFlags.None; - if (resolveOrdering.Count > _readAheadThreshold) - { - flags = ReadFlags.HintReadAhead; - } + resolveOrdering.Add(i); + } - // This loop is about 60 to 70% of the time spent. If you set very high memory budget, this drop to about 50MB. - for (int i = 0; i < resolveOrdering.Count; i++) - { - int idx = resolveOrdering[i]; + // This innocent looking sort is surprisingly effective when batch size is large enough. The sort itself + // take about 0.1% of the time, so not very cpu intensive in this case. + resolveOrdering + .AsSpan() + .Sort((item1, item2) => + currentBatch[item1].Item1.Keccak.CompareTo(currentBatch[item2].Item1.Keccak)); - (TrieNode nodeToResolve, TNodeContext nodeContext, SmallTrieVisitContext ctx) = currentBatch[idx]; - try + ReadFlags flags = ReadFlags.None; + if (resolveOrdering.Count > _readAheadThreshold) { - Hash256 theKeccak = nodeToResolve.Keccak; - nodeToResolve.ResolveNode(_resolver, emptyPath, flags); - nodeToResolve.Keccak = theKeccak; // The resolve may set a key which clear the keccak + flags = ReadFlags.HintReadAhead; } - catch (TrieException) + + // This loop is about 60 to 70% of the time spent. If you set very high memory budget, this drop to about 50MB. + for (int i = 0; i < resolveOrdering.Count; i++) { - _visitor.VisitMissingNode(nodeContext, nodeToResolve.Keccak); - } - } + int idx = resolveOrdering[i]; - // Going in reverse to reduce memory - for (int i = currentBatch.Count - 1; i >= 0; i--) - { - (TrieNode nodeToResolve, TNodeContext nodeContext, SmallTrieVisitContext ctx) = currentBatch[i]; + (TrieNode nodeToResolve, TNodeContext nodeContext, SmallTrieVisitContext ctx) = currentBatch[idx]; + try + { + Hash256 theKeccak = nodeToResolve.Keccak; + nodeToResolve.ResolveNode(_resolver, emptyPath, flags); + nodeToResolve.Keccak = theKeccak; // The resolve may set a key which clear the keccak + } + catch (TrieException) + { + _visitor.VisitMissingNode(nodeContext, nodeToResolve.Keccak); + } + } - nextToProcesses.Clear(); - if (nodeToResolve.FullRlp.IsNull) + // Going in reverse to reduce memory + for (int i = currentBatch.Count - 1; i >= 0; i--) { - // Still need to decrement counter - QueueNextNodes(nextToProcesses); - return; // missing node + (TrieNode nodeToResolve, TNodeContext nodeContext, SmallTrieVisitContext ctx) = currentBatch[i]; + + nextToProcesses.Clear(); + if (nodeToResolve.FullRlp.IsNull) + { + // Still need to decrement counter + QueueNextNodes(ref nextToProcesses); + return; // missing node + } + + AcceptResolvedNode(nodeToResolve, nodeContext, _resolver, ctx, ref nextToProcesses); + QueueNextNodes(ref nextToProcesses); } - AcceptResolvedNode(nodeToResolve, nodeContext, _resolver, ctx, nextToProcesses); - QueueNextNodes(nextToProcesses); + currentBatch.Dispose(); } - - currentBatch.Dispose(); + } + finally + { + nextToProcesses.Dispose(); } return; - static void ThrowUnableToResolve(in SmallTrieVisitContext ctx) + void ThrowUnableToResolve(in SmallTrieVisitContext ctx) { throw new TrieException( - $"Unable to resolve node without Keccak. ctx: {ctx.Level}, {ctx.ExpectAccounts}, {ctx.IsStorage}"); + $"Unable to resolve node without Keccak. ctx: {ctx.Level}, {_visitor.ExpectAccounts}, {ctx.IsStorage}"); } } @@ -378,7 +391,7 @@ static void ThrowUnableToResolve(in SmallTrieVisitContext ctx) /// Like `Accept`, but does not execute its children. Instead it return the next trie to visit in the list /// `nextToVisit`. Also, it assume the node is already resolved. /// - internal void AcceptResolvedNode(TrieNode node, in TNodeContext nodeContext, ITrieNodeResolver nodeResolver, SmallTrieVisitContext trieVisitContext, IList<(TrieNode, TNodeContext, SmallTrieVisitContext)> nextToVisit) + internal void AcceptResolvedNode(TrieNode node, in TNodeContext nodeContext, ITrieNodeResolver nodeResolver, SmallTrieVisitContext trieVisitContext, ref ArrayPoolListRef<(TrieNode, TNodeContext, SmallTrieVisitContext)> nextToVisit) { // Note: The path is not maintained here, its just for a placeholder. This code is only used for BatchedTrieVisitor // which should only be used with hash keys. @@ -422,8 +435,6 @@ internal void AcceptResolvedNode(TrieNode node, in TNodeContext nodeContext, ITr if (_visitor.ShouldVisit(childContext, child.Keccak!)) { trieVisitContext.Level++; - - nextToVisit.Add((child, childContext, trieVisitContext)); } @@ -434,11 +445,11 @@ internal void AcceptResolvedNode(TrieNode node, in TNodeContext nodeContext, ITr { _visitor.VisitLeaf(nodeContext, node); - if (!trieVisitContext.IsStorage && trieVisitContext.ExpectAccounts) // can combine these conditions + if (!trieVisitContext.IsStorage && _visitor.ExpectAccounts) // can combine these conditions { TNodeContext childContext = nodeContext.Add(node.Key!); - Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(node.Value.Span); + Rlp.ValueDecoderContext decoderContext = new(node.Value.Span); if (!_accountDecoder.TryDecodeStruct(ref decoderContext, out AccountStruct account)) { throw new InvalidDataException("Non storage leaf should be an account"); @@ -473,18 +484,11 @@ internal void AcceptResolvedNode(TrieNode node, in TNodeContext nodeContext, ITr } [StructLayout(LayoutKind.Sequential, Pack = 1)] - private readonly struct Job + private readonly struct Job(ValueHash256 key, TNodeContext nodeContext, SmallTrieVisitContext context) { - public readonly ValueHash256 Key; - public readonly TNodeContext NodeContext; - public readonly SmallTrieVisitContext Context; - - public Job(ValueHash256 key, TNodeContext nodeContext, SmallTrieVisitContext context) - { - Key = key; - NodeContext = nodeContext; - Context = context; - } + public readonly ValueHash256 Key = key; + public readonly TNodeContext NodeContext = nodeContext; + public readonly SmallTrieVisitContext Context = context; } } diff --git a/src/Nethermind/Nethermind.Trie/CachedTrieStore.cs b/src/Nethermind/Nethermind.Trie/CachedTrieStore.cs index bf2f7c32e74c..93518dfb4ce0 100644 --- a/src/Nethermind/Nethermind.Trie/CachedTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/CachedTrieStore.cs @@ -35,7 +35,5 @@ public ITrieNodeResolver GetStorageTrieNodeResolver(Hash256? address) => public ICommitter BeginCommit(TrieNode? root, WriteFlags writeFlags = WriteFlags.None) => @base.BeginCommit(root, writeFlags); - - public bool IsPersisted(in TreePath path, in ValueHash256 keccak) => @base.IsPersisted(in path, in keccak); } diff --git a/src/Nethermind/Nethermind.Trie/HexPrefix.cs b/src/Nethermind/Nethermind.Trie/HexPrefix.cs index 739db8afa83e..e7babfa09bc5 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/ITreeVisitor.cs b/src/Nethermind/Nethermind.Trie/ITreeVisitor.cs index c7f40cbba2ba..ebcd71e5b104 100644 --- a/src/Nethermind/Nethermind.Trie/ITreeVisitor.cs +++ b/src/Nethermind/Nethermind.Trie/ITreeVisitor.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Nethermind.Core; using Nethermind.Core.Crypto; diff --git a/src/Nethermind/Nethermind.Trie/NibbleExtensions.cs b/src/Nethermind/Nethermind.Trie/NibbleExtensions.cs index f56c055057fc..97d6ecefdcbc 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/NodeStorage.cs b/src/Nethermind/Nethermind.Trie/NodeStorage.cs index e0f50c0d60db..a788f79002b7 100644 --- a/src/Nethermind/Nethermind.Trie/NodeStorage.cs +++ b/src/Nethermind/Nethermind.Trie/NodeStorage.cs @@ -2,13 +2,11 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers.Binary; using System.Diagnostics; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Db; -using Nethermind.Trie.Pruning; namespace Nethermind.Trie; diff --git a/src/Nethermind/Nethermind.Trie/NodeStorageCache.cs b/src/Nethermind/Nethermind.Trie/NodeStorageCache.cs new file mode 100644 index 000000000000..7502c5aea640 --- /dev/null +++ b/src/Nethermind/Nethermind.Trie/NodeStorageCache.cs @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Collections; + +namespace Nethermind.Trie; + +public sealed class NodeStorageCache +{ + private readonly SeqlockCache _cache = new(); + + private volatile bool _enabled = false; + + public bool Enabled + { + get => _enabled; + set => _enabled = value; + } + + public byte[]? GetOrAdd(in NodeKey nodeKey, SeqlockCache.ValueFactory tryLoadRlp) + { + if (!_enabled) + { + return tryLoadRlp(in nodeKey); + } + return _cache.GetOrAdd(in nodeKey, tryLoadRlp); + } + + public bool ClearCaches() + { + bool wasEnabled = _enabled; + _enabled = false; + _cache.Clear(); + return wasEnabled; + } +} diff --git a/src/Nethermind/Nethermind.Trie/PatriciaTree.BulkSet.cs b/src/Nethermind/Nethermind.Trie/PatriciaTree.BulkSet.cs index a314d4bd1a4d..07938e21839c 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; @@ -38,7 +38,7 @@ public int CompareTo(BulkSetEntry entry) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte GetPathNibbble(int index) + public byte GetPathNibble(int index) { int offset = index / 2; Span theSpan = Path.BytesAsSpan; @@ -62,13 +62,13 @@ public byte GetPathNibbble(int index) /// /// /// - public void BulkSet(ArrayPoolList entries, Flags flags = Flags.None) + public void BulkSet(in ArrayPoolListRef entries, Flags flags = Flags.None) { if (entries.Count == 0) return; - using ArrayPoolList sortBuffer = new ArrayPoolList(entries.Count, entries.Count); + using ArrayPoolListRef sortBuffer = new(entries.Count, entries.Count); - Context ctx = new Context() + Context ctx = new() { originalSortBufferArray = sortBuffer.UnsafeGetInternalArray(), originalEntriesArray = entries.UnsafeGetInternalArray(), @@ -166,8 +166,7 @@ private struct Context int nonNullChildCount = 0; if (entries.Length >= MinEntriesToParallelizeThreshold && nibMask == FullBranch && !flags.HasFlag(Flags.DoNotParallelize)) { - (int startIdx, int count, int nibble, TreePath appendedPath, TrieNode? currentChild, TrieNode? newChild)[] jobs = - new (int startIdx, int count, int nibble, TreePath appendedPath, TrieNode? currentChild, TrieNode? newChild)[TrieNode.BranchesCount]; + var jobs = new (int startIdx, int count, int nibble, TreePath appendedPath, TrieNode? currentChild, TrieNode? newChild)[TrieNode.BranchesCount]; Context closureCtx = ctx; BulkSetEntry[] originalEntriesArray = (flipCount % 2 == 0) ? ctx.originalEntriesArray : ctx.originalSortBufferArray; @@ -210,7 +209,7 @@ private struct Context TrieNode? child = jobs[i].currentChild; TrieNode? newChild = jobs[i].newChild; - if (!ShouldUpdateChild(node, child, newChild)) continue; + if (!ShouldUpdateChild(originalNode, child, newChild)) continue; if (newChild is null) hasRemove = true; if (newChild is not null) nonNullChildCount++; @@ -242,7 +241,7 @@ private struct Context ? BulkSetOne(traverseStack, entries[startRange], ref path, child) : BulkSet(in ctx, traverseStack, entries[startRange..endRange], sortBuffer[startRange..endRange], ref path, child, flipCount, flags); - if (!ShouldUpdateChild(node, child, newChild)) continue; + if (!ShouldUpdateChild(originalNode, child, newChild)) continue; if (newChild is null) hasRemove = true; if (newChild is not null) nonNullChildCount++; @@ -257,13 +256,13 @@ private struct Context if (!hasRemove && nonNullChildCount == 0) return originalNode; if ((hasRemove || newBranch) && nonNullChildCount < 2) - node = MaybeCombineNode(ref path, node); + node = MaybeCombineNode(ref path, node, originalNode); return node; } - private TrieNode? BulkSetOne(Stack traverseStack, in BulkSetEntry entry, ref TreePath path, - TrieNode? node) + [SkipLocalsInit] + private TrieNode? BulkSetOne(Stack traverseStack, in BulkSetEntry entry, ref TreePath path, TrieNode? node) { Span nibble = stackalloc byte[64]; Nibbles.BytesToNibbleBytes(entry.Path.BytesAsSpan, nibble); @@ -275,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]; @@ -341,12 +339,12 @@ 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++) { - byte nib = entries[i].GetPathNibbble(pathIndex); + byte nib = entries[i].GetPathNibble(pathIndex); counts[nib]++; } @@ -367,7 +365,7 @@ internal static int BucketSort16Large( for (int i = 0; i < entries.Length; i++) { - int nib = entries[i].GetPathNibbble(pathIndex); + int nib = entries[i].GetPathNibble(pathIndex); sortTarget[starts[nib]++] = entries[i]; } @@ -386,7 +384,7 @@ internal static int BucketSort16Small( Span counts = stackalloc int[TrieNode.BranchesCount]; for (int i = 0; i < entries.Length; i++) { - byte nib = entries[i].GetPathNibbble(pathIndex); + byte nib = entries[i].GetPathNibble(pathIndex); counts[nib]++; usedMask |= 1 << nib; } @@ -407,7 +405,7 @@ internal static int BucketSort16Small( for (int i = 0; i < entries.Length; i++) { - int nib = entries[i].GetPathNibbble(pathIndex); + int nib = entries[i].GetPathNibble(pathIndex); sortTarget[starts[nib]++] = entries[i]; } @@ -435,7 +433,7 @@ internal static int HexarySearchAlreadySortedSmall(Span entries, i for (int i = 0; i < entries.Length && curIdx < TrieNode.BranchesCount; i++) { - var currentNib = entries[i].GetPathNibbble(pathIndex); + var currentNib = entries[i].GetPathNibble(pathIndex); if (currentNib > curIdx) { @@ -453,6 +451,7 @@ internal static int HexarySearchAlreadySortedSmall(Span entries, i return usedMask; } + [SkipLocalsInit] internal static int HexarySearchAlreadySortedLarge( Span entries, int pathIndex, @@ -465,7 +464,7 @@ internal static int HexarySearchAlreadySortedLarge( Span his = stackalloc int[TrieNode.BranchesCount]; his.Fill(n); - int nib = entries[0].GetPathNibbble(pathIndex); + int nib = entries[0].GetPathNibble(pathIndex); // First nib is free int usedMask = 0; @@ -481,7 +480,7 @@ internal static int HexarySearchAlreadySortedLarge( while (lo < hi) { int mid = (int)((uint)(lo + hi) >> 1); - int midnib = entries[mid].GetPathNibbble(pathIndex); + int midnib = entries[mid].GetPathNibble(pathIndex); if (midnib < nib) { lo = mid + 1; @@ -497,7 +496,7 @@ internal static int HexarySearchAlreadySortedLarge( if (lo == n) break; // Note: The nib can be different, but its fine as it automatically skip. - nib = entries[lo].GetPathNibbble(pathIndex); + nib = entries[lo].GetPathNibble(pathIndex); usedMask |= 1 << nib; indexes[nib] = lo; diff --git a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs index 15cebdfca3df..d42c85a23f6e 100644 --- a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs +++ b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -25,7 +24,6 @@ namespace Nethermind.Trie public partial class PatriciaTree { private const int MaxKeyStackAlloc = 64; - private readonly static byte[][] _singleByteKeys = [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15]]; private readonly ILogger _logger; @@ -141,13 +139,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 +167,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 +236,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 +244,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 +280,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 +300,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 +391,7 @@ public virtual ReadOnlySpan Get(ReadOnlySpan rawKey, Hash256? rootHa } } + [SkipLocalsInit] [DebuggerStepThrough] public byte[]? GetNodeByKey(Span rawKey, Hash256? rootHash = null) { @@ -537,7 +545,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 +630,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 +681,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 { @@ -706,18 +714,20 @@ public void Set(ReadOnlySpan rawKey, Rlp? value) } // About 1% reach here - node = MaybeCombineNode(ref path, node); + node = MaybeCombineNode(ref path, node, null); } return node; } - internal bool ShouldUpdateChild(TrieNode parent, TrieNode? oldChild, TrieNode? newChild) + internal bool ShouldUpdateChild(TrieNode? parent, TrieNode? oldChild, TrieNode? newChild) { + 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; } /// @@ -726,7 +736,7 @@ internal bool ShouldUpdateChild(TrieNode parent, TrieNode? oldChild, TrieNode? n /// /// /// - internal TrieNode? MaybeCombineNode(ref TreePath path, in TrieNode? node) + internal TrieNode? MaybeCombineNode(ref TreePath path, in TrieNode? node, TrieNode? originalNode) { int onlyChildIdx = -1; TrieNode? onlyChildNode = null; @@ -764,12 +774,54 @@ internal bool ShouldUpdateChild(TrieNode parent, TrieNode? oldChild, TrieNode? n if (onlyChildNode.IsBranch) { - return TrieNodeFactory.CreateExtension([(byte)onlyChildIdx], onlyChildNode); + 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(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; + } + } + } + + if (originalNode.IsLeaf && onlyChildNode.IsLeaf) + { + if (Bytes.AreEqual(newKey, originalNode.Key)) + { + if (onlyChildNode.Value.Equals(originalNode.Value)) + { + return originalNode; + } + } + } + } + TrieNode tn = onlyChildNode.CloneWithChangedKey(newKey); return tn; } @@ -949,25 +1001,5 @@ bool TryGetRootRef(out TrieNode? rootRef) [DoesNotReturn, StackTraceHidden] static void ThrowReadOnlyTrieException() => throw new TrieException("Commits are not allowed on this trie."); - - [DoesNotReturn, StackTraceHidden] - private static void ThrowInvalidDataException(TrieNode originalNode) - { - throw new InvalidDataException( - $"Extension {originalNode.Keccak} has no child."); - } - - [DoesNotReturn, StackTraceHidden] - private static void ThrowMissingChildException(TrieNode node) - { - throw new TrieException( - $"Found an {nameof(NodeType.Extension)} {node.Keccak} that is missing a child."); - } - - [DoesNotReturn, StackTraceHidden] - private static void ThrowMissingPrefixException() - { - throw new InvalidDataException("An attempt to visit a node without a prefix path."); - } } } diff --git a/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs b/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs index 0fd1def82394..0cb466b033c3 100644 --- a/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs @@ -2,30 +2,31 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Concurrent; using System.Numerics; +using System.Runtime.CompilerServices; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using Nethermind.Trie.Pruning; namespace Nethermind.Trie; -public class PreCachedTrieStore : ITrieStore +public sealed class PreCachedTrieStore : ITrieStore { private readonly ITrieStore _inner; - private readonly ConcurrentDictionary _preBlockCache; - private readonly Func _loadRlp; - private readonly Func _tryLoadRlp; + private readonly NodeStorageCache _preBlockCache; + private readonly SeqlockCache.ValueFactory _loadRlp; + private readonly SeqlockCache.ValueFactory _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); - _tryLoadRlp = (NodeKey key) => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash, flags: ReadFlags.None); + _loadRlp = (in NodeKey key) => _inner.LoadRlp(key.Address, in key.Path, key.Hash, flags: ReadFlags.None); + _tryLoadRlp = (in NodeKey key) => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash, flags: ReadFlags.None); } public void Dispose() @@ -43,14 +44,6 @@ public IBlockCommitter BeginBlockCommit(long blockNumber) return _inner.BeginBlockCommit(blockNumber); } - public bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 keccak) - { - byte[]? rlp = _preBlockCache.GetOrAdd(new(address, in path, in keccak), - key => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash)); - - return rlp is not null; - } - public bool HasRoot(Hash256 stateRoot) => _inner.HasRoot(stateRoot); public IDisposable BeginScope(BlockHeader? baseBlock) => _inner.BeginScope(baseBlock); @@ -62,17 +55,17 @@ public bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 kecc public byte[]? LoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => _preBlockCache.GetOrAdd(new(address, in path, hash), flags == ReadFlags.None ? _loadRlp : - key => _inner.LoadRlp(key.Address, in key.Path, key.Hash, flags)); + (in NodeKey key) => _inner.LoadRlp(key.Address, in key.Path, key.Hash, flags)); public byte[]? TryLoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => _preBlockCache.GetOrAdd(new(address, in path, hash), flags == ReadFlags.None ? _tryLoadRlp : - key => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash, flags)); + (in NodeKey key) => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash, flags)); public INodeStorage.KeyScheme Scheme => _inner.Scheme; } -public readonly struct NodeKey : IEquatable +public readonly struct NodeKey : IEquatable, IHash64bit { public readonly Hash256? Address; public readonly TreePath Path; @@ -92,15 +85,28 @@ public NodeKey(Hash256? address, in TreePath path, Hash256 hash) Hash = hash; } - public bool Equals(NodeKey other) => - Address == other.Address && Path.Equals(in other.Path) && Hash.Equals(other.Hash); + public bool Equals(NodeKey other) => Equals(in other); public override bool Equals(object? obj) => obj is NodeKey key && Equals(key); + public bool Equals(in NodeKey other) => + Address == other.Address && Path.Equals(in other.Path) && Hash.Equals(other.Hash); + public override int GetHashCode() { uint hashCode0 = (uint)Hash.GetHashCode(); ulong hashCode1 = ((ulong)(uint)Path.GetHashCode() << 32) | (uint)(Address?.GetHashCode() ?? 1); return (int)BitOperations.Crc32C(hashCode0, hashCode1); } + + public long GetHashCode64() + { + long hashCode0 = Address is null ? 1L : SpanExtensions.FastHash64For32Bytes(ref Unsafe.As(ref Unsafe.AsRef(in Address.ValueHash256))); + long hashCode1 = SpanExtensions.FastHash64For32Bytes(ref Unsafe.As(ref Unsafe.AsRef(in Hash.ValueHash256))); + long hashCode2 = SpanExtensions.FastHash64For32Bytes(ref Unsafe.As(ref Unsafe.AsRef(in Path.Path))); + + // Rotations spaced by 64/3 ensure way 0 (bits 0-13) and way 1 (bits 42-55) + // sample non-overlapping 14-bit windows from each input + return hashCode1 + (long)BitOperations.RotateLeft((ulong)hashCode0, 21) + (long)BitOperations.RotateLeft((ulong)hashCode2, 42); + } } diff --git a/src/Nethermind/Nethermind.Trie/Pruning/BlockCommitPackage.cs b/src/Nethermind/Nethermind.Trie/Pruning/BlockCommitPackage.cs index 56f610f9dba8..1df8cfa11a2a 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/BlockCommitPackage.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/BlockCommitPackage.cs @@ -1,21 +1,31 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Diagnostics; +using Nethermind.Core.Crypto; namespace Nethermind.Trie.Pruning { - public class BlockCommitSet(long blockNumber) + public class BlockCommitSet(long blockNumber) : IComparable { public long BlockNumber { get; } = blockNumber; public TrieNode? Root { get; private set; } + public Hash256 StateRoot => Root?.Keccak ?? Keccak.EmptyTreeHash; - public bool IsSealed => Root is not null; + private bool _isSealed; + + /// + /// A commit set is sealed once has been called, regardless of whether the root is null. + /// A null root is valid for an empty state trie (e.g., genesis blocks with no allocations). + /// + public bool IsSealed => _isSealed; public void Seal(TrieNode? root) { Root = root; + _isSealed = true; } public override string ToString() => $"{BlockNumber}({Root})"; @@ -37,5 +47,13 @@ public void Prune() Metrics.DeepPruningTime = (long)Stopwatch.GetElapsedTime(start).TotalMilliseconds; } + public int CompareTo(BlockCommitSet? other) + { + if (ReferenceEquals(this, other)) return 0; + if (other is null) return 1; + int comp = BlockNumber.CompareTo(other.BlockNumber); + if (comp != 0) return comp; + return StateRoot.CompareTo(other.StateRoot); + } } } diff --git a/src/Nethermind/Nethermind.Trie/Pruning/CommitSetQueue.cs b/src/Nethermind/Nethermind.Trie/Pruning/CommitSetQueue.cs new file mode 100644 index 000000000000..8ee77f2fb631 --- /dev/null +++ b/src/Nethermind/Nethermind.Trie/Pruning/CommitSetQueue.cs @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; + +namespace Nethermind.Trie.Pruning; + +public class CommitSetQueue +{ + private SortedSet _queue = new(); + + public int Count + { + get + { + lock (_queue) + { + return _queue.Count; + } + } + } + + public bool IsEmpty => Count == 0; + public long? MinBlockNumber + { + get + { + lock (_queue) return _queue.Min?.BlockNumber; + } + } + + public long? MaxBlockNumber + { + get + { + lock (_queue) return _queue.Max?.BlockNumber; + } + } + + public void Enqueue(BlockCommitSet set) + { + lock (_queue) _queue.Add(set); + } + + public bool TryPeek(out BlockCommitSet? blockCommitSet) + { + lock (_queue) + { + if (_queue.Count == 0) + { + blockCommitSet = null; + return false; + } + + blockCommitSet = _queue.Min; + return true; + } + } + + public bool TryDequeue(out BlockCommitSet? blockCommitSet) + { + lock (_queue) + { + if (_queue.Count == 0) + { + blockCommitSet = null; + return false; + } + + blockCommitSet = _queue.Min; + _queue.Remove(blockCommitSet); + return true; + } + } + + public ArrayPoolListRef GetCommitSetsAtBlockNumber(long blockNumber) + { + lock (_queue) + { + BlockCommitSet lowerBound = new BlockCommitSet(blockNumber); + lowerBound.Seal(new TrieNode(NodeType.Unknown, Hash256.Zero)); + BlockCommitSet upperBound = new BlockCommitSet(blockNumber); + upperBound.Seal(new TrieNode(NodeType.Unknown, Keccak.MaxValue)); + + var result = new ArrayPoolListRef(); + result.AddRange(_queue.GetViewBetween(lowerBound, upperBound)); + return result; + } + } + + public ArrayPoolListRef GetAndDequeueCommitSetsBeforeOrAt(long blockNumber) + { + lock (_queue) + { + var result = new ArrayPoolListRef(); + while (_queue.Count > 0) + { + BlockCommitSet min = _queue.Min; + if (min.BlockNumber > blockNumber) break; + result.Add(min); + _queue.Remove(min); + } + + return result; + } + } + + public void Remove(BlockCommitSet blockCommitSet) + { + lock (_queue) _queue.Remove(blockCommitSet); + } +} diff --git a/src/Nethermind/Nethermind.Trie/Pruning/IFinalizedStateProvider.cs b/src/Nethermind/Nethermind.Trie/Pruning/IFinalizedStateProvider.cs new file mode 100644 index 000000000000..7959a026a9f2 --- /dev/null +++ b/src/Nethermind/Nethermind.Trie/Pruning/IFinalizedStateProvider.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; + +namespace Nethermind.Trie.Pruning; + +public interface IFinalizedStateProvider +{ + long FinalizedBlockNumber { get; } + Hash256? GetFinalizedStateRootAt(long blockNumber); +} diff --git a/src/Nethermind/Nethermind.Trie/Pruning/IScopedTrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/IScopedTrieStore.cs index 2c44d3e83438..82b124f757ec 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/IScopedTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/IScopedTrieStore.cs @@ -15,9 +15,6 @@ public interface IScopedTrieStore : ITrieNodeResolver { // Begins a commit to update the trie store. The `ICommitter` provide `CommitNode` to add node into. ICommitter BeginCommit(TrieNode? root, WriteFlags writeFlags = WriteFlags.None); - - // Only used by snap provider, so ValueHash instead of Hash - bool IsPersisted(in TreePath path, in ValueHash256 keccak); } public interface ICommitter : IDisposable diff --git a/src/Nethermind/Nethermind.Trie/Pruning/ITrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/ITrieStore.cs index 2ff035ca49da..3d266231bb53 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/ITrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/ITrieStore.cs @@ -34,7 +34,6 @@ public interface IScopableTrieStore TrieNode FindCachedOrUnknown(Hash256? address, in TreePath path, Hash256 hash); byte[]? LoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None); byte[]? TryLoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None); - bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 keccak); INodeStorage.KeyScheme Scheme { get; } } diff --git a/src/Nethermind/Nethermind.Trie/Pruning/MaxBlockInCachePruneStrategy.cs b/src/Nethermind/Nethermind.Trie/Pruning/MaxBlockInCachePruneStrategy.cs index 138a87c6914d..ac2272bf0f87 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/MaxBlockInCachePruneStrategy.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/MaxBlockInCachePruneStrategy.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; - namespace Nethermind.Trie.Pruning; public class MaxBlockInCachePruneStrategy(IPruningStrategy baseStrategy, long maxBlockFromPersisted, long pruneBoundary) : IPruningStrategy diff --git a/src/Nethermind/Nethermind.Trie/Pruning/MemoryLimit.cs b/src/Nethermind/Nethermind.Trie/Pruning/MemoryLimit.cs index ce198f13f234..730abe6cf2b6 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/MemoryLimit.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/MemoryLimit.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Diagnostics; -using Nethermind.Core; namespace Nethermind.Trie.Pruning { diff --git a/src/Nethermind/Nethermind.Trie/Pruning/NoPruning.cs b/src/Nethermind/Nethermind.Trie/Pruning/NoPruning.cs index 5f21e8c9f544..e507f70fe847 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/NoPruning.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/NoPruning.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Nethermind.Core; - namespace Nethermind.Trie.Pruning { public class NoPruning : IPruningStrategy diff --git a/src/Nethermind/Nethermind.Trie/Pruning/NullCommitter.cs b/src/Nethermind/Nethermind.Trie/Pruning/NullCommitter.cs index b4a68bee09de..60e8b665ee2c 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/NullCommitter.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/NullCommitter.cs @@ -1,9 +1,6 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Nethermind.Core; -using Nethermind.Core.Crypto; - namespace Nethermind.Trie.Pruning; public class NullCommitter : ICommitter, IBlockCommitter diff --git a/src/Nethermind/Nethermind.Trie/Pruning/NullTrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/NullTrieStore.cs index 6eaf83163a35..7ca78d35ba59 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/NullTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/NullTrieStore.cs @@ -20,8 +20,6 @@ private NullTrieStore() { } public ICommitter BeginCommit(TrieNode? root, WriteFlags writeFlags = WriteFlags.None) => NullCommitter.Instance; - public bool IsPersisted(in TreePath path, in ValueHash256 keccak) => true; - public void Set(in TreePath path, in ValueHash256 keccak, byte[] rlp) { } public ITrieNodeResolver GetStorageTrieNodeResolver(Hash256 storageRoot) => this; diff --git a/src/Nethermind/Nethermind.Trie/Pruning/OverlayTrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/OverlayTrieStore.cs index 72dce31c2d2f..8d17fea09e1e 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 { @@ -37,8 +37,6 @@ public TrieNode FindCachedOrUnknown(Hash256? address, in TreePath path, Hash256 public byte[]? TryLoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => _nodeStorage.Get(address, in path, hash, flags) ?? baseStore.TryLoadRlp(address, in path, hash, flags); - public bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 keccak) => _nodeStorage.Get(address, in path, in keccak) is not null || baseStore.IsPersisted(address, in path, in keccak); - public bool HasRoot(Hash256 stateRoot) => _nodeStorage.Get(null, TreePath.Empty, stateRoot) is not null || baseStore.HasRoot(stateRoot); public IDisposable BeginScope(BlockHeader? baseBlock) => baseStore.BeginScope(baseBlock); diff --git a/src/Nethermind/Nethermind.Trie/Pruning/RawScopedTrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/RawScopedTrieStore.cs index 7ee95fb8f42a..86bd98ece5eb 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/RawScopedTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/RawScopedTrieStore.cs @@ -31,8 +31,6 @@ public RawScopedTrieStore(IKeyValueStoreWithBatching kv, Hash256? address = null public ICommitter BeginCommit(TrieNode? root, WriteFlags writeFlags = WriteFlags.None) => new Committer(nodeStorage, address, writeFlags); - public bool IsPersisted(in TreePath path, in ValueHash256 keccak) => nodeStorage.KeyExists(address, path, keccak); - public class Committer(INodeStorage nodeStorage, Hash256? address, WriteFlags writeFlags) : ICommitter { INodeStorage.IWriteBatch _writeBatch = nodeStorage.StartWriteBatch(); diff --git a/src/Nethermind/Nethermind.Trie/Pruning/ReadOnlyTrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/ReadOnlyTrieStore.cs index 4fb0266eca99..73ee3dd2a157 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/ReadOnlyTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/ReadOnlyTrieStore.cs @@ -23,8 +23,6 @@ public byte[] LoadRlp(Hash256? address, in TreePath treePath, Hash256 hash, Read public byte[]? TryLoadRlp(Hash256? address, in TreePath treePath, Hash256 hash, ReadFlags flags) => _trieStore.TryLoadRlp(address, treePath, hash, flags); - public bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 keccak) => _trieStore.IsPersisted(address, path, keccak); - public ICommitter BeginCommit(Hash256? address, TrieNode? root, WriteFlags writeFlags) => NullCommitter.Instance; public IBlockCommitter BeginBlockCommit(long blockNumber) @@ -56,9 +54,6 @@ public ITrieNodeResolver GetStorageTrieNodeResolver(Hash256? address1) => public INodeStorage.KeyScheme Scheme => fullTrieStore.Scheme; public ICommitter BeginCommit(TrieNode? root, WriteFlags writeFlags = WriteFlags.None) => NullCommitter.Instance; - - public bool IsPersisted(in TreePath path, in ValueHash256 keccak) => - fullTrieStore.IsPersisted(address, path, in keccak); } } } diff --git a/src/Nethermind/Nethermind.Trie/Pruning/ScopedTrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/ScopedTrieStore.cs index 5548a1d6bf58..249014b87d28 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/ScopedTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/ScopedTrieStore.cs @@ -24,7 +24,4 @@ public ITrieNodeResolver GetStorageTrieNodeResolver(Hash256? address1) => public ICommitter BeginCommit(TrieNode? root, WriteFlags writeFlags = WriteFlags.None) => fullTrieStore.BeginCommit(address, root, writeFlags); - - public bool IsPersisted(in TreePath path, in ValueHash256 keccak) => - fullTrieStore.IsPersisted(address, path, in keccak); } diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TreePath.cs b/src/Nethermind/Nethermind.Trie/Pruning/TreePath.cs index 9714b92ddbc3..eec30ada3850 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; @@ -338,6 +339,25 @@ public readonly int CompareToTruncated(in TreePath otherTree, int length) return length.CompareTo(otherTree.Length); } + /// + /// Returns the Path extended to 64 nibbles with 0xF (upper bound of subtree). + /// + public readonly ValueHash256 ToUpperBoundPath() + { + ValueHash256 result = Path; + Span bytes = result.BytesAsSpan; + + int startByte = Length / 2; + if (Length % 2 == 1) + { + bytes[startByte] |= 0x0F; + startByte++; + } + bytes[startByte..].Fill(0xFF); + + return result; + } + private static ReadOnlySpan ZeroMasksData => new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs index 1e14a0875b53..4b92db781b39 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs @@ -22,16 +22,18 @@ 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 { + private const double PruningEfficiencyWarningThreshold = 0.9; private readonly int _shardedDirtyNodeCount = 256; private readonly int _shardBit = 8; private readonly int _maxBufferedCommitCount; private readonly int _maxDepth; private readonly double _prunePersistedNodePortion; private readonly long _prunePersistedNodeMinimumTarget; + private readonly int _pruneDelayMs; private int _isFirst; @@ -49,7 +51,7 @@ public sealed class TrieStore : ITrieStore, IPruningTrieStore // Small optimization to not re-create CommitBuffer private CommitBuffer? _commitBufferUnused = null; - private bool IsInCommitBufferMode => _commitBuffer is not null; + internal bool IsInCommitBufferMode => _commitBuffer is not null; // Only one scope can be active at the same time. Any mutation to trieStore as part of block processing need to // acquire _scopeLock. @@ -62,13 +64,17 @@ public sealed class TrieStore : ITrieStore, IPruningTrieStore private readonly bool _pastKeyTrackingEnabled = false; private bool _lastPersistedReachedReorgBoundary; + private long _toBePersistedBlockNumber = -1; + private Task _pruningTask = Task.CompletedTask; private readonly CancellationTokenSource _pruningTaskCancellationTokenSource = new(); + private readonly IFinalizedStateProvider _finalizedStateProvider; public TrieStore( INodeStorage nodeStorage, IPruningStrategy pruningStrategy, IPersistenceStrategy persistenceStrategy, + IFinalizedStateProvider finalizedStateProvider, IPruningConfig pruningConfig, ILogManager logManager) { @@ -76,6 +82,8 @@ public TrieStore( _nodeStorage = nodeStorage; _pruningStrategy = pruningStrategy; _persistenceStrategy = persistenceStrategy; + _finalizedStateProvider = finalizedStateProvider; + _publicStore = new TrieKeyValueStore(this); _persistedNodeRecorder = PersistedNodeRecorder; _persistedNodeRecorderNoop = PersistedNodeRecorderNoop; @@ -84,6 +92,9 @@ public TrieStore( _prunePersistedNodeMinimumTarget = pruningConfig.PrunePersistedNodeMinimumTarget; _maxBufferedCommitCount = pruningConfig.MaxBufferedCommitCount; + _pastKeyTrackingEnabled = pruningConfig.TrackPastKeys && nodeStorage.RequirePath; + _pruneDelayMs = pruningConfig.PruneDelayMilliseconds; + _deleteOldNodes = _pruningStrategy.DeleteObsoleteKeys && _pastKeyTrackingEnabled; _shardBit = pruningConfig.DirtyNodeShardBit; _shardedDirtyNodeCount = 1 << _shardBit; _dirtyNodes = new TrieStoreDirtyNodesCache[_shardedDirtyNodeCount]; @@ -91,12 +102,9 @@ public TrieStore( _persistedHashes = new ConcurrentDictionary[_shardedDirtyNodeCount]; for (int i = 0; i < _shardedDirtyNodeCount; i++) { - _dirtyNodes[i] = new TrieStoreDirtyNodesCache(this, !_nodeStorage.RequirePath, _logger); + _dirtyNodes[i] = new TrieStoreDirtyNodesCache(this, !_nodeStorage.RequirePath, keepRoot: _deleteOldNodes, _logger); _persistedHashes[i] = new ConcurrentDictionary(); } - - _deleteOldNodes = _pruningStrategy.DeleteObsoleteKeys; - _pastKeyTrackingEnabled = pruningConfig.TrackPastKeys && nodeStorage.RequirePath; } public IScopedTrieStore GetTrieStore(Hash256? address) => new ScopedTrieStore(this, address); @@ -225,6 +233,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(); } @@ -321,22 +330,52 @@ long blockNumber public IDisposable BeginScope(BlockHeader? baseBlock) { _scopeLock.Enter(); - if (_pruningLock.TryEnter()) + + SpinWait spinWait = new SpinWait(); + while (true) { - // When in non commit buffer mode, FindCachedOrUnknown can also modify the dirty cache which has - // a notable performance benefit. So we try to clear the buffer before. - FlushCommitBufferNoLock(); + if (_pruningLock.TryEnter()) + { + // When in non commit buffer mode, FindCachedOrUnknown can also modify the dirty cache which has + // a notable performance benefit. So we try to clear the buffer before. + FlushCommitBufferNoLock(); - return new Reactive.AnonymousDisposable(() => + return new Reactive.AnonymousDisposable(() => + { + _pruningLock.Exit(); + _scopeLock.Exit(); + }); + } + + if (_commitBuffer is null) { - _pruningLock.Exit(); - _scopeLock.Exit(); - }); + long persistedBoundary = Interlocked.Read(ref _toBePersistedBlockNumber); + if (persistedBoundary == -1) + { + // This can happen in the tiny time in between pruningLock was acquired but the exact block to + // persist was not determined yet. + spinWait.SpinOnce(); + continue; + } + + // _dirtyNodesLock was not acquired, likely due to memory pruning. + // Will continue with commit buffer. + CommitBuffer? commitBuffer = _commitBufferUnused; + if (commitBuffer is null) + { + commitBuffer = new CommitBuffer(this, persistedBoundary); + } + else + { + commitBuffer.Reset(persistedBoundary); + } + + _commitBuffer = commitBuffer; + } + + break; } - // _dirtyNodesLock was not acquired, likely due to memory pruning. - // Will continue with commit buffer. - if (_commitBuffer is null) _commitBuffer = _commitBufferUnused ?? new CommitBuffer(this); if (_commitBuffer.CommitCount >= _maxBufferedCommitCount) { // Prevent commit buffer from becoming too large. @@ -424,8 +463,8 @@ public IBlockCommitter BeginBlockCommit(long blockNumber) private void FinishBlockCommit(BlockCommitSet set, TrieNode? root) { - if (_logger.IsTrace) _logger.Trace($"Enqueued blocks {_commitSetQueue?.Count ?? 0}"); - // Note: root is null when the state trie is empty. It will therefore make the block commit set not sealed. + if (_logger.IsTrace) _logger.Trace($"Enqueued blocks {_commitSetQueue.Count}"); + // Note: root can be null when the state trie is empty (e.g., genesis with no allocations). set.Seal(root); set.Prune(); @@ -482,20 +521,6 @@ static void ThrowMissingNode(Hash256? address, in TreePath path, Hash256 keccak) } } - public bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 keccak) - { - byte[]? rlp = _nodeStorage.Get(address, path, keccak, ReadFlags.None); - - if (rlp is null) - { - return false; - } - - Metrics.LoadedFromDbNodesCount++; - - return true; - } - public IReadOnlyTrieStore AsReadOnly() => new ReadOnlyTrieStore(this); public bool IsNodeCached(Hash256? address, in TreePath path, Hash256? hash) => DirtyNodesIsNodeCached(new TrieStoreDirtyNodesCache.Key(address, path, hash)); @@ -535,53 +560,137 @@ private TrieStoreState CaptureCurrentState() public void Prune() { - var state = CaptureCurrentState(); + TrieStoreState state = CaptureCurrentState(); if ((_pruningStrategy.ShouldPruneDirtyNode(state) || _pruningStrategy.ShouldPrunePersistedNode(state)) && _pruningTask.IsCompleted) { - _pruningTask = Task.Run(() => + _pruningTask = TrySyncPrune(); + } + } + + 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); + } + + using (_pruningLock.EnterScope()) + { + // Skip triggering GC while pruning so they don't fight each other causing pruning to take longer + GCScheduler.Instance.SkipNextGC(); + SyncPruneNonLocked(); + } + + TryExitCommitBufferMode(); + } + + // Testing purpose only + internal void SyncPruneQueue() + { + using (var _ = _pruningLock.EnterScope()) + { + SyncPruneNonLocked(); + } + + TryExitCommitBufferMode(); + } + + private void SyncPruneNonLocked() + { + Debug.Assert(_pruningLock.IsHeldByCurrentThread, "Pruning lock must be held to perform sync prune."); + + if (_pruningStrategy.ShouldPruneDirtyNode(CaptureCurrentState())) + { + PersistAndPruneDirtyCache(); + } + + if (_prunePersistedNodePortion > 0) + { + try { - using (var _ = _pruningLock.EnterScope()) + // 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 startingShard = _lastPrunedShardIdx; + while (_lastPrunedShardIdx - startingShard < _shardedDirtyNodeCount && _pruningStrategy.ShouldPrunePersistedNode(CaptureCurrentState())) { - if (_pruningStrategy.ShouldPruneDirtyNode(CaptureCurrentState())) - { - PersistAndPruneDirtyCache(); - } - - if (_pruningStrategy.ShouldPrunePersistedNode(CaptureCurrentState())) - { - PrunePersistedNodes(); - } + PrunePersistedNodes(); } - TryExitCommitBufferMode(); - }); + 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; + } } } - private void PersistAndPruneDirtyCache() + internal void PersistAndPruneDirtyCache() { try { long start = Stopwatch.GetTimestamp(); - if (_logger.IsDebug) _logger.Debug($"Locked {nameof(TrieStore)} for pruning."); + if (_logger.IsInfo) _logger.Info($"Starting memory pruning. Dirty memory {DirtyMemoryUsedByDirtyCache / 1.MiB()}MB, Persisted node memory {(PersistedMemoryUsedByDirtyCache / 1.MiB())}MB"); long memoryUsedByDirtyCache = DirtyMemoryUsedByDirtyCache; 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; Metrics.PruningTime = ms; if (_logger.IsInfo) _logger.Info($"Executed memory prune. Took {ms:0.##} ms. Dirty memory from {memoryUsedByDirtyCache / 1.MiB()}MB to {DirtyMemoryUsedByDirtyCache / 1.MiB()}MB"); + // Warn if pruning did not reduce the dirty cache significantly + if (_logger.IsWarn && memoryUsedByDirtyCache > 0) + { + double retentionRatio = (double)DirtyMemoryUsedByDirtyCache / memoryUsedByDirtyCache; + if (retentionRatio > PruningEfficiencyWarningThreshold) + { + long recommendedCacheMb = (long)(memoryUsedByDirtyCache / 1.MiB() * 1.3); + _logger.Warn($"Pruning cache is too low. Dirty memory reduced by only {(1 - retentionRatio) * 100:0.##}% (from {memoryUsedByDirtyCache / 1.MiB()}MB to {DirtyMemoryUsedByDirtyCache / 1.MiB()}MB). Consider increasing the pruning cache limit with --Pruning.DirtyCacheMb or --pruning-dirtycachemb (recommended: {recommendedCacheMb}MB)."); + } + } + if (_logger.IsDebug) _logger.Debug($"Pruning finished. Unlocked {nameof(TrieStore)}."); } catch (Exception e) { if (_logger.IsError) _logger.Error("Pruning failed with exception.", e); } + finally + { + _toBePersistedBlockNumber = -1; + } } private void SaveSnapshot() @@ -591,17 +700,25 @@ private void SaveSnapshot() int count = _commitSetQueue?.Count ?? 0; if (count == 0) return; - using ArrayPoolList candidateSets = DetermineCommitSetToPersistInSnapshot(count); + (ArrayPoolList candidateSets, long? finalizedBlockNumber) = DetermineCommitSetToPersistInSnapshot(count); + using var _ = candidateSets; bool shouldTrackPastKey = // Its disabled _pastKeyTrackingEnabled && // Full pruning need to visit all node, so can't delete anything. - (_deleteOldNodes - // If more than one candidate set, its a reorg, we can't remove node as persisted node may not be canonical - ? candidateSets.Count == 1 - // For archice node, it is safe to remove canon key from cache as it will just get re-loaded. - : true); + + // If more than one candidate set, its a reorg, we can't remove node as persisted node may not be canonical + // 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} commit sets. Finalized block number {finalizedBlockNumber}. Should track past keys {shouldTrackPastKey}"); + + if (!finalizedBlockNumber.HasValue) + { + if (_logger.IsInfo) _logger.Info($"Finalized block unknown. Persisting {candidateSets.Count} states."); + } if (shouldTrackPastKey) { @@ -614,6 +731,16 @@ private void SaveSnapshot() } } + if (candidateSets.Count > 0) + { + long minToBePersistedBlock = long.MaxValue; + foreach (BlockCommitSet blockCommitSet in candidateSets) + { + minToBePersistedBlock = Math.Min(minToBePersistedBlock, blockCommitSet.BlockNumber); + } + _toBePersistedBlockNumber = minToBePersistedBlock; + } + Action persistedNodeRecorder = shouldTrackPastKey ? _persistedNodeRecorder : _persistedNodeRecorderNoop; for (int index = 0; index < candidateSets.Count; index++) @@ -634,46 +761,100 @@ private void SaveSnapshot() if (_logger.IsDebug) _logger.Debug($"Found no candidate for elevated pruning (sets: {_commitSetQueue.Count}, earliest: {uselessFrontSet?.BlockNumber}, newest kept: {LatestCommittedBlockNumber}, reorg depth {_maxDepth})"); } - private ArrayPoolList DetermineCommitSetToPersistInSnapshot(int count) + /// + /// Determined the state that will be persisted in a snapshot. + /// If more than 1 commit set, then its an archive node. + /// + /// + /// A tuple of the block to be committed and the canonical block number if known. + private (ArrayPoolList, long?) DetermineCommitSetToPersistInSnapshot(int count) { - ArrayPoolList? candidateSets = null; + ArrayPoolList candidateSets = new(count); try { + if (_commitSetQueue.IsEmpty) + { + if (_logger.IsDebug) _logger.Debug("Unable to persist commit set due to empty queue"); + return (candidateSets, null); + } + + long finalizedBlockNumber = _finalizedStateProvider.FinalizedBlockNumber; + long pruningBoundaryBlockNumber = _commitSetQueue.MaxBlockNumber.Value - _maxDepth; + long effectiveFinalizedBlockNumber = Math.Min(pruningBoundaryBlockNumber, finalizedBlockNumber); + effectiveFinalizedBlockNumber = Math.Max(0, effectiveFinalizedBlockNumber); - long lastBlockBeforeRorgBoundary = 0; - foreach (BlockCommitSet blockCommitSet in _commitSetQueue) + if (effectiveFinalizedBlockNumber < _commitSetQueue.MinBlockNumber) { - if (!blockCommitSet.IsSealed) continue; - if (blockCommitSet.BlockNumber <= LatestCommittedBlockNumber - _maxDepth && blockCommitSet.BlockNumber > lastBlockBeforeRorgBoundary) + // Finalized block number far behind any commit. Persist everything so that it can be pruned, but not after + // pruning boundary point as snap sync need it. + using ArrayPoolListRef commitSet = _commitSetQueue.GetAndDequeueCommitSetsBeforeOrAt(pruningBoundaryBlockNumber); + + if (commitSet.Count > 0) + { + if (_logger.IsDebug) _logger.Debug($"Committing {commitSet.Count} commit sets after finalized block. Effective finalized block: {effectiveFinalizedBlockNumber}, Finalized block number: {finalizedBlockNumber}"); + candidateSets.AddRange(commitSet.AsSpan()); + } + else { - lastBlockBeforeRorgBoundary = blockCommitSet.BlockNumber; + // 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); } - using ArrayPoolList toAddBack = new(count); - candidateSets = new(count); - while (_commitSetQueue.TryDequeue(out BlockCommitSet frontSet)) + using ArrayPoolListRef commitSetsAtFinalizedBlock = _commitSetQueue.GetCommitSetsAtBlockNumber(effectiveFinalizedBlockNumber); + + BlockCommitSet? finalizedBlockCommitSet = null; + Hash256? finalizedStateRoot = _finalizedStateProvider.GetFinalizedStateRootAt(effectiveFinalizedBlockNumber); + if (finalizedStateRoot is not null) { - if (frontSet.BlockNumber == lastBlockBeforeRorgBoundary || (_persistenceStrategy.ShouldPersist(frontSet.BlockNumber) && frontSet.BlockNumber < lastBlockBeforeRorgBoundary)) + foreach (BlockCommitSet blockCommitSet in commitSetsAtFinalizedBlock) { - candidateSets.Add(frontSet); + if (blockCommitSet.StateRoot == finalizedStateRoot) + { + finalizedBlockCommitSet = blockCommitSet; + break; + } } - else if (frontSet.BlockNumber >= LatestCommittedBlockNumber - _maxDepth) + } + + if (finalizedBlockCommitSet is null) + { + // 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) + { + 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); + } + + bool finalizedWasAdded = false; + while (_commitSetQueue.TryPeek(out BlockCommitSet? set)) + { + if (set.BlockNumber > effectiveFinalizedBlockNumber) break; + + _commitSetQueue.Remove(set); + + if (_persistenceStrategy.ShouldPersist(set.BlockNumber)) { - toAddBack.Add(frontSet); + candidateSets.Add(set); + if (ReferenceEquals(set, finalizedBlockCommitSet)) finalizedWasAdded = true; } } - for (int index = 0; index < toAddBack.Count; index++) + if (!finalizedWasAdded) { - _commitSetQueue.Enqueue(toAddBack[index]); + _commitSetQueue.Remove(finalizedBlockCommitSet); + candidateSets.Add(finalizedBlockCommitSet); } - return candidateSets; + return (candidateSets, effectiveFinalizedBlockNumber); } catch { - candidateSets?.Dispose(); + candidateSets.Dispose(); throw; } } @@ -720,7 +901,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(); @@ -729,25 +910,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); @@ -757,10 +920,32 @@ 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. /// - private void PrunePersistedNodes() + internal void PrunePersistedNodes() { try { @@ -770,26 +955,26 @@ private 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.IsDebug) _logger.Debug($"Pruning persisted nodes {PersistedMemoryUsedByDirtyCache / 1.MB()} MB, Pruning {shardCountToPrune} shards starting from shard {_lastPrunedShardIdx % _shardedDirtyNodeCount}"); long start = Stopwatch.GetTimestamp(); - using ArrayPoolList pruneTask = new(shardCountToPrune); + 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()); RecalculateTotalMemoryUsage(); - if (_logger.IsWarn) _logger.Debug($"Finished pruning persisted nodes in {(long)Stopwatch.GetElapsedTime(start).TotalMilliseconds}ms {PersistedMemoryUsedByDirtyCache / 1.MB()} MB, last persisted block: {LastPersistedBlockNumber} current: {LatestCommittedBlockNumber}."); + if (_logger.IsDebug) _logger.Debug($"Finished pruning persisted nodes in {(long)Stopwatch.GetElapsedTime(start).TotalMilliseconds}ms {PersistedMemoryUsedByDirtyCache / 1.MB()} MB, last persisted block: {LastPersistedBlockNumber} current: {LatestCommittedBlockNumber}."); Metrics.PersistedNodePruningTime = (long)Stopwatch.GetElapsedTime(start).TotalMilliseconds; } catch (Exception e) @@ -855,7 +1040,8 @@ public void WaitForPruning() private readonly ILogger _logger; - private ConcurrentQueue _commitSetQueue = new ConcurrentQueue(); + private CommitSetQueue _commitSetQueue = new CommitSetQueue(); + public CommitSetQueue CommitSetQueue => _commitSetQueue; private BlockCommitSet? _lastCommitSet = null; @@ -872,7 +1058,7 @@ public void WaitForPruning() private BlockCommitter? _currentBlockCommitter = null; - private long LatestCommittedBlockNumber { get; set; } + public long LatestCommittedBlockNumber { get; set; } public INodeStorage.KeyScheme Scheme => _nodeStorage.Scheme; private void VerifyNewCommitSet(long blockNumber) @@ -912,7 +1098,7 @@ void TopLevelPersist(TrieNode tn, Hash256? address2, TreePath path) if (path.Length < parallelBoundaryPathLength) { persistedNodeRecorder.Invoke(path, address2, tn); - PersistNode(address2, path, tn, topLevelWriteBatch, writeFlags); + PersistNode(address2, path, tn, commitSet.BlockNumber, topLevelWriteBatch, writeFlags); } else { @@ -950,7 +1136,7 @@ void TopLevelPersist(TrieNode tn, Hash256? address2, TreePath path) } using ArrayPoolList persistNodeStartingFromTasks = parallelStartNodes.Select( - entry => Task.Run(() => PersistNodeStartingFrom(entry.trieNode, entry.address2, entry.path, persistedNodeRecorder, writeFlags, disposeQueue))) + entry => Task.Run(() => PersistNodeStartingFrom(entry.trieNode, entry.address2, entry.path, commitSet.BlockNumber, persistedNodeRecorder, writeFlags, disposeQueue))) .ToPooledList(parallelStartNodes.Count); Task.WaitAll(persistNodeStartingFromTasks.AsSpan()); @@ -975,6 +1161,7 @@ void TopLevelPersist(TrieNode tn, Hash256? address2, TreePath path) } private async Task PersistNodeStartingFrom(TrieNode tn, Hash256 address2, TreePath path, + long blockNumber, Action persistedNodeRecorder, WriteFlags writeFlags, Channel disposeQueue) { @@ -984,7 +1171,7 @@ private async Task PersistNodeStartingFrom(TrieNode tn, Hash256 address2, TreePa async ValueTask DoPersist(TrieNode node, Hash256? address3, TreePath path2) { persistedNodeRecorder.Invoke(path2, address3, node); - PersistNode(address3, path2, node, writeBatch, writeFlags); + PersistNode(address3, path2, node, blockNumber, writeBatch, writeFlags); persistedNodeCount++; if (persistedNodeCount % 512 == 0) @@ -998,12 +1185,17 @@ async ValueTask DoPersist(TrieNode node, Hash256? address3, TreePath path2) await disposeQueue.Writer.WriteAsync(writeBatch); } - private void PersistNode(Hash256? address, in TreePath path, TrieNode currentNode, INodeStorage.IWriteBatch writeBatch, WriteFlags writeFlags = WriteFlags.None) + private void PersistNode(Hash256? address, in TreePath path, TrieNode currentNode, long blockNumber, INodeStorage.IWriteBatch writeBatch, WriteFlags writeFlags = WriteFlags.None) { ArgumentNullException.ThrowIfNull(currentNode); if (currentNode.Keccak is not null) { + TrieStoreDirtyNodesCache.Key key = new TrieStoreDirtyNodesCache.Key(address, path, currentNode.Keccak); + // Unpersisted note may have lower commit number than its parent. This can when its child is created + // on a different block than its parent. + GetDirtyNodeShard(key).GetOrAdd(key, new TrieStoreDirtyNodesCache.NodeRecord(currentNode, blockNumber)); + if (_logger.IsTrace) _logger.Trace($"Persisting {nameof(TrieNode)} {currentNode}."); writeBatch.Set(address, path, currentNode.Keccak, currentNode.FullRlp.Span, writeFlags); currentNode.IsPersisted = true; @@ -1022,11 +1214,6 @@ public bool IsNoLongerNeeded(long lastCommit) && lastCommit < LatestCommittedBlockNumber - _maxDepth; } - private bool IsStillNeeded(long lastCommit) - { - return !IsNoLongerNeeded(lastCommit); - } - private void AnnounceReorgBoundaries() { if (LatestCommittedBlockNumber < 1) @@ -1068,31 +1255,33 @@ private void AnnounceReorgBoundaries() private void PersistOnShutdown() { - if (_commitSetQueue?.IsEmpty ?? true) return; + if (_commitSetQueue.IsEmpty) return; - using ArrayPoolList candidateSets = DetermineCommitSetToPersistInSnapshot(_commitSetQueue.Count); - if (candidateSets.Count == 0 && _commitSetQueue.TryDequeue(out BlockCommitSet anyCommmitSet)) + (ArrayPoolList candidateSets, long? finalizedBlockNumber) = DetermineCommitSetToPersistInSnapshot(_commitSetQueue.Count); + using var _ = candidateSets; + 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); + // 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."); } - INodeStorage.IWriteBatch writeBatch = _nodeStorage.StartWriteBatch(); + if (_logger.IsDebug) _logger.Debug($"On shutdown persisting {candidateSets.Count} commit sets. Finalized block is {finalizedBlockNumber}."); + for (int index = 0; index < candidateSets.Count; index++) { BlockCommitSet blockCommitSet = candidateSets[index]; if (_logger.IsDebug) _logger.Debug($"Persisting on disposal {blockCommitSet} (cache memory at {MemoryUsedByDirtyCache})"); ParallelPersistBlockCommitSet(blockCommitSet, _persistedNodeRecorderNoop); } - writeBatch.Dispose(); _nodeStorage.Flush(onlyWal: false); 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 { @@ -1107,9 +1296,9 @@ 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. - ConcurrentQueue commitSetQueue = _commitSetQueue; + CommitSetQueue commitSetQueue = _commitSetQueue; void ClearCommitSetQueue() { @@ -1139,7 +1328,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; @@ -1157,12 +1346,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)}."); @@ -1431,18 +1620,25 @@ private class CommitBuffer private readonly ILogger _logger; public int CommitCount => _commitSetQueueBuffer.Count; + private long _minCommitBlockNumber; - public CommitBuffer(TrieStore trieStore) + public CommitBuffer(TrieStore trieStore, long minCommitBlockNumber) { + _minCommitBlockNumber = minCommitBlockNumber; _trieStore = trieStore; _logger = trieStore._logger; _dirtyNodesBuffer = new TrieStoreDirtyNodesCache[trieStore._dirtyNodes.Length]; for (int i = 0; i < trieStore._shardedDirtyNodeCount; i++) { - _dirtyNodesBuffer[i] = new TrieStoreDirtyNodesCache(trieStore, !trieStore._nodeStorage.RequirePath, trieStore._logger); + _dirtyNodesBuffer[i] = new TrieStoreDirtyNodesCache(trieStore, !trieStore._nodeStorage.RequirePath, _trieStore._deleteOldNodes, trieStore._logger); } } + public void Reset(long minCommitBlockNumber) + { + _minCommitBlockNumber = minCommitBlockNumber; + } + public void EnqueueCommitSet(BlockCommitSet set) { _commitSetQueueBuffer.Enqueue(set); @@ -1485,47 +1681,50 @@ public TrieNode FindCachedOrUnknown(TrieStoreDirtyNodesCache.Key key, bool isRea TrieStoreDirtyNodesCache mainShard = _trieStore._dirtyNodes[shardIdx]; var hasInBuffer = bufferShard.TryGetValue(key, out TrieNode bufferNode); - if (!hasInBuffer && mainShard.TryGetRecord(key, out TrieStoreDirtyNodesCache.NodeRecord nodeRecord)) + if (isReadOnly) { - if (_trieStore.IsStillNeeded(nodeRecord.LastCommit)) + if (hasInBuffer) { - var rlp = nodeRecord.Node.FullRlp; - if (rlp.IsNull) - { - bufferShard.GetOrAdd(key, nodeRecord); - if (!isReadOnly) return nodeRecord.Node; - } - else - { - // clone is as if it read only - TrieNode node = nodeRecord.Node.Clone(); - if (nodeRecord.Node.IsSealed) node.Seal(); - if (nodeRecord.Node.IsPersisted) node.IsPersisted = true; - node.Keccak = nodeRecord.Node.Keccak; - bufferShard.GetOrAdd(key, nodeRecord); - if (!isReadOnly) return nodeRecord.Node; - } + return _trieStore.CloneForReadOnly(key, bufferNode); } + + return mainShard.FromCachedRlpOrUnknown(key); } - if (hasInBuffer) + if (!hasInBuffer && mainShard.TryGetRecord(key, out TrieStoreDirtyNodesCache.NodeRecord nodeRecord)) { - if (!isReadOnly) + if (nodeRecord.Node.IsPersisted) { - return bufferNode; + // If a node is persisted, then it is either a node that was previously not persisted and not yet + // in disk, or a node that will be deleted. We must never get a node that will be deleted. + if (nodeRecord.LastCommit >= _minCommitBlockNumber) + { + bufferShard.GetOrAdd(key, new TrieStoreDirtyNodesCache.NodeRecord(nodeRecord.Node, -1)); + return nodeRecord.Node; + } } else { - return _trieStore.CloneForReadOnly(key, bufferNode); + // If it is not persisted, then its child is still referred directly. + // The child will not get unreferred until after later it and all its children was persisted. + bufferShard.GetOrAdd(key, new TrieStoreDirtyNodesCache.NodeRecord(nodeRecord.Node, -1)); + return nodeRecord.Node; } } - return isReadOnly ? bufferShard.FromCachedRlpOrUnknown(key) : bufferShard.FindCachedOrUnknown(key); + return hasInBuffer ? bufferNode : bufferShard.FindCachedOrUnknown(key); } } internal TrieNode CloneForReadOnly(in TrieStoreDirtyNodesCache.Key key, TrieNode node) { + if (node!.FullRlp.IsNull) + { + // // this happens in SyncProgressResolver + // throw new InvalidAsynchronousStateException("Read only trie store is trying to read a transient node."); + return new TrieNode(NodeType.Unknown, key.Keccak); + } + // we returning a copy to avoid multithreaded access var trieNode = new TrieNode(NodeType.Unknown, key.Keccak, node.FullRlp); trieNode.ResolveNode(GetTrieStore(key.Address), key.Path); @@ -1553,8 +1752,6 @@ public TrieNode FindCachedOrUnknown(Hash256? address, in TreePath path, Hash256 public byte[]? TryLoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => baseTrieStore.TryLoadRlp(address, in path, hash, flags); - public bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 keccak) => baseTrieStore.IsPersisted(address, in path, in keccak); - public INodeStorage.KeyScheme Scheme => baseTrieStore.Scheme; } } diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TrieStoreDirtyNodesCache.cs b/src/Nethermind/Nethermind.Trie/Pruning/TrieStoreDirtyNodesCache.cs index c74fa5711627..356f94abde04 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/TrieStoreDirtyNodesCache.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/TrieStoreDirtyNodesCache.cs @@ -35,14 +35,21 @@ internal class TrieStoreDirtyNodesCache public long TotalDirtyMemory => _totalDirtyMemory; public readonly long KeyMemoryUsage; + private readonly bool _keepRoot; - public TrieStoreDirtyNodesCache(TrieStore trieStore, bool storeByHash, ILogger logger) + public TrieStoreDirtyNodesCache(TrieStore trieStore, bool storeByHash, bool keepRoot, ILogger logger) { _trieStore = trieStore; _logger = logger; // If the nodestore indicated that path is not required, // we will use a map with hash as its key instead of the full Key to reduce memory usage. _storeByHash = storeByHash; + + // Keep root causes persisted root nodes to not get pruned out of the cache. This ensure that it will + // be deleted when another canonical state is persisted which prevent having incomplete state which can happen + // when inner nodes get deleted but the root does not. + _keepRoot = keepRoot; + // NOTE: DirtyNodesCache is already sharded. int concurrencyLevel = Math.Min(Environment.ProcessorCount * 4, 32); int initialBuckets = TrieStore.HashHelpers.GetPrime(Math.Max(31, concurrencyLevel)); @@ -87,13 +94,6 @@ public TrieNode FromCachedRlpOrUnknown(in Key key) // ReSharper disable once ConditionIsAlwaysTrueOrFalse if (TryGetValue(key, out TrieNode trieNode)) { - if (trieNode!.FullRlp.IsNull) - { - // // this happens in SyncProgressResolver - // throw new InvalidAsynchronousStateException("Read only trie store is trying to read a transient node."); - return new TrieNode(NodeType.Unknown, key.Keccak); - } - trieNode = _trieStore.CloneForReadOnly(key, trieNode); Metrics.LoadedFromCacheNodesCount++; @@ -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; } @@ -325,7 +327,7 @@ public void PruneCache( continue; } - if (_trieStore.IsNoLongerNeeded(lastCommit)) + if (_trieStore.IsNoLongerNeeded(lastCommit) && !(_keepRoot && key.IsRoot())) { RemoveNodeFromCache(key, node, ref Metrics.PrunedPersistedNodesCount); continue; @@ -499,11 +501,20 @@ public override string ToString() { return $"A:{Address} P:{Path} K:{Keccak}"; } + + public bool IsRoot() + { + 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/RawTrieStore.cs b/src/Nethermind/Nethermind.Trie/RawTrieStore.cs new file mode 100644 index 000000000000..721d5ad7acfb --- /dev/null +++ b/src/Nethermind/Nethermind.Trie/RawTrieStore.cs @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Trie.Pruning; + +namespace Nethermind.Trie; + +/// +/// Expose interface directly backed by without any pruning +/// or buffering. +/// +/// +public class RawTrieStore(INodeStorage nodeStorage) : IReadOnlyTrieStore +{ + public RawTrieStore(IKeyValueStoreWithBatching kv) : this(new NodeStorage(kv)) + { + } + + void IDisposable.Dispose() + { + } + + public virtual ICommitter BeginCommit(Hash256? address, TrieNode? root, WriteFlags writeFlags) + { + return new RawScopedTrieStore.Committer(nodeStorage, address, writeFlags); + } + + public TrieNode FindCachedOrUnknown(Hash256? address, in TreePath path, Hash256 hash) + { + return new TrieNode(NodeType.Unknown, hash); + } + + public byte[]? LoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags) + { + byte[]? ret = nodeStorage.Get(address, path, hash, flags); + if (ret is null) throw new MissingTrieNodeException("Node missing", address, path, hash); + return ret; + } + + public byte[]? TryLoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags) + { + return nodeStorage.Get(address, path, hash, flags); + } + + public INodeStorage.KeyScheme Scheme { get; } = nodeStorage.Scheme; + + public bool HasRoot(Hash256 stateRoot) + { + return nodeStorage.KeyExists(null, TreePath.Empty, stateRoot); + } + + public IDisposable BeginScope(BlockHeader? baseBlock) + { + return new Reactive.AnonymousDisposable(() => { }); + } + + public IScopedTrieStore GetTrieStore(Hash256? address) + { + return new RawScopedTrieStore(nodeStorage, address); + } + + public IBlockCommitter BeginBlockCommit(long blockNumber) + { + return NullCommitter.Instance; + } +} diff --git a/src/Nethermind/Nethermind.Trie/TrackingCappedArrayPool.cs b/src/Nethermind/Nethermind.Trie/TrackingCappedArrayPool.cs index 82c39b94b134..c0b679074f99 100644 --- a/src/Nethermind/Nethermind.Trie/TrackingCappedArrayPool.cs +++ b/src/Nethermind/Nethermind.Trie/TrackingCappedArrayPool.cs @@ -3,7 +3,9 @@ using System; using System.Buffers; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Nethermind.Core.Buffers; @@ -13,21 +15,16 @@ namespace Nethermind.Trie; /// /// Track every rented CappedArray and return them all at once /// -public sealed class TrackingCappedArrayPool : ICappedArrayPool, IDisposable +public sealed class TrackingCappedArrayPool(int initialCapacity, ArrayPool arrayPool = null, bool canBeParallel = true) : ICappedArrayPool, IDisposable { - private readonly List _rentedBuffers; - private readonly ArrayPool? _arrayPool; + private readonly ConcurrentQueue? _rentedQueue = canBeParallel ? new() : null; + private readonly List? _rentedList = canBeParallel ? null : new(initialCapacity); + private readonly ArrayPool? _arrayPool = arrayPool; public TrackingCappedArrayPool() : this(0) { } - public TrackingCappedArrayPool(int initialCapacity, ArrayPool arrayPool = null) - { - _rentedBuffers = new List(initialCapacity); - _arrayPool = arrayPool; - } - public CappedArray Rent(int size) { if (size == 0) @@ -37,9 +34,16 @@ public CappedArray Rent(int size) // Devirtualize shared array pool by referring directly to it byte[] array = _arrayPool?.Rent(size) ?? ArrayPool.Shared.Rent(size); - CappedArray rented = new CappedArray(array, size); + CappedArray rented = new(array, size); array.AsSpan().Clear(); - lock (_rentedBuffers) _rentedBuffers.Add(array); + if (_rentedQueue is not null) + { + _rentedQueue.Enqueue(array); + } + else + { + _rentedList.Add(array); + } return rented; } @@ -49,9 +53,16 @@ public void Return(in CappedArray buffer) public void Dispose() { - if (_arrayPool is null) + if (_arrayPool is not null) + { + DisposeCustomArrayPool(); + return; + } + + ConcurrentQueue? rentedQueue = _rentedQueue; + if (rentedQueue is not null) { - foreach (byte[] rentedBuffer in CollectionsMarshal.AsSpan(_rentedBuffers)) + while (rentedQueue.TryDequeue(out byte[]? rentedBuffer)) { // Devirtualize shared array pool by referring directly to it ArrayPool.Shared.Return(rentedBuffer); @@ -59,9 +70,32 @@ public void Dispose() } else { - foreach (byte[] rentedBuffer in CollectionsMarshal.AsSpan(_rentedBuffers)) + Span items = CollectionsMarshal.AsSpan(_rentedList); + foreach (byte[] rentedBuffer in items) + { + // Devirtualize shared array pool by referring directly to it + ArrayPool.Shared.Return(rentedBuffer); + } + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void DisposeCustomArrayPool() + { + ArrayPool arrayPool = _arrayPool; + if (_rentedQueue is not null) + { + while (_rentedQueue.TryDequeue(out byte[]? rentedBuffer)) + { + arrayPool.Return(rentedBuffer); + } + } + else + { + Span items = CollectionsMarshal.AsSpan(_rentedList); + foreach (byte[] rentedBuffer in items) { - _arrayPool.Return(rentedBuffer); + arrayPool.Return(rentedBuffer); } } } diff --git a/src/Nethermind/Nethermind.Trie/TreeDumper.cs b/src/Nethermind/Nethermind.Trie/TreeDumper.cs index 5115e4ee87bd..c9fbae5d8e87 100644 --- a/src/Nethermind/Nethermind.Trie/TreeDumper.cs +++ b/src/Nethermind/Nethermind.Trie/TreeDumper.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Text; using Nethermind.Core; using Nethermind.Core.Crypto; diff --git a/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs b/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs index 4b711e210f17..ef8b70015635 100644 --- a/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs +++ b/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs @@ -137,7 +137,7 @@ public static SpanSource RlpEncodeBranch(TrieNode item, ITrieNodeResolver tree, Metrics.TreeNodeRlpEncodings++; const int valueRlpLength = 1; - int contentLength = valueRlpLength + (UseParallel(canBeParallel) ? GetChildrenRlpLengthForBranchParallel(tree, ref path, item, pool) : GetChildrenRlpLengthForBranch(tree, ref path, item, pool)); + int contentLength = valueRlpLength + (UseParallel(canBeParallel, item) ? GetChildrenRlpLengthForBranchParallel(tree, ref path, item, pool) : GetChildrenRlpLengthForBranch(tree, ref path, item, pool)); int sequenceLength = Rlp.LengthOfSequence(contentLength); SpanSource result = pool.SafeRentBuffer(sequenceLength); Span resultSpan = result.Span; @@ -148,7 +148,26 @@ public static SpanSource RlpEncodeBranch(TrieNode item, ITrieNodeResolver tree, return result; - static bool UseParallel(bool canBeParallel) => Environment.ProcessorCount > 1 && canBeParallel; + static bool UseParallel(bool canBeParallel, TrieNode item) + { + if (Environment.ProcessorCount <= 1 || !canBeParallel) + { + return false; + } + + const int MinChildrenForParallel = 4; + int nonNullChildren = 0; + for (int i = 0; i < BranchesCount; i++) + { + object? data = item._nodeData[i]; + if (data is not null && !ReferenceEquals(data, _nullNode) && ++nonNullChildren >= MinChildrenForParallel) + { + return true; + } + } + + return false; + } } private static int GetChildrenRlpLengthForBranch(ITrieNodeResolver tree, ref TreePath path, TrieNode item, ICappedArrayPool? bufferPool) diff --git a/src/Nethermind/Nethermind.Trie/TrieNode.cs b/src/Nethermind/Nethermind.Trie/TrieNode.cs index 0bc7d8cf40c1..c288ccaf3b11 100644 --- a/src/Nethermind/Nethermind.Trie/TrieNode.cs +++ b/src/Nethermind/Nethermind.Trie/TrieNode.cs @@ -32,12 +32,8 @@ public sealed partial class TrieNode #endif private static readonly object _nullNode = new(); - private static readonly TrieNodeDecoder _nodeDecoder = new(); private static readonly AccountDecoder _accountDecoder = new(); - private static readonly Action _markPersisted = static (tn, _, _) => - tn.IsPersisted = true; - private const byte _dirtyMask = 0b001; private const byte _persistedMask = 0b010; private const byte _boundaryProof = 0b100; @@ -241,7 +237,7 @@ public bool IsValidWithOneNodeLess } } - return nonEmptyNodes > 2; + return false; } } @@ -595,6 +591,29 @@ static SpanSource ThrowUnhandledNodeType(TrieNode item) } } + public byte[]? GetInlineNodeRlp(int i) + { + SpanSource rlp = _rlp; + if (rlp.IsNull) + { + return null; + } + + ValueRlpStream rlpStream = new(rlp); + SeekChild(ref rlpStream, i); + + int prefixValue = rlpStream.PeekByte(); + if (prefixValue < 192) + { + return null; + } + else + { + int length = rlpStream.PeekNextRlpLength(); + return rlpStream.Read(length).ToArray(); + } + } + public bool GetChildHashAsValueKeccak(int i, out ValueHash256 keccak) { Unsafe.SkipInit(out keccak); @@ -641,7 +660,7 @@ static void ThrowNotABranch() } } - public bool IsChildDirty(int i) + public bool TryGetDirtyChild(int i, [NotNullWhen(true)] out TrieNode? dirtyChild) { if (IsExtension) { @@ -651,20 +670,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 +755,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 +1169,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; // } } @@ -1228,7 +1251,8 @@ private void SeekChildNotNull(ref ValueRlpStream rlpStream, int index) } else { - if (data is null) + childOrRef = data; + if (childOrRef is null) { // Allows to load children in parallel ValueRlpStream rlpStream = new ValueRlpStream(rlp); @@ -1251,12 +1275,6 @@ private void SeekChildNotNull(ref ValueRlpStream rlpStream, int index) TrieNode child = tree.FindCachedOrUnknown(childPath, keccak); data = childOrRef = child; - if (IsPersisted && !child.IsPersisted) - { - child.CallRecursively(_markPersisted, null, ref childPath, tree, false, - NullLogger.Instance); - } - break; } default: @@ -1269,10 +1287,6 @@ private void SeekChildNotNull(ref ValueRlpStream rlpStream, int index) } } } - else - { - childOrRef = data; - } } return childOrRef; @@ -1400,7 +1414,8 @@ public ref struct ChildIterator(TrieNode node) } else { - if (data is null) + childOrRef = data; + if (childOrRef is null) { if (_currentStreamIndex.HasValue && _currentStreamIndex <= i) { @@ -1446,12 +1461,6 @@ public ref struct ChildIterator(TrieNode node) TrieNode child = tree.FindCachedOrUnknown(childPath, keccak); data = childOrRef = child; - if (node.IsPersisted && !child.IsPersisted) - { - child.CallRecursively(_markPersisted, null, ref childPath, tree, false, - NullLogger.Instance); - } - break; } default: @@ -1464,10 +1473,6 @@ public ref struct ChildIterator(TrieNode node) } } } - else - { - childOrRef = data; - } } return childOrRef; @@ -1502,7 +1507,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 0469b0b439c4..7b44107d6330 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 f799f5eb20ba..747f8df258f2 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` @@ -58,11 +58,15 @@ public readonly Context AddStorage(in ValueHash256 storage) } } - public TrieStatsCollector(IKeyValueStore codeKeyValueStore, ILogManager logManager, CancellationToken cancellationToken = default) + public bool ExpectAccounts { get; } + + public TrieStatsCollector(IKeyValueStore codeKeyValueStore, ILogManager logManager, CancellationToken cancellationToken = default, bool expectAccounts = true) { _codeKeyValueStore = codeKeyValueStore ?? throw new ArgumentNullException(nameof(codeKeyValueStore)); _logger = logManager.GetClassLogger(); + ExpectAccounts = expectAccounts; _cancellationToken = cancellationToken; + _progressTracker = new VisitorProgressTracker("Trie Verification", logManager); } public TrieStats Stats { get; } = new(); @@ -90,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) @@ -108,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) @@ -124,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); @@ -147,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) @@ -180,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/TrieStoreWithReadFlags.cs b/src/Nethermind/Nethermind.Trie/TrieStoreWithReadFlags.cs index 74c51065e05f..8d8833593d47 100644 --- a/src/Nethermind/Nethermind.Trie/TrieStoreWithReadFlags.cs +++ b/src/Nethermind/Nethermind.Trie/TrieStoreWithReadFlags.cs @@ -12,7 +12,4 @@ public class TrieStoreWithReadFlags(IScopedTrieStore implementation, ReadFlags f { public ICommitter BeginCommit(TrieNode? root, WriteFlags writeFlags = WriteFlags.None) => implementation.BeginCommit(root, writeFlags); - - public bool IsPersisted(in TreePath path, in ValueHash256 keccak) => - implementation.IsPersisted(in path, in keccak); } diff --git a/src/Nethermind/Nethermind.Trie/VisitContext.cs b/src/Nethermind/Nethermind.Trie/VisitContext.cs index 72c8e0d46cfa..e636efd9557d 100644 --- a/src/Nethermind/Nethermind.Trie/VisitContext.cs +++ b/src/Nethermind/Nethermind.Trie/VisitContext.cs @@ -58,11 +58,9 @@ public SmallTrieVisitContext(TrieVisitContext trieVisitContext) } public byte Level { get; internal set; } - private byte _branchChildIndex = 255; private byte _flags = 0; private const byte StorageFlag = 1; - private const byte ExpectAccountsFlag = 2; public bool IsStorage { @@ -79,21 +77,5 @@ internal set } } } - - public bool ExpectAccounts - { - readonly get => (_flags & ExpectAccountsFlag) == ExpectAccountsFlag; - internal set - { - if (value) - { - _flags = (byte)(_flags | ExpectAccountsFlag); - } - else - { - _flags = (byte)(_flags & ~ExpectAccountsFlag); - } - } - } } } diff --git a/src/Nethermind/Nethermind.Trie/VisitorProgressTracker.cs b/src/Nethermind/Nethermind.Trie/VisitorProgressTracker.cs new file mode 100644 index 000000000000..33817dfece04 --- /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/BlobTxStorageTests.cs b/src/Nethermind/Nethermind.TxPool.Test/BlobTxStorageTests.cs index b8333b8af243..802844f03cd1 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/BlobTxStorageTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/BlobTxStorageTests.cs @@ -4,12 +4,16 @@ using System; using FluentAssertions; using Nethermind.Core; +using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Int256; using NUnit.Framework; namespace Nethermind.TxPool.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] public class BlobTxStorageTests { [Test] @@ -32,4 +36,100 @@ public void should_throw_when_trying_to_add_tx_with_null_hash() Action act = () => blobTxStorage.Add(tx); act.Should().Throw(); } + + [Test] + public void TryGetMany_should_return_zero_for_empty_batch() + { + BlobTxStorage blobTxStorage = new(); + Transaction[] results = new Transaction[0]; + + int found = blobTxStorage.TryGetMany([], 0, results); + found.Should().Be(0); + } + + [Test] + public void TryGetMany_should_batch_retrieve_stored_transactions() + { + BlobTxStorage blobTxStorage = new(); + EthereumEcdsa ecdsa = new(BlockchainIds.Mainnet); + + Transaction[] txs = new Transaction[3]; + TxLookupKey[] keys = new TxLookupKey[3]; + + for (int i = 0; i < 3; i++) + { + txs[i] = Build.A.Transaction + .WithShardBlobTxTypeAndFields() + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce((UInt256)i) + .SignedAndResolved(ecdsa, TestItem.PrivateKeys[i]).TestObject; + + blobTxStorage.Add(txs[i]); + keys[i] = new TxLookupKey(txs[i].Hash, txs[i].SenderAddress!, txs[i].Timestamp); + } + + Transaction[] results = new Transaction[3]; + int found = blobTxStorage.TryGetMany(keys, 3, results); + + found.Should().Be(3); + for (int i = 0; i < 3; i++) + { + results[i].Should().BeEquivalentTo(txs[i], static options => options + .Excluding(static t => t.GasBottleneck) + .Excluding(static t => t.PoolIndex)); + } + } + + [Test] + public void TryGetMany_should_handle_mix_of_existing_and_missing_keys() + { + BlobTxStorage blobTxStorage = new(); + EthereumEcdsa ecdsa = new(BlockchainIds.Mainnet); + + Transaction[] txs = new Transaction[2]; + for (int i = 0; i < 2; i++) + { + txs[i] = Build.A.Transaction + .WithShardBlobTxTypeAndFields() + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce((UInt256)i) + .SignedAndResolved(ecdsa, TestItem.PrivateKeys[i]).TestObject; + + blobTxStorage.Add(txs[i]); + } + + TxLookupKey[] keys = new TxLookupKey[3]; + keys[0] = new TxLookupKey(txs[0].Hash, txs[0].SenderAddress!, txs[0].Timestamp); + keys[1] = new TxLookupKey(txs[1].Hash, txs[1].SenderAddress!, txs[1].Timestamp); + keys[2] = new TxLookupKey(TestItem.KeccakA, TestItem.AddressC, UInt256.One); + + Transaction[] results = new Transaction[3]; + int found = blobTxStorage.TryGetMany(keys, 3, results); + + found.Should().Be(2); + results[0].Should().NotBeNull(); + results[1].Should().NotBeNull(); + results[2].Should().BeNull(); + } + + [Test] + public void TryGetMany_should_handle_all_missing_keys() + { + BlobTxStorage blobTxStorage = new(); + + TxLookupKey[] keys = + [ + new TxLookupKey(TestItem.KeccakA, TestItem.AddressA, UInt256.One), + new TxLookupKey(TestItem.KeccakB, TestItem.AddressB, UInt256.One), + ]; + + Transaction[] results = new Transaction[2]; + int found = blobTxStorage.TryGetMany(keys, 2, results); + + found.Should().Be(0); + results[0].Should().BeNull(); + results[1].Should().BeNull(); + } } diff --git a/src/Nethermind/Nethermind.TxPool.Test/Collections/SortedPoolTests.cs b/src/Nethermind/Nethermind.TxPool.Test/Collections/SortedPoolTests.cs index 758a9c9ff3da..b2820b770334 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/Collections/SortedPoolTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/Collections/SortedPoolTests.cs @@ -20,6 +20,8 @@ namespace Nethermind.TxPool.Test.Collections { [TestFixture] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class SortedPoolTests { private const int Capacity = 16; diff --git a/src/Nethermind/Nethermind.TxPool.Test/CompetingTransactionEqualityComparerTests.cs b/src/Nethermind/Nethermind.TxPool.Test/CompetingTransactionEqualityComparerTests.cs index 9d316746a3f5..c5fab4a47687 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/CompetingTransactionEqualityComparerTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/CompetingTransactionEqualityComparerTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.TxPool.Test { + [Parallelizable(ParallelScope.All)] public class CompetingTransactionEqualityComparerTests { public static IEnumerable TestCases diff --git a/src/Nethermind/Nethermind.TxPool.Test/DelegatedAccountFilterTest.cs b/src/Nethermind/Nethermind.TxPool.Test/DelegatedAccountFilterTest.cs index 4ad374f6f752..b7d7e3590d5c 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/DelegatedAccountFilterTest.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/DelegatedAccountFilterTest.cs @@ -18,6 +18,8 @@ using Nethermind.Core.Test; namespace Nethermind.TxPool.Test; + +[Parallelizable(ParallelScope.All)] internal class DelegatedAccountFilterTest { [Test] @@ -25,7 +27,7 @@ public void Accept_SenderIsNotDelegated_ReturnsAccepted() { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, Substitute.For(), new DelegationCache()); Transaction transaction = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; @@ -45,7 +47,7 @@ public void Accept_SenderIsDelegatedWithNoTransactionsInPool_ReturnsAccepted() stateProvider.InsertCode(code, TestItem.AddressA); IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, stateProvider, new DelegationCache()); Transaction transaction = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; @@ -61,7 +63,7 @@ public void Accept_SenderIsDelegatedWithOneTransactionInPoolWithSameNonce_Return { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); Transaction inPool = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; standardPool.TryInsert(inPool.Hash, inPool); @@ -83,7 +85,7 @@ public void Accept_SenderIsDelegatedWithOneTransactionInPoolWithDifferentNonce_R { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); Transaction inPool = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; standardPool.TryInsert(inPool.Hash, inPool); @@ -110,7 +112,7 @@ public void Accept_Eip7702IsNotActivated_ReturnsExpected(bool isActive, AcceptTx { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(isActive ? Prague.Instance : Cancun.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); Transaction inPool = Build.A.Transaction.WithNonce(0).SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; standardPool.TryInsert(inPool.Hash, inPool); @@ -139,7 +141,7 @@ public void Accept_SenderHasPendingDelegation_OnlyAcceptsIfNonceIsExactMatch(int { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegationCache pendingDelegations = new(); pendingDelegations.IncrementDelegationCount(TestItem.AddressA); @@ -160,7 +162,7 @@ public void Accept_AuthorityHasPendingTransaction_ReturnsDelegatorHasPendingTx(b { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, Substitute.For(), new()); Transaction transaction; @@ -199,7 +201,7 @@ public void Accept_SetCodeTxHasAuthorityWithPendingTx_ReturnsDelegatorHasPending { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegationCache pendingDelegations = new(); pendingDelegations.IncrementDelegationCount(TestItem.AddressA); diff --git a/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs b/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs index dce8e3bde40c..d857b84cf6b4 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs @@ -19,6 +19,8 @@ namespace Nethermind.TxPool.Test; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class NonceManagerTests { private ISpecProvider _specProvider; @@ -170,7 +172,7 @@ public void ReserveNonce_should_skip_nonce_if_TxWithNonceReceived() [Test] public void should_reuse_nonce_if_tx_rejected() { - using (NonceLocker locker = _nonceManager.ReserveNonce(TestItem.AddressA, out UInt256 nonce)) + using (_nonceManager.ReserveNonce(TestItem.AddressA, out UInt256 nonce)) { nonce.Should().Be(0); } @@ -181,7 +183,7 @@ public void should_reuse_nonce_if_tx_rejected() locker.Accept(); } - using (NonceLocker locker = _nonceManager.TxWithNonceReceived(TestItem.AddressA, 1)) { } + using (_nonceManager.TxWithNonceReceived(TestItem.AddressA, 1)) { } using (NonceLocker locker = _nonceManager.ReserveNonce(TestItem.AddressA, out UInt256 nonce)) { @@ -200,13 +202,12 @@ public void should_lock_on_same_account() { using NonceLocker locker = _nonceManager.ReserveNonce(TestItem.AddressA, out UInt256 _); }); - TimeSpan ts = TimeSpan.FromMilliseconds(100); - task.Wait(ts); + task.Wait(TimeSpan.FromMilliseconds(1_000)); task.IsCompleted.Should().Be(false); } [Test] - [Repeat(10)] + [Repeat(3)] public void should_not_lock_on_different_accounts() { using NonceLocker locker = _nonceManager.ReserveNonce(TestItem.AddressA, out UInt256 nonce); @@ -216,8 +217,7 @@ public void should_not_lock_on_different_accounts() using NonceLocker locker2 = _nonceManager.ReserveNonce(TestItem.AddressB, out UInt256 nonce2); nonce2.Should().Be(0); }); - TimeSpan ts = TimeSpan.FromMilliseconds(1000); - task.Wait(ts); + task.Wait(TimeSpan.FromMilliseconds(10_000)); task.IsCompleted.Should().Be(true); } } diff --git a/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs b/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs index cf9b832a7cb3..4bebaa002d24 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs @@ -19,6 +19,8 @@ namespace Nethermind.TxPool.Test { [TestFixture(true)] [TestFixture(false)] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class ReceiptStorageTests { private readonly bool _useEip2718; diff --git a/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs b/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs new file mode 100644 index 000000000000..1ee4370de27c --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs @@ -0,0 +1,185 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Logging; +using NUnit.Framework; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Nethermind.TxPool.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class RetryCacheTests +{ + public readonly struct ResourceRequestMessage : INew + { + public int Resource { get; init; } + public static ResourceRequestMessage New(int resourceId) => new() { Resource = resourceId }; + } + + public interface ITestHandler : IMessageHandler; + + /// + /// A test handler that tracks HandleMessage calls without using NSubstitute. + /// + private class TestHandler : ITestHandler + { + private int _handleMessageCallCount; + public int HandleMessageCallCount => _handleMessageCallCount; + public bool WasCalled => _handleMessageCallCount > 0; + public Action OnHandleMessage { get; set; } + + public void HandleMessage(ResourceRequestMessage message) + { + Interlocked.Increment(ref _handleMessageCallCount); + OnHandleMessage?.Invoke(message); + } + } + + private CancellationTokenSource _cancellationTokenSource; + private RetryCache _cache; + + private readonly int Timeout = 10000; + + [SetUp] + public void Setup() + { + _cancellationTokenSource = new CancellationTokenSource(); + _cache = new(TestLogManager.Instance, timeoutMs: Timeout / 2, token: _cancellationTokenSource.Token); + } + + [TearDown] + public void TearDown() + { + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource?.Dispose(); + } + + [Test] + public void Announced_SameResourceDifferentNode_ReturnsEnqueued() + { + AnnounceResult result1 = _cache.Announced(1, new TestHandler()); + AnnounceResult result2 = _cache.Announced(1, new TestHandler()); + + Assert.That(result1, Is.EqualTo(AnnounceResult.RequestRequired)); + Assert.That(result2, Is.EqualTo(AnnounceResult.Delayed)); + } + + [Test] + public void Announced_AfterTimeout_ExecutesRetryRequests() + { + TestHandler request1 = new(); + TestHandler request2 = new(); + + _cache.Announced(1, request1); + _cache.Announced(1, request2); + + Assert.That(() => request2.WasCalled, Is.True.After(Timeout, 100)); + Assert.That(request1.WasCalled, Is.False); + } + + [Test] + public void Announced_MultipleResources_ExecutesAllRetryRequestsExceptInitialOne() + { + TestHandler request1 = new(); + TestHandler request2 = new(); + TestHandler request3 = new(); + TestHandler request4 = new(); + + _cache.Announced(1, request1); + _cache.Announced(1, request2); + _cache.Announced(2, request3); + _cache.Announced(2, request4); + + Assert.That(() => request2.WasCalled, Is.True.After(Timeout, 100)); + Assert.That(() => request4.WasCalled, Is.True.After(Timeout, 100)); + Assert.That(request1.WasCalled, Is.False); + Assert.That(request3.WasCalled, Is.False); + } + + [Test] + public void Received_RemovesResourceFromRetryQueue() + { + _cache.Announced(1, new TestHandler()); + _cache.Received(1); + + AnnounceResult result = _cache.Announced(1, new TestHandler()); + Assert.That(result, Is.EqualTo(AnnounceResult.RequestRequired)); + } + + [Test] + public async Task Received_BeforeTimeout_PreventsRetryExecution() + { + TestHandler request = new(); + + _cache.Announced(1, request); + _cache.Announced(1, request); + _cache.Received(1); + + await Task.Delay(Timeout, _cancellationTokenSource.Token); + + Assert.That(request.WasCalled, Is.False); + } + + [Test] + public async Task Clear_cache_after_timeout() + { + Parallel.For(0, 100, (i) => + { + Parallel.For(0, 100, (j) => + { + _cache.Announced(i, new TestHandler()); + }); + }); + + await Task.Delay(Timeout * 2, _cancellationTokenSource.Token); + + Assert.That(_cache.ResourcesInRetryQueue, Is.Zero); + } + + [Test] + [Retry(3)] + public void RetryExecution_HandlesExceptions() + { + TestHandler faultyRequest = new() { OnHandleMessage = _ => throw new InvalidOperationException("Test exception") }; + TestHandler normalRequest = new(); + + _cache.Announced(1, new TestHandler()); + _cache.Announced(1, faultyRequest); + _cache.Announced(1, normalRequest); + + Assert.That(() => normalRequest.WasCalled, Is.True.After(Timeout, 100)); + } + + [Test] + public async Task CancellationToken_StopsProcessing() + { + TestHandler request = new(); + + _cache.Announced(1, request); + await _cancellationTokenSource.CancelAsync(); + await Task.Delay(Timeout); + + Assert.That(request.WasCalled, Is.False); + } + + [Test] + public async Task Announced_AfterRetryInProgress_ReturnsNew() + { + _cache.Announced(1, new TestHandler()); + + await Task.Delay(Timeout, _cancellationTokenSource.Token); + + Assert.That(() => _cache.Announced(1, new TestHandler()), Is.EqualTo(AnnounceResult.RequestRequired).After(Timeout, 100)); + } + + [Test] + public void Received_NonExistentResource_DoesNotThrow() + { + Assert.That(() => _cache.Received(999), Throws.Nothing); + } +} diff --git a/src/Nethermind/Nethermind.TxPool.Test/TestBlockTree.cs b/src/Nethermind/Nethermind.TxPool.Test/TestBlockTree.cs new file mode 100644 index 000000000000..a8473dc82663 --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool.Test/TestBlockTree.cs @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Find; +using Nethermind.Blockchain.Visitors; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; + +namespace Nethermind.TxPool.Test; + +/// +/// A minimal IBlockTree implementation for testing that avoids NSubstitute's +/// static state issues when running tests in parallel. +/// +internal class TestBlockTree : IBlockTree +{ + public Block? Head { get; set; } + public BlockHeader? BestSuggestedHeader { get; set; } + + public event EventHandler? BlockAddedToMain; + public event EventHandler? NewBestSuggestedBlock { add { } remove { } } + public event EventHandler? NewSuggestedBlock { add { } remove { } } + public event EventHandler? NewHeadBlock { add { } remove { } } + public event EventHandler? OnUpdateMainChain { add { } remove { } } + public event EventHandler? OnForkChoiceUpdated { add { } remove { } } + + public void RaiseBlockAddedToMain(BlockReplacementEventArgs args) + { + BlockAddedToMain?.Invoke(this, args); + } + + public BlockHeader FindBestSuggestedHeader() => BestSuggestedHeader!; + + // IBlockFinder implementation + public Hash256 HeadHash => Head?.Hash ?? Keccak.Zero; + public Hash256 GenesisHash => Keccak.Zero; + public Hash256? PendingHash => null; + public Hash256? FinalizedHash => null; + public Hash256? SafeHash => null; + public long? BestPersistedState { get; set; } + + public Block? FindBlock(Hash256 blockHash, BlockTreeLookupOptions options, long? blockNumber = null) => null; + public Block? FindBlock(long blockNumber, BlockTreeLookupOptions options) => null; + public bool HasBlock(long blockNumber, Hash256 blockHash) => false; + public BlockHeader? FindHeader(Hash256 blockHash, BlockTreeLookupOptions options, long? blockNumber = null) => null; + public BlockHeader? FindHeader(long blockNumber, BlockTreeLookupOptions options) => null; + public Hash256? FindBlockHash(long blockNumber) => null; + public bool IsMainChain(BlockHeader blockHeader) => false; + public bool IsMainChain(Hash256 blockHash, bool throwOnMissingHash = true) => false; + public long GetLowestBlock() => 0; + + // IBlockTree implementation + public ulong NetworkId => 1; + public ulong ChainId => 1; + public BlockHeader? Genesis => null; + public Block? BestSuggestedBody => null; + public BlockHeader? BestSuggestedBeaconHeader => null; + public BlockHeader? LowestInsertedHeader { get; set; } + public BlockHeader? LowestInsertedBeaconHeader { get; set; } + public long BestKnownNumber => Head?.Number ?? 0; + public long BestKnownBeaconNumber => 0; + public bool CanAcceptNewBlocks => true; + public (long BlockNumber, Hash256 BlockHash) SyncPivot { get; set; } + public bool IsProcessingBlock { get; set; } + + public AddBlockResult Insert(BlockHeader header, BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.None) + => AddBlockResult.Added; + + public void BulkInsertHeader(IReadOnlyList headers, BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.None) { } + + public AddBlockResult Insert(Block block, BlockTreeInsertBlockOptions insertBlockOptions = BlockTreeInsertBlockOptions.None, + BlockTreeInsertHeaderOptions insertHeaderOptions = BlockTreeInsertHeaderOptions.None, WriteFlags bodiesWriteFlags = WriteFlags.None) + => AddBlockResult.Added; + + public void UpdateHeadBlock(Hash256 blockHash) { } + public void NewOldestBlock(long oldestBlock) { } + public AddBlockResult SuggestBlock(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) => AddBlockResult.Added; + public ValueTask SuggestBlockAsync(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) + => ValueTask.FromResult(AddBlockResult.Added); + public AddBlockResult SuggestHeader(BlockHeader header) => AddBlockResult.Added; + public bool IsKnownBlock(long number, Hash256 blockHash) => false; + public bool IsKnownBeaconBlock(long number, Hash256 blockHash) => false; + public bool WasProcessed(long number, Hash256 blockHash) => false; + public void UpdateMainChain(IReadOnlyList blocks, bool wereProcessed, bool forceHeadBlock = false) { } + public void MarkChainAsProcessed(IReadOnlyList blocks) { } + public Task Accept(IBlockTreeVisitor blockTreeVisitor, CancellationToken cancellationToken) => Task.CompletedTask; + public (BlockInfo? Info, ChainLevelInfo? Level) GetInfo(long number, Hash256 blockHash) => (null, null); + public ChainLevelInfo? FindLevel(long number) => null; + public BlockInfo FindCanonicalBlockInfo(long blockNumber) => null!; + public Hash256? FindHash(long blockNumber) => null; + public IOwnedReadOnlyList FindHeaders(Hash256 hash, int numberOfBlocks, int skip, bool reverse) + => new ArrayPoolList(0); + public void DeleteInvalidBlock(Block invalidBlock) { } + public void DeleteOldBlock(long blockNumber, Hash256 blockHash) { } + public void ForkChoiceUpdated(Hash256? finalizedBlockHash, Hash256? safeBlockBlockHash) { } + public int DeleteChainSlice(in long startNumber, long? endNumber = null, bool force = false) => 0; + public bool IsBetterThanHead(BlockHeader? header) => false; + public void UpdateBeaconMainChain(BlockInfo[]? blockInfos, long clearBeaconMainChainStartPoint) { } + public void RecalculateTreeLevels() { } +} diff --git a/src/Nethermind/Nethermind.TxPool.Test/TestChainHeadInfoProvider.cs b/src/Nethermind/Nethermind.TxPool.Test/TestChainHeadInfoProvider.cs new file mode 100644 index 000000000000..9cb26ba9b4ea --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool.Test/TestChainHeadInfoProvider.cs @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +#nullable enable + +using System; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm.State; +using Nethermind.Int256; + +namespace Nethermind.TxPool.Test; + +/// +/// A minimal IChainHeadInfoProvider implementation for testing that avoids NSubstitute's +/// static state issues when running tests in parallel. +/// +internal class TestChainHeadInfoProvider : IChainHeadInfoProvider +{ + public IChainHeadSpecProvider SpecProvider { get; set; } = null!; + public IReadOnlyStateProvider ReadOnlyStateProvider { get; set; } = null!; + public long HeadNumber { get; set; } + public long? BlockGasLimit { get; set; } = 30_000_000; + public UInt256 CurrentBaseFee { get; set; } + public UInt256 CurrentFeePerBlobGas { get; set; } + public ProofVersion CurrentProofVersion { get; set; } + public bool IsSyncing { get; set; } + public bool IsProcessingBlock { get; set; } + public event EventHandler? HeadChanged; + + public void RaiseHeadChanged(BlockReplacementEventArgs args) + { + HeadChanged?.Invoke(this, args); + } +} diff --git a/src/Nethermind/Nethermind.TxPool.Test/TransactionExtensionsTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TransactionExtensionsTests.cs index f8baecc5b94f..1028eddc5577 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TransactionExtensionsTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TransactionExtensionsTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.TxPool.Test { [TestFixture] + [Parallelizable(ParallelScope.All)] public class TransactionExtensionsTests { [Test] diff --git a/src/Nethermind/Nethermind.TxPool.Test/TransactionPoolInfoProviderTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TransactionPoolInfoProviderTests.cs index f9486f29e11c..722f7ba43e68 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TransactionPoolInfoProviderTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TransactionPoolInfoProviderTests.cs @@ -13,6 +13,8 @@ namespace Nethermind.TxPool.Test { [TestFixture] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class TxPoolInfoProviderTests { private Address _address; diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs index 82718dea4c71..17daab08c3ac 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.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; @@ -22,7 +22,6 @@ using Nethermind.Logging; using Nethermind.Network; using Nethermind.Network.P2P; -using Nethermind.Network.P2P.Subprotocols.Eth; using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; using Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages; using Nethermind.Network.P2P.Subprotocols.Eth.V67; @@ -40,6 +39,8 @@ namespace Nethermind.TxPool.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class TxBroadcasterTests { private ILogManager _logManager; @@ -49,7 +50,7 @@ public class TxBroadcasterTests private TxBroadcaster _broadcaster; private EthereumEcdsa _ethereumEcdsa; private TxPoolConfig _txPoolConfig; - private IChainHeadInfoProvider _headInfo; + private TestChainHeadInfoProvider _headInfo; [SetUp] public void Setup() @@ -60,18 +61,19 @@ public void Setup() _blockTree = Substitute.For(); _comparer = new TransactionComparerProvider(_specProvider, _blockTree).GetDefaultComparer(); _txPoolConfig = new TxPoolConfig(); - _headInfo = Substitute.For(); + _headInfo = new TestChainHeadInfoProvider(); } [TearDown] public void TearDown() => _broadcaster?.Dispose(); [Test] - public async Task should_not_broadcast_persisted_tx_to_peer_too_quickly() + public void should_not_broadcast_persisted_tx_to_peer_too_quickly() { + ManualTimestamper timestamper = new(DateTime.UtcNow); _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = 100 }; - _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager, timestamper: timestamper); + _headInfo.CurrentBaseFee = 0.GWei(); int addedTxsCount = TestItem.PrivateKeys.Length; Transaction[] transactions = new Transaction[addedTxsCount]; @@ -103,7 +105,8 @@ public async Task should_not_broadcast_persisted_tx_to_peer_too_quickly() peer.Received(1).SendNewTransactions(Arg.Any>(), true); - await Task.Delay(TimeSpan.FromMilliseconds(1001)); + // Advance time by 2 seconds (throttle is 1 second) + timestamper.Add(TimeSpan.FromSeconds(2)); peer.Received(1).SendNewTransactions(Arg.Any>(), true); @@ -121,7 +124,7 @@ public void should_pick_best_persistent_txs_to_broadcast([Values(1, 2, 99, 100, { _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = threshold }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _headInfo.CurrentBaseFee = 0.GWei(); int addedTxsCount = TestItem.PrivateKeys.Length; Transaction[] transactions = new Transaction[addedTxsCount]; @@ -204,7 +207,7 @@ public void should_skip_large_txs_when_picking_best_persistent_txs_to_broadcast( { _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = threshold }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _headInfo.CurrentBaseFee = 0.GWei(); // add 256 transactions, 10% of them is large int addedTxsCount = TestItem.PrivateKeys.Length; @@ -250,7 +253,7 @@ public void should_skip_blob_txs_when_picking_best_persistent_txs_to_broadcast([ { _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = threshold }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _headInfo.CurrentBaseFee = 0.GWei(); // add 256 transactions, 10% of them is blob type int addedTxsCount = TestItem.PrivateKeys.Length; @@ -298,7 +301,7 @@ public void should_not_pick_txs_with_GasPrice_lower_than_CurrentBaseFee([Values( _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); const int currentBaseFeeInGwei = 250; - _headInfo.CurrentBaseFee.Returns(currentBaseFeeInGwei.GWei()); + _headInfo.CurrentBaseFee = currentBaseFeeInGwei.GWei(); Block headBlock = Build.A.Block .WithNumber(MainnetSpecProvider.LondonBlockNumber) .WithBaseFeePerGas(currentBaseFeeInGwei.GWei()) @@ -342,7 +345,7 @@ public void should_not_pick_txs_with_GasPrice_lower_than_CurrentBaseFee([Values( [TestCase(150, true)] public void should_not_broadcast_tx_with_MaxFeePerGas_lower_than_70_percent_of_CurrentBaseFee(int maxFeePerGas, bool shouldBroadcast) { - _headInfo.CurrentBaseFee.Returns((UInt256)100); + _headInfo.CurrentBaseFee = (UInt256)100; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); @@ -372,7 +375,7 @@ public void should_not_pick_1559_txs_with_MaxFeePerGas_lower_than_CurrentBaseFee _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); const int currentBaseFeeInGwei = 250; - _headInfo.CurrentBaseFee.Returns(currentBaseFeeInGwei.GWei()); + _headInfo.CurrentBaseFee = currentBaseFeeInGwei.GWei(); Block headBlock = Build.A.Block .WithNumber(MainnetSpecProvider.LondonBlockNumber) .WithBaseFeePerGas(currentBaseFeeInGwei.GWei()) @@ -417,7 +420,7 @@ public void should_not_pick_blob_txs_with_MaxFeePerBlobGas_lower_than_CurrentFee _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); const int currentFeePerBlobGas = 250; - _headInfo.CurrentFeePerBlobGas.Returns(currentFeePerBlobGas.GWei()); + _headInfo.CurrentFeePerBlobGas = currentFeePerBlobGas.GWei(); // add 256 transactions with MaxFeePerBlobGas 0-255 int addedTxsCount = TestItem.PrivateKeys.Length; @@ -464,7 +467,7 @@ public void should_pick_tx_with_lowest_nonce_from_bucket() { _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = 5 }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _headInfo.CurrentBaseFee = 0.GWei(); const int addedTxsCount = 5; Transaction[] transactions = new Transaction[addedTxsCount]; @@ -509,7 +512,7 @@ public void should_broadcast_local_tx_immediately_after_receiving_it() public void should_broadcast_full_local_tx_immediately_after_receiving_it() { _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _headInfo.CurrentBaseFee = 0.GWei(); ISession session = Substitute.For(); session.Node.Returns(new Node(TestItem.PublicKeyA, TestItem.IPEndPointA)); @@ -519,10 +522,11 @@ public void should_broadcast_full_local_tx_immediately_after_receiving_it() Substitute.For(), RunImmediatelyScheduler.Instance, Substitute.For(), - Substitute.For(), Substitute.For(), Substitute.For(), - Substitute.For()); + Substitute.For(), + Substitute.For(), + Substitute.For()); _broadcaster.AddPeer(eth68Handler); Transaction localTx = Build.A.Transaction @@ -547,7 +551,6 @@ public void should_broadcast_hash_of_blob_local_tx_to_eth68_peers_immediately_af Substitute.For(), RunImmediatelyScheduler.Instance, Substitute.For(), - Substitute.For(), Substitute.For(), Substitute.For(), Substitute.For()); @@ -560,10 +563,11 @@ public void should_broadcast_hash_of_blob_local_tx_to_eth68_peers_immediately_af Substitute.For(), RunImmediatelyScheduler.Instance, Substitute.For(), - Substitute.For(), Substitute.For(), Substitute.For(), - Substitute.For()); + Substitute.For(), + Substitute.For(), + Substitute.For()); Transaction localTx = Build.A.Transaction .WithShardBlobTxTypeAndFields() @@ -601,10 +605,11 @@ public void should_broadcast_full_local_tx_up_to_max_size_and_only_announce_if_l Substitute.For(), RunImmediatelyScheduler.Instance, Substitute.For(), - Substitute.For(), Substitute.For(), Substitute.For(), - Substitute.For()); + Substitute.For(), + Substitute.For(), + Substitute.For()); Transaction localTx = Build.A.Transaction .WithData(new byte[txSize]) @@ -645,7 +650,7 @@ public void should_check_tx_policy_for_broadcast(bool canGossipTransactions, boo { ITxGossipPolicy txGossipPolicy = Substitute.For(); _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager, txGossipPolicy); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _headInfo.CurrentBaseFee = 0.GWei(); ISession session = Substitute.For(); session.Node.Returns(new Node(TestItem.PublicKeyA, TestItem.IPEndPointA)); @@ -655,10 +660,11 @@ public void should_check_tx_policy_for_broadcast(bool canGossipTransactions, boo Substitute.For(), RunImmediatelyScheduler.Instance, Substitute.For(), - Substitute.For(), Substitute.For(), Substitute.For(), - Substitute.For()); + Substitute.For(), + Substitute.For(), + Substitute.For()); _broadcaster.AddPeer(eth68Handler); Transaction localTx = Build.A.Transaction @@ -706,7 +712,7 @@ public void should_rebroadcast_all_persistent_transactions_if_PeerNotificationTh [TestCase(10000, 7000)] public void should_calculate_baseFeeThreshold_correctly(int baseFee, int expectedThreshold) { - _headInfo.CurrentBaseFee.Returns((UInt256)baseFee); + _headInfo.CurrentBaseFee = (UInt256)baseFee; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); _broadcaster.CalculateBaseFeeThreshold().Should().Be((UInt256)expectedThreshold); } @@ -715,7 +721,7 @@ public void should_calculate_baseFeeThreshold_correctly(int baseFee, int expecte public void calculation_of_baseFeeThreshold_should_handle_overflow_correctly([Values(0, 70, 100, 101, 500)] int threshold, [Values(2, 3, 4, 5, 6, 7, 8, 9, 10, 11)] int divisor) { UInt256.Divide(UInt256.MaxValue, (UInt256)divisor, out UInt256 baseFee); - _headInfo.CurrentBaseFee.Returns(baseFee); + _headInfo.CurrentBaseFee = baseFee; _txPoolConfig = new TxPoolConfig() { MinBaseFeeThreshold = threshold }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); @@ -729,6 +735,31 @@ public void calculation_of_baseFeeThreshold_should_handle_overflow_correctly([Va : baseFeeThreshold); } + [Test] + public void can_correctly_broadcast_light_transactions_without_wrappers([Values] ProofVersion proofVersion, [Values] bool versionMatches) + { + // Arrange + IChainHeadInfoProvider mockChainHeadInfoProvider = Substitute.For(); + mockChainHeadInfoProvider.CurrentProofVersion.Returns(proofVersion); + IReleaseSpec spec = Substitute.For(); + spec.IsEip7594Enabled.Returns(versionMatches ? proofVersion == ProofVersion.V1 : proofVersion == ProofVersion.V0); + + SpecDrivenTxGossipPolicy gossipPolicy = new(mockChainHeadInfoProvider); + + Transaction blobTransaction = Build.A.Transaction + .WithShardBlobTxTypeAndFields(spec: spec) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA) + .TestObject; + + LightTransaction lightTransaction = new(blobTransaction); + + // Act + bool result = gossipPolicy.ShouldGossipTransaction(lightTransaction); + + // Assert + result.Should().Be(versionMatches, "LightTransaction from blob transaction should be gossiped when proof version matches."); + } + private (IList expectedTxs, IList expectedHashes) GetTxsAndHashesExpectedToBroadcast(Transaction[] transactions, int expectedCountTotal) { List expectedTxs = new(); diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs index 5d1e4ff7aaaf..ce0d4cf783f7 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using CkzgLib; using FluentAssertions; using Nethermind.Blockchain; @@ -26,6 +27,7 @@ using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; +using Nethermind.Blockchain.Spec; namespace Nethermind.TxPool.Test { @@ -242,11 +244,22 @@ public void should_not_throw_when_asking_for_non_existing_tx() blobTxFromDb.Should().BeNull(); } - [TestCase(999_999_999, false)] - [TestCase(1_000_000_000, true)] - public void should_not_allow_to_add_blob_tx_with_MaxPriorityFeePerGas_lower_than_1GWei(int maxPriorityFeePerGas, bool expectedResult) + [TestCase(1, null, true)] + [TestCase(999_999_999, null, true)] + [TestCase(1_000_000_000, null, true)] + [TestCase(1_000_000_001, null, true)] + [TestCase(1, 0ul, true)] + [TestCase(1_000_000_000, 0ul, true)] + [TestCase(1, 10ul, false)] + public void should_allow_to_add_blob_tx_with_MaxPriorityFeePerGas_lower_than_1GWei(int maxPriorityFeePerGas, ulong? configuredMinPriorityFee, bool expectedResult) { TxPoolConfig txPoolConfig = new() { BlobsSupport = BlobsSupportMode.InMemory, Size = 10 }; + + if (configuredMinPriorityFee is not null) + { + txPoolConfig.MinBlobTxPriorityFee = configuredMinPriorityFee.Value; + } + _txPool = CreatePool(txPoolConfig, GetCancunSpecProvider()); EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); @@ -261,6 +274,39 @@ public void should_not_allow_to_add_blob_tx_with_MaxPriorityFeePerGas_lower_than : AcceptTxResult.FeeTooLow); } + [Test] + public void should_not_allow_to_add_blob_tx_with_MaxFeePerBlobGas_lower_than_CurrentFeePerBlobGas([Values(true, false)] bool isMaxFeePerBlobGasHighEnough, [Values(true, false)] bool isRequirementEnabled) + { + ISpecProvider specProvider = GetCancunSpecProvider(); + ChainHeadInfoProvider chainHeadInfoProvider = new(new ChainHeadSpecProvider(specProvider, _blockTree), _blockTree, _stateProvider); + + TxPoolConfig txPoolConfig = new() + { + BlobsSupport = BlobsSupportMode.InMemory, + Size = 10, + CurrentBlobBaseFeeRequired = isRequirementEnabled + }; + + UInt256 currentFeePerBlobGas = 100; + chainHeadInfoProvider.CurrentFeePerBlobGas = currentFeePerBlobGas; + + _txPool = CreatePool(config: txPoolConfig, + specProvider: specProvider, + chainHeadInfoProvider: chainHeadInfoProvider); + EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); + + Transaction tx = Build.A.Transaction + .WithShardBlobTxTypeAndFields() + .WithMaxFeePerBlobGas(isMaxFeePerBlobGasHighEnough ? currentFeePerBlobGas : currentFeePerBlobGas - 1) + .WithMaxFeePerGas(1.GWei()) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; + + Assert.That(_txPool.SubmitTx(tx, TxHandlingOptions.None), + Is.EqualTo(isRequirementEnabled && !isMaxFeePerBlobGasHighEnough + ? AcceptTxResult.FeeTooLow + : AcceptTxResult.Accepted)); + } + [Test] public void should_not_add_nonce_gap_blob_tx_even_to_not_full_TxPool([Values(true, false)] bool isBlob) { @@ -392,7 +438,8 @@ public void should_dump_GasBottleneck_of_blob_tx_to_zero_if_MaxFeePerBlobGas_is_ TxPoolConfig txPoolConfig = new() { BlobsSupport = isPersistentStorage ? BlobsSupportMode.Storage : BlobsSupportMode.InMemory, - Size = 10 + Size = 10, + CurrentBlobBaseFeeRequired = false }; _txPool = CreatePool(txPoolConfig, GetCancunSpecProvider()); EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); @@ -561,7 +608,8 @@ Transaction GetTx(bool isBlob, UInt256 nonce) _txPool.GetPendingTransactionsCount().Should().Be(firstIsBlob ? 0 : 1); _txPool.GetPendingBlobTransactionsCount().Should().Be(firstIsBlob ? 1 : 0); _stateProvider.IncrementNonce(TestItem.AddressA); - await RaiseBlockAddedToMainAndWaitForTransactions(1); + Block block = Build.A.Block.WithNumber(1).TestObject; + await RaiseBlockAddedToMainAndWaitForNewHead(block); _txPool.GetPendingTransactionsCount().Should().Be(0); _txPool.GetPendingBlobTransactionsCount().Should().Be(0); @@ -905,7 +953,7 @@ public void should_convert_blob_proofs_to_cell_proofs_if_enabled([Values(true, f EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); // update head and set correct current proof version - _blockTree.BlockAddedToMain += Raise.EventWith(new BlockReplacementEventArgs(Build.A.Block.TestObject)); + _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.TestObject)); Transaction blobTxAdded = Build.A.Transaction .WithShardBlobTxTypeAndFields() @@ -939,7 +987,7 @@ public void should_convert_blob_proofs_to_cell_proofs_if_enabled([Values(true, f public async Task should_evict_based_on_proof_version_and_fork(BlobsSupportMode poolMode, TestAction[] testActions) { Block head = _blockTree.Head; - _blockTree.FindBestSuggestedHeader().Returns(head.Header); + _blockTree.BestSuggestedHeader = head.Header; (ChainSpecBasedSpecProvider provider, _) = TestSpecHelper.LoadChainSpec(new ChainSpecJson { @@ -1007,12 +1055,12 @@ TestCaseData MakeTestCase(string testName, int finalCount, BlobsSupportMode mode } } - private Task AddEmptyBlock() + private async Task AddEmptyBlock() { BlockHeader bh = new(_blockTree.Head.Hash, Keccak.EmptyTreeHash, TestItem.AddressA, 0, _blockTree.Head.Number + 1, _blockTree.Head.GasLimit, _blockTree.Head.Timestamp + 1, []); - _blockTree.FindBestSuggestedHeader().Returns(bh); - _blockTree.BlockAddedToMain += Raise.EventWith(new BlockReplacementEventArgs(new Block(bh, new BlockBody([], [])), _blockTree.Head)); - return Task.Delay(300); + _blockTree.BestSuggestedHeader = bh; + Block block = new Block(bh, new BlockBody([], [])); + await RaiseBlockAddedToMainAndWaitForNewHead(block, _blockTree.Head); } private Transaction CreateBlobTx(PrivateKey sender, UInt256 nonce = default, int blobCount = 1, IReleaseSpec releaseSpec = default) @@ -1046,7 +1094,7 @@ public async Task should_evict_txs_with_too_many_blobs_per_tx_after_fork() }; Block head = _blockTree.Head; - _blockTree.FindBestSuggestedHeader().Returns(head.Header); + _blockTree.BestSuggestedHeader = head.Header; _txPool = CreatePool(specProvider: provider); EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); @@ -1080,7 +1128,7 @@ public async Task should_evict_txs_with_too_many_blobs_per_block_after_fork() }; Block head = _blockTree.Head; - _blockTree.FindBestSuggestedHeader().Returns(head.Header); + _blockTree.BestSuggestedHeader = head.Header; _txPool = CreatePool(specProvider: provider); EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); @@ -1109,5 +1157,139 @@ public void max_blobs_per_tx_should_not_exceed_max_blobs_per_block() Assert.That(maxBlobsPerTx, Is.EqualTo(regularMaxBlobCount)); } + + [Test] + public void should_batch_return_blobs_and_proofs_v1_from_persistent_storage() + { + // BlobCacheSize = 1 forces cache eviction after the first insert, + // so the second tx must be fetched via TryGetMany (Phase 2 DB path). + TxPoolConfig txPoolConfig = new() + { + BlobsSupport = BlobsSupportMode.Storage, + BlobCacheSize = 1, + Size = 10 + }; + BlobTxStorage blobTxStorage = new(); + _txPool = CreatePool(txPoolConfig, GetOsakaSpecProvider(), txStorage: blobTxStorage); + EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); + EnsureSenderBalance(TestItem.AddressB, UInt256.MaxValue); + + Transaction tx1 = Build.A.Transaction + .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce(UInt256.Zero) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; + + Transaction tx2 = Build.A.Transaction + .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce(UInt256.Zero) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyB).TestObject; + + _txPool.SubmitTx(tx1, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); + _txPool.SubmitTx(tx2, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); + + // tx1 was evicted from cache (size=1) when tx2 was inserted, + // so at least one must come from DB via TryGetMany + byte[][] requestedHashes = [tx1.BlobVersionedHashes![0]!, tx2.BlobVersionedHashes![0]!]; + byte[][] blobs = new byte[2][]; + ReadOnlyMemory[] proofs = new ReadOnlyMemory[2]; + + int found = _txPool.TryGetBlobsAndProofsV1(requestedHashes, blobs, proofs); + + found.Should().Be(2); + blobs[0].Should().NotBeNull(); + blobs[1].Should().NotBeNull(); + proofs[0].Length.Should().Be(Ckzg.CellsPerExtBlob); + proofs[1].Length.Should().Be(Ckzg.CellsPerExtBlob); + } + + [Test] + public void should_batch_return_partial_blobs_when_some_missing() + { + TxPoolConfig txPoolConfig = new() + { + BlobsSupport = BlobsSupportMode.Storage, + BlobCacheSize = 1, + Size = 10 + }; + BlobTxStorage blobTxStorage = new(); + _txPool = CreatePool(txPoolConfig, GetOsakaSpecProvider(), txStorage: blobTxStorage); + EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); + + Transaction tx1 = Build.A.Transaction + .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce(UInt256.Zero) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; + + _txPool.SubmitTx(tx1, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); + + byte[] fakeBlobHash = new byte[32]; + fakeBlobHash[0] = 0x01; // versioned hash prefix + byte[][] requestedHashes = [tx1.BlobVersionedHashes![0]!, fakeBlobHash]; + byte[][] blobs = new byte[2][]; + ReadOnlyMemory[] proofs = new ReadOnlyMemory[2]; + + int found = _txPool.TryGetBlobsAndProofsV1(requestedHashes, blobs, proofs); + + found.Should().Be(1); + blobs[0].Should().NotBeNull(); + blobs[1].Should().BeNull(); + } + + [Test] + public void should_batch_return_blobs_from_cache_and_db() + { + // BlobCacheSize = 1: after inserting tx1 and tx2, only tx2 remains in cache. + // Accessing tx1 via single lookup re-populates it, evicting tx2. + // Batch lookup then exercises: tx1 from cache, tx2 from DB (TryGetMany). + TxPoolConfig txPoolConfig = new() + { + BlobsSupport = BlobsSupportMode.Storage, + BlobCacheSize = 1, + Size = 10 + }; + BlobTxStorage blobTxStorage = new(); + _txPool = CreatePool(txPoolConfig, GetOsakaSpecProvider(), txStorage: blobTxStorage); + EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); + EnsureSenderBalance(TestItem.AddressB, UInt256.MaxValue); + + Transaction tx1 = Build.A.Transaction + .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce(UInt256.Zero) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; + + Transaction tx2 = Build.A.Transaction + .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce(UInt256.Zero) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyB).TestObject; + + _txPool.SubmitTx(tx1, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); + _txPool.SubmitTx(tx2, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); + + // Access tx1 via single lookup — this re-populates tx1 in cache, evicting tx2 + _txPool.TryGetBlobAndProofV1(tx1.BlobVersionedHashes![0]!, out byte[] _, out byte[][] _).Should().BeTrue(); + + // Now batch lookup: tx1 from cache (just accessed), tx2 from DB + byte[][] requestedHashes = [tx1.BlobVersionedHashes![0]!, tx2.BlobVersionedHashes![0]!]; + byte[][] blobs = new byte[2][]; + ReadOnlyMemory[] proofs = new ReadOnlyMemory[2]; + + int found = _txPool.TryGetBlobsAndProofsV1(requestedHashes, blobs, proofs); + + found.Should().Be(2); + blobs[0].Should().NotBeNull(); + blobs[1].Should().NotBeNull(); + proofs[0].Length.Should().Be(Ckzg.CellsPerExtBlob); + proofs[1].Length.Should().Be(Ckzg.CellsPerExtBlob); + } } } diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs index 823d402adea2..537752efcb27 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs @@ -36,16 +36,25 @@ namespace Nethermind.TxPool.Test { [TestFixture] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public partial class TxPoolTests { + private const int Timeout = 10000; private ILogManager _logManager; private IEthereumEcdsa _ethereumEcdsa; private ISpecProvider _specProvider; private TxPool _txPool; private TestReadOnlyStateProvider _stateProvider; - private IBlockTree _blockTree; + private TestBlockTree _blockTree; - private readonly int _txGasLimit = 1_000_000; + private const int TxGasLimit = 1_000_000; + + [OneTimeSetUp] + public static void OneTimeSetup() + { + KzgPolynomialCommitments.InitializeAsync().Wait(); + } [SetUp] public void Setup() @@ -54,19 +63,17 @@ public void Setup() _specProvider = MainnetSpecProvider.Instance; _ethereumEcdsa = new EthereumEcdsa(_specProvider.ChainId); _stateProvider = new TestReadOnlyStateProvider(); - _blockTree = Substitute.For(); - Block block = Build.A.Block.WithNumber(10000000 - 1).TestObject; - _blockTree.Head.Returns(block); - _blockTree.FindBestSuggestedHeader().Returns(Build.A.BlockHeader.WithNumber(10000000).TestObject); - - KzgPolynomialCommitments.InitializeAsync().Wait(); + _blockTree = new TestBlockTree(); + Block block = Build.A.Block.WithNumber(10000000 - 1).WithBaseFeePerGas(0).TestObject; + _blockTree.Head = block; + _blockTree.BestSuggestedHeader = Build.A.BlockHeader.WithNumber(10000000).WithBaseFee(0).TestObject; } [Test] public void should_add_peers() { _txPool = CreatePool(); - var peers = GetPeers(); + IDictionary peers = GetPeers(); foreach ((ITxPoolPeer peer, _) in peers) { @@ -78,7 +85,7 @@ public void should_add_peers() public void should_delete_peers() { _txPool = CreatePool(); - var peers = GetPeers(); + IDictionary peers = GetPeers(); foreach ((ITxPoolPeer peer, _) in peers) { @@ -152,7 +159,7 @@ public void should_add_valid_transactions_recovering_its_address() { _txPool = CreatePool(); Transaction tx = Build.A.Transaction - .WithGasLimit(_txGasLimit) + .WithGasLimit(TxGasLimit) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); tx.SenderAddress = null; @@ -166,7 +173,7 @@ public void should_reject_transactions_from_contract_address() { _txPool = CreatePool(); Transaction tx = Build.A.Transaction - .WithGasLimit(_txGasLimit) + .WithGasLimit(TxGasLimit) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); _stateProvider.InsertCode(TestItem.AddressA, "A"u8.ToArray(), _specProvider.GetSpec((ForkActivation)1)); @@ -192,7 +199,7 @@ public void should_accept_1559_transactions_only_when_eip1559_enabled([Values(fa .WithMaxPriorityFeePerGas(5.GWei()) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); - _blockTree.BlockAddedToMain += Raise.EventWith(_blockTree, new BlockReplacementEventArgs(Build.A.Block.WithGasLimit(10000000).TestObject)); + _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.WithGasLimit(10000000).TestObject)); AcceptTxResult result = txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); txPool.GetPendingTransactionsCount().Should().Be(eip1559Enabled ? 1 : 0); result.Should().Be(eip1559Enabled ? AcceptTxResult.Accepted : AcceptTxResult.Invalid); @@ -215,7 +222,7 @@ public void should_not_ignore_insufficient_funds_for_eip1559_transactions() var headProcessed = new ManualResetEventSlim(false); txPool.TxPoolHeadChanged += (s, a) => headProcessed.Set(); - _blockTree.BlockAddedToMain += Raise.EventWith(_blockTree, new BlockReplacementEventArgs(Build.A.Block.WithGasLimit(10000000).TestObject)); + _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.WithGasLimit(10000000).TestObject)); headProcessed.Wait(); result = txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); @@ -332,7 +339,7 @@ public void should_ignore_overflow_transactions() public void should_ignore_overflow_transactions_gas_premium_and_fee_cap() { ISpecProvider specProvider = GetLondonSpecProvider(); - var txPool = CreatePool(null, specProvider); + TxPool txPool = CreatePool(null, specProvider); Transaction tx = Build.A.Transaction.WithGasPrice(UInt256.MaxValue / Transaction.BaseTxGasCost) .WithGasLimit(Transaction.BaseTxGasCost) .WithValue(Transaction.BaseTxGasCost) @@ -366,7 +373,7 @@ public void should_reject_tx_if_max_size_is_exceeded([Values(true, false)] bool Transaction tx = Build.A.Transaction.SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); - var txPoolConfig = new TxPoolConfig() { MaxTxSize = tx.GetLength() - (sizeExceeded ? 1 : 0) }; + TxPoolConfig txPoolConfig = new() { MaxTxSize = tx.GetLength() - (sizeExceeded ? 1 : 0) }; _txPool = CreatePool(txPoolConfig); AcceptTxResult result = _txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); @@ -399,7 +406,7 @@ public void should_ignore_tx_gas_limit_exceeded() { _txPool = CreatePool(); Transaction tx = Build.A.Transaction - .WithGasLimit(_txGasLimit + 1) + .WithGasLimit(TxGasLimit + 1) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); AcceptTxResult result = _txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); @@ -558,7 +565,7 @@ public void should_not_add_tx_if_already_pending_lower_nonces_are_exhausting_bal { const int gasPrice = 10; const int value = 1; - int oneTxPrice = _txGasLimit * gasPrice + value; + int oneTxPrice = TxGasLimit * gasPrice + value; _txPool = CreatePool(); Transaction[] transactions = new Transaction[10]; @@ -570,7 +577,7 @@ public void should_not_add_tx_if_already_pending_lower_nonces_are_exhausting_bal .WithSenderAddress(TestItem.AddressA) .WithNonce((UInt256)i) .WithGasPrice((UInt256)gasPrice) - .WithGasLimit(_txGasLimit) + .WithGasLimit(TxGasLimit) .WithValue(value) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; }); @@ -591,7 +598,7 @@ public void should_not_count_txs_with_stale_nonces_when_calculating_cumulative_c { const int gasPrice = 10; const int value = 1; - int oneTxPrice = _txGasLimit * gasPrice + value; + int oneTxPrice = TxGasLimit * gasPrice + value; _txPool = CreatePool(); EnsureSenderBalance(TestItem.AddressA, (UInt256)(oneTxPrice * numberOfTxsPossibleToExecuteBeforeGasExhaustion)); @@ -604,7 +611,7 @@ public void should_not_count_txs_with_stale_nonces_when_calculating_cumulative_c .WithSenderAddress(TestItem.AddressA) .WithNonce((UInt256)i) .WithGasPrice((UInt256)gasPrice) - .WithGasLimit(_txGasLimit) + .WithGasLimit(TxGasLimit) .WithValue(value) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; }); @@ -626,12 +633,12 @@ 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; const int value = 1; - int oneTxPrice = _txGasLimit * gasPrice + value; + int oneTxPrice = TxGasLimit * gasPrice + value; _txPool = CreatePool(); Transaction[] transactions = new Transaction[count]; @@ -643,7 +650,7 @@ public void should_add_tx_if_cost_of_executing_all_txs_in_bucket_exceeds_balance .WithSenderAddress(TestItem.AddressA) .WithNonce((UInt256)i) .WithGasPrice((UInt256)gasPrice) - .WithGasLimit(_txGasLimit) + .WithGasLimit(TxGasLimit) .WithValue(value) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; }); @@ -777,7 +784,7 @@ public void should_remove_txHash_from_hashCache_when_tx_removed_because_of_txPoo var headProcessed = new ManualResetEventSlim(false); _txPool.TxPoolHeadChanged += (s, a) => headProcessed.Set(); - _blockTree.BlockAddedToMain += Raise.EventWith(new BlockReplacementEventArgs(Build.A.Block.TestObject)); + _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.TestObject)); headProcessed.Wait(); _txPool.IsKnown(higherPriorityTx.Hash).Should().BeTrue(); @@ -888,10 +895,11 @@ public void should_remove_stale_txs_from_persistent_transactions(int numberOfTxs BlockReplacementEventArgs blockReplacementEventArgs = new(block, null); ManualResetEvent manualResetEvent = new(false); - _txPool.RemoveTransaction(Arg.Do(t => manualResetEvent.Set())); - _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockReplacementEventArgs); - manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(200)); + _txPool.TxPoolHeadChanged += (o, e) => manualResetEvent.Set(); + _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); + bool signaled = manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(Timeout)); + signaled.Should().BeTrue("TxPoolHeadChanged event should have been raised"); // transactions[nonceIncludedInBlock] was included in the block and should be removed, as well as all lower nonces. _txPool.GetOwnPendingTransactions().Length.Should().Be(numberOfTxs - nonceIncludedInBlock - 1); } @@ -926,10 +934,11 @@ public void broadcaster_should_work_well_when_there_are_no_txs_in_persistent_txs BlockReplacementEventArgs blockReplacementEventArgs = new(block, null); ManualResetEvent manualResetEvent = new(false); - _txPool.RemoveTransaction(Arg.Do(t => manualResetEvent.Set())); - _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockReplacementEventArgs); - manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(200)); + _txPool.TxPoolHeadChanged += (o, e) => manualResetEvent.Set(); + _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); + bool signaled = manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(Timeout)); + signaled.Should().BeTrue("TxPoolHeadChanged event should have been raised"); _txPool.GetPendingTransactionsCount().Should().Be(1); _txPool.GetOwnPendingTransactions().Length.Should().Be(1); } @@ -960,7 +969,7 @@ public async Task should_remove_transactions_concurrently() public void should_add_transactions_concurrently() { int size = 3; - TxPoolConfig config = new() { GasLimit = _txGasLimit, Size = size }; + TxPoolConfig config = new() { GasLimit = TxGasLimit, Size = size }; _txPool = CreatePool(config); foreach (PrivateKey privateKey in TestItem.PrivateKeys) @@ -1007,10 +1016,11 @@ public void should_remove_tx_from_txPool_when_included_in_block(bool sameTransac BlockReplacementEventArgs blockReplacementEventArgs = new(block, null); ManualResetEvent manualResetEvent = new(false); - _txPool.RemoveTransaction(Arg.Do(t => manualResetEvent.Set())); - _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockReplacementEventArgs); - manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(200)); + _txPool.TxPoolHeadChanged += (o, e) => manualResetEvent.Set(); + _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); + bool signaled = manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(Timeout)); + signaled.Should().BeTrue("TxPoolHeadChanged event should have been raised"); _txPool.GetPendingTransactionsCount().Should().Be(0); } @@ -1030,10 +1040,11 @@ public void should_not_remove_txHash_from_hashCache_when_tx_removed_because_of_i BlockReplacementEventArgs blockReplacementEventArgs = new(block, null); ManualResetEvent manualResetEvent = new(false); - _txPool.RemoveTransaction(Arg.Do(t => manualResetEvent.Set())); - _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockReplacementEventArgs); - manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(200)); + _txPool.TxPoolHeadChanged += (o, e) => manualResetEvent.Set(); + _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); + bool signaled = manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(Timeout)); + signaled.Should().BeTrue("TxPoolHeadChanged event should have been raised"); foreach (Transaction transaction in transactions) { _txPool.IsKnown(transaction.Hash).Should().BeTrue(); @@ -1130,15 +1141,14 @@ public void should_notify_added_peer_of_own_tx_when_we_are_synced([Values(0, 1)] } [Test] - public async Task should_notify_peer_only_once() + public void should_notify_peer_only_once() { _txPool = CreatePool(); ITxPoolPeer txPoolPeer = Substitute.For(); txPoolPeer.Id.Returns(TestItem.PublicKeyA); _txPool.AddPeer(txPoolPeer); _ = AddTransactionToPool(); - await Task.Delay(500); - txPoolPeer.Received(1).SendNewTransaction(Arg.Any()); + Assert.That(() => txPoolPeer.ReceivedCallsMatching(p => p.SendNewTransaction(Arg.Any())), Is.True.After(500, 10)); txPoolPeer.DidNotReceive().SendNewTransactions(Arg.Any>(), false); } @@ -1169,9 +1179,9 @@ public void should_accept_access_list_transactions_only_when_eip2930_enabled([Va { if (!eip2930Enabled) { - _blockTree.FindBestSuggestedHeader().Returns(Build.A.BlockHeader.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 1).TestObject); + _blockTree.BestSuggestedHeader = Build.A.BlockHeader.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 1).TestObject; Block block = Build.A.Block.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 2).TestObject; - _blockTree.Head.Returns(block); + _blockTree.Head = block; } _txPool = CreatePool(null, new TestSpecProvider(eip2930Enabled ? Berlin.Instance : Istanbul.Instance)); @@ -1190,9 +1200,9 @@ public void should_accept_only_when_synced([Values(false, true)] bool isSynced, { if (!isSynced) { - _blockTree.FindBestSuggestedHeader().Returns(Build.A.BlockHeader.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 1).TestObject); + _blockTree.BestSuggestedHeader = Build.A.BlockHeader.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 1).TestObject; Block block = Build.A.Block.WithNumber(1).TestObject; - _blockTree.Head.Returns(block); + _blockTree.Head = block; } _txPool = CreatePool(null, new TestSpecProvider(Berlin.Instance)); @@ -1489,7 +1499,9 @@ public void should_increase_nonce_when_transaction_not_included_in_txPool_but_br } [Test] - public async Task should_include_transaction_after_removal() + [Retry(3)] + [NonParallelizable] + public void should_include_transaction_after_removal() { ISpecProvider specProvider = GetLondonSpecProvider(); _txPool = CreatePool(new TxPoolConfig { Size = 2 }, specProvider); @@ -1524,15 +1536,10 @@ public async Task should_include_transaction_after_removal() _txPool.SubmitTx(expensiveTx2, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); // Rise new block event to cleanup cash and remove one expensive tx - _blockTree.BlockAddedToMain += - Raise.Event>(this, - new BlockReplacementEventArgs(Build.A.Block.WithTransactions(expensiveTx1).TestObject)); - - // Wait four event processing - await Task.Delay(100); + _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.WithTransactions(expensiveTx1).TestObject)); - // Send txA again => should be Accepted - _txPool.SubmitTx(txA, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); + // Wait for event processing and send txA again => should be Accepted + Assert.That(() => _txPool.SubmitTx(txA, TxHandlingOptions.None), Is.EqualTo(AcceptTxResult.Accepted).After(Timeout, 10)); } [TestCase(true, 1, 1, true)] @@ -2249,7 +2256,7 @@ private TxPool CreatePool( _ethereumEcdsa, txStorage, _headInfo, - config ?? new TxPoolConfig() { GasLimit = _txGasLimit }, + config ?? new TxPoolConfig() { GasLimit = TxGasLimit }, new TxValidator(_specProvider.ChainId), _logManager, transactionComparerProvider.GetDefaultComparer(), @@ -2267,33 +2274,13 @@ private ITxPoolPeer GetPeer(PublicKey publicKey) return peer; } - private static ISpecProvider GetLondonSpecProvider() - { - var specProvider = Substitute.For(); - specProvider.GetSpec(Arg.Any()).Returns(London.Instance); - return specProvider; - } + private static ISpecProvider GetLondonSpecProvider() => new TestSpecProvider(London.Instance); - private static ISpecProvider GetCancunSpecProvider() - { - var specProvider = Substitute.For(); - specProvider.GetSpec(Arg.Any()).Returns(Cancun.Instance); - return specProvider; - } + private static ISpecProvider GetCancunSpecProvider() => new TestSpecProvider(Cancun.Instance); - private static ISpecProvider GetPragueSpecProvider() - { - var specProvider = Substitute.For(); - specProvider.GetSpec(Arg.Any()).Returns(Prague.Instance); - return specProvider; - } + private static ISpecProvider GetPragueSpecProvider() => new TestSpecProvider(Prague.Instance); - private static ISpecProvider GetOsakaSpecProvider() - { - var specProvider = Substitute.For(); - specProvider.GetSpec(Arg.Any()).Returns(Osaka.Instance); - return specProvider; - } + private static ISpecProvider GetOsakaSpecProvider() => new TestSpecProvider(Osaka.Instance); private Transaction[] AddTransactionsToPool(bool sameTransactionSenderPerPeer = true, bool sameNoncePerPeer = false, int transactionsPerPeer = 10) { @@ -2401,10 +2388,10 @@ private async Task RaiseBlockAddedToMainAndWaitForTransactions(int txCount, Bloc SemaphoreSlim semaphoreSlim = new(0, txCount); _txPool.NewPending += (o, e) => semaphoreSlim.Release(); - _blockTree.BlockAddedToMain += Raise.EventWith(blockReplacementEventArgs); + _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); for (int i = 0; i < txCount; i++) { - await semaphoreSlim.WaitAsync(10); + await semaphoreSlim.WaitAsync(1000); } } @@ -2421,7 +2408,7 @@ private async Task RaiseBlockAddedToMainAndWaitForNewHead(Block block, Block pre e => e.Number == block.Number ); - _blockTree.BlockAddedToMain += Raise.EventWith(blockReplacementEventArgs); + _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); await waitTask; } diff --git a/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs b/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs index 3517b76f9e79..97c80befa18c 100644 --- a/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs +++ b/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs @@ -122,6 +122,7 @@ public AcceptTxResult(int id, string code, string? message = null) } public static implicit operator bool(AcceptTxResult result) => result.Id == Accepted.Id; + public static implicit operator AcceptTxResult(bool result) => result ? Accepted : Invalid; public AcceptTxResult WithMessage(string message) => new(Id, Code, message); public static bool operator ==(AcceptTxResult a, AcceptTxResult b) => a.Equals(b); public static bool operator !=(AcceptTxResult a, AcceptTxResult b) => !(a == b); diff --git a/src/Nethermind/Nethermind.TxPool/BlobTxStorage.cs b/src/Nethermind/Nethermind.TxPool/BlobTxStorage.cs index 80a8d6f097ee..d76d0f620a0e 100644 --- a/src/Nethermind/Nethermind.TxPool/BlobTxStorage.cs +++ b/src/Nethermind/Nethermind.TxPool/BlobTxStorage.cs @@ -3,9 +3,11 @@ using System; using System.Buffers.Binary; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Db; @@ -16,7 +18,9 @@ namespace Nethermind.TxPool; public class BlobTxStorage : IBlobTxStorage { + private const int MaxPooledKeys = 128; private static readonly TxDecoder _txDecoder = TxDecoder.Instance; + private readonly ConcurrentQueue _keyPool = new(); private readonly IDb _fullBlobTxsDb; private readonly IDb _lightBlobTxsDb; private readonly IDb _processedBlobTxsDb; @@ -44,6 +48,35 @@ public bool TryGet(in ValueHash256 hash, Address sender, in UInt256 timestamp, [ return TryDecodeFullTx(txBytes, sender, out transaction); } + public int TryGetMany(TxLookupKey[] keys, int count, Transaction?[] results) + { + if (count == 0) return 0; + + // Outer array must be exact-size for the IDb indexer (uses keys.Length). + // Inner byte[64] keys are pooled via ConcurrentQueue to avoid per-call allocations. + byte[][] dbKeys = new byte[count][]; + for (int i = 0; i < dbKeys.Length; i++) + { + byte[] key = RentKey(); + GetHashPrefixedByTimestamp(keys[i].Timestamp, keys[i].Hash, key); + dbKeys[i] = key; + } + + KeyValuePair[] dbResults = _fullBlobTxsDb[dbKeys]; + + int found = 0; + for (int i = 0; i < count; i++) + { + if (TryDecodeFullTx(dbResults[i].Value, keys[i].Sender, out results[i])) + found++; + } + + for (int i = 0; i < count; i++) + ReturnKey(dbKeys[i]); + + return found; + } + public IEnumerable GetAll() { foreach (byte[] txBytes in _lightBlobTxsDb.GetAllValues()) @@ -78,7 +111,7 @@ public void Delete(in ValueHash256 hash, in UInt256 timestamp) _lightBlobTxsDb.Remove(hash.BytesAsSpan); } - public void AddBlobTransactionsFromBlock(long blockNumber, IList blockBlobTransactions) + public void AddBlobTransactionsFromBlock(long blockNumber, in ArrayPoolListRef blockBlobTransactions) { if (blockBlobTransactions.Count == 0) { @@ -132,6 +165,14 @@ private static bool TryDecodeLightTx(byte[]? txBytes, out LightTransaction? ligh return false; } + private byte[] RentKey() => _keyPool.TryDequeue(out byte[]? key) ? key : new byte[64]; + + private void ReturnKey(byte[] key) + { + if (_keyPool.Count < MaxPooledKeys) + _keyPool.Enqueue(key); + } + private static void GetHashPrefixedByTimestamp(UInt256 timestamp, ValueHash256 hash, Span txHashPrefixed) { timestamp.WriteBigEndian(txHashPrefixed); @@ -144,7 +185,7 @@ private void EncodeAndSaveTx(Transaction transaction, IDb db, Span txHashP db.PutSpan(txHashPrefixed, rlpStream.AsSpan()); } - private void EncodeAndSaveTxs(IList blockBlobTransactions, IDb db, long blockNumber) + private void EncodeAndSaveTxs(in ArrayPoolListRef blockBlobTransactions, IDb db, long blockNumber) { using NettyRlpStream rlpStream = _txDecoder.EncodeToNewNettyStream(blockBlobTransactions!, RlpBehaviors.InMempoolForm); db.PutSpan(blockNumber.ToBigEndianSpanWithoutLeadingZeros(out _), rlpStream.AsSpan()); diff --git a/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs b/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs index 262d10815345..c7b49199a4aa 100644 --- a/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs +++ b/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs @@ -4,11 +4,9 @@ using CkzgLib; using DotNetty.Common.Utilities; using Nethermind.Core; -using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Threading; -using Nethermind.Crypto; using Nethermind.Logging; using System; using System.Collections.Generic; @@ -77,20 +75,46 @@ private bool TryGetBlobAndProof( return false; } - public int GetBlobCounts(byte[][] requestedBlobVersionedHashes) + public virtual int TryGetBlobsAndProofsV1( + byte[][] requestedBlobVersionedHashes, + byte[]?[] blobs, + ReadOnlyMemory[] proofs) { - using var lockRelease = Lock.Acquire(); - int count = 0; + using McsLock.Disposable lockRelease = Lock.Acquire(); + int found = 0; - foreach (byte[] requestedBlobVersionedHash in requestedBlobVersionedHashes) + for (int i = 0; i < requestedBlobVersionedHashes.Length; i++) { - if (BlobIndex.ContainsKey(requestedBlobVersionedHash)) + byte[] requestedBlobVersionedHash = requestedBlobVersionedHashes[i]; + if (!BlobIndex.TryGetValue(requestedBlobVersionedHash, out List? txHashes)) + continue; + + foreach (Hash256 hash in CollectionsMarshal.AsSpan(txHashes)) { - count += 1; + if (!TryGetValueNonLocked(hash, out Transaction? blobTx) + || blobTx.BlobVersionedHashes is not { Length: > 0 }) + continue; + + bool matched = false; + for (int indexOfBlob = 0; indexOfBlob < blobTx.BlobVersionedHashes.Length; indexOfBlob++) + { + if (Bytes.AreEqual(blobTx.BlobVersionedHashes[indexOfBlob], requestedBlobVersionedHash) + && blobTx.NetworkWrapper is ShardBlobNetworkWrapper { Version: ProofVersion.V1 } wrapper) + { + blobs[i] = wrapper.Blobs[indexOfBlob]; + proofs[i] = new ReadOnlyMemory( + wrapper.Proofs, + Ckzg.CellsPerExtBlob * indexOfBlob, + Ckzg.CellsPerExtBlob); + found++; + matched = true; + break; + } + } + if (matched) break; } } - - return count; + return found; } protected override bool InsertCore(ValueHash256 key, Transaction value, AddressAsKey groupKey) diff --git a/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs b/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs index 7b4e1e4b1235..18647a7f5b7c 100644 --- a/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs +++ b/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs @@ -2,12 +2,17 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using CkzgLib; using Nethermind.Core; using Nethermind.Core.Caching; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Threading; using Nethermind.Logging; namespace Nethermind.TxPool.Collections; @@ -89,6 +94,127 @@ protected override bool TryGetValueNonLocked(ValueHash256 hash, [NotNullWhen(tru return false; } + public override int TryGetBlobsAndProofsV1( + byte[][] requestedBlobVersionedHashes, + byte[]?[] blobs, + ReadOnlyMemory[] proofs) + { + int found = 0; + int missCount = 0; + + // Rent arrays for Phase 2 (DB lookup of cache misses). + // Sized to 4x request length to accommodate multiple candidate tx hashes per blob + // (e.g. when the same blob versioned hash appears in multiple transactions). + int maxMisses = requestedBlobVersionedHashes.Length * 4; + TxLookupKey[] dbKeys = ArrayPool.Shared.Rent(maxMisses); + int[] missOutputIndex = ArrayPool.Shared.Rent(maxMisses); + int[] missBlobIndex = ArrayPool.Shared.Rent(maxMisses); + try + { + // Phase 1: Under lock — in-memory lookups only + using (McsLock.Disposable lockRelease = Lock.Acquire()) + { + for (int i = 0; i < requestedBlobVersionedHashes.Length; i++) + { + byte[] requestedBlobVersionedHash = requestedBlobVersionedHashes[i]; + if (!BlobIndex.TryGetValue(requestedBlobVersionedHash, out List? txHashes)) + continue; + + foreach (Hash256 hash in CollectionsMarshal.AsSpan(txHashes)) + { + if (!base.TryGetValueNonLocked(hash, out Transaction? lightTx) + || lightTx is not LightTransaction lt + || lt.ProofVersion != ProofVersion.V1 + || lightTx.BlobVersionedHashes is not { Length: > 0 }) + continue; + + int indexOfBlob = -1; + for (int b = 0; b < lightTx.BlobVersionedHashes.Length; b++) + { + if (Bytes.AreEqual(lightTx.BlobVersionedHashes[b], requestedBlobVersionedHash)) + { + indexOfBlob = b; + break; + } + } + if (indexOfBlob < 0) + continue; + + // Try cache first — on hit, populate and stop searching for this blob hash + if (_blobTxCache.TryGet(hash, out Transaction? cachedTx) + && cachedTx.NetworkWrapper is ShardBlobNetworkWrapper { Version: ProofVersion.V1 } cachedWrapper) + { + blobs[i] = cachedWrapper.Blobs[indexOfBlob]; + proofs[i] = new ReadOnlyMemory( + cachedWrapper.Proofs, + Ckzg.CellsPerExtBlob * indexOfBlob, + Ckzg.CellsPerExtBlob); + found++; + break; + } + + // Cache miss — record for Phase 2 DB lookup and continue to try + // remaining tx hashes so that if this candidate is missing from DB, + // later candidates can still satisfy the request. + if (missCount < maxMisses) + { + dbKeys[missCount] = new TxLookupKey(hash, lightTx.SenderAddress!, lightTx.Timestamp); + missOutputIndex[missCount] = i; + missBlobIndex[missCount] = indexOfBlob; + missCount++; + } + } + } + } + + // Phase 2: Outside lock — single RocksDB MultiGet for all misses + if (missCount > 0) + { + Transaction?[] dbResults = ArrayPool.Shared.Rent(missCount); + try + { + Array.Clear(dbResults, 0, missCount); + _blobTxStorage.TryGetMany(dbKeys, missCount, dbResults); + + for (int m = 0; m < missCount; m++) + { + int outIdx = missOutputIndex[m]; + + // Skip if this output slot was already filled by a cache hit or earlier candidate + if (blobs[outIdx] is not null) + continue; + + Transaction? fullTx = dbResults[m]; + if (fullTx?.NetworkWrapper is ShardBlobNetworkWrapper { Version: ProofVersion.V1 } wrapper) + { + int blobIdx = missBlobIndex[m]; + blobs[outIdx] = wrapper.Blobs[blobIdx]; + proofs[outIdx] = new ReadOnlyMemory( + wrapper.Proofs, + Ckzg.CellsPerExtBlob * blobIdx, + Ckzg.CellsPerExtBlob); + found++; + + _blobTxCache.Set(dbKeys[m].Hash, fullTx); + } + } + } + finally + { + ArrayPool.Shared.Return(dbResults, clearArray: true); + } + } + + return found; + } + finally + { + ArrayPool.Shared.Return(dbKeys, clearArray: true); + ArrayPool.Shared.Return(missOutputIndex); + ArrayPool.Shared.Return(missBlobIndex); + } + } + protected override bool Remove(ValueHash256 hash, out Transaction? tx) { if (base.Remove(hash, out tx)) diff --git a/src/Nethermind/Nethermind.TxPool/Collections/SortedPool.Events.cs b/src/Nethermind/Nethermind.TxPool/Collections/SortedPool.Events.cs index 9e6f03037652..b71516f43aec 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/Comparison/TxComparisonResult.cs b/src/Nethermind/Nethermind.TxPool/Comparison/TxComparisonResult.cs index 84896e596faa..f722468947ab 100644 --- a/src/Nethermind/Nethermind.TxPool/Comparison/TxComparisonResult.cs +++ b/src/Nethermind/Nethermind.TxPool/Comparison/TxComparisonResult.cs @@ -3,9 +3,29 @@ namespace Nethermind.TxPool.Comparison; +/// +/// Named constants for transaction comparison results. +/// +/// +/// In IComparer convention: negative means first arg has higher priority (comes first in sorted order). +/// public readonly struct TxComparisonResult { + // TxPool replacement semantics + /// Indicates that the new transaction should replace the old one. + public const int TakeNew = -1; + + /// Indicates that the comparison result between two transactions is currently undecided. public const int NotDecided = 0; + + /// Indicates that the old transaction should be retained instead of the new one. public const int KeepOld = 1; - public const int TakeNew = -1; + + // General comparison/sorting semantics (aliases) + /// The first argument has higher priority and should come first in sort order. + public const int XFirst = TakeNew; + /// Transactions are equal in priority. + public const int Equal = NotDecided; + /// The second argument has higher priority and should come first in sort order. + public const int YFirst = KeepOld; } diff --git a/src/Nethermind/Nethermind.TxPool/DelegationCache.cs b/src/Nethermind/Nethermind.TxPool/DelegationCache.cs index 1beba2074dc1..afcd529c667e 100644 --- a/src/Nethermind/Nethermind.TxPool/DelegationCache.cs +++ b/src/Nethermind/Nethermind.TxPool/DelegationCache.cs @@ -2,16 +2,11 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core; -using Nethermind.Int256; -using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; namespace Nethermind.TxPool; + internal sealed class DelegationCache { private readonly ConcurrentDictionary _pendingDelegations = new(); diff --git a/src/Nethermind/Nethermind.TxPool/Filters/DeployedCodeFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/DeployedCodeFilter.cs index a2a3b009f020..c7d90379f57d 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/DeployedCodeFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/DeployedCodeFilter.cs @@ -4,7 +4,6 @@ using System; using Nethermind.Core; using Nethermind.Core.Specs; -using Nethermind.Evm; using Nethermind.Evm.State; namespace Nethermind.TxPool.Filters diff --git a/src/Nethermind/Nethermind.TxPool/Filters/FeeTooLowFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/FeeTooLowFilter.cs index 1ab5ab76a3be..dee9638a68f1 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/Filters/PriorityFeeTooLowFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/PriorityFeeTooLowFilter.cs index 2b61d7695a14..c3be370bb208 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/PriorityFeeTooLowFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/PriorityFeeTooLowFilter.cs @@ -1,31 +1,37 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System.Diagnostics; using Nethermind.Core; -using Nethermind.Core.Extensions; using Nethermind.Int256; using Nethermind.Logging; namespace Nethermind.TxPool.Filters; -public class PriorityFeeTooLowFilter : IIncomingTxFilter +public class PriorityFeeTooLowFilter(IChainHeadInfoProvider chainHeadInfoProvider, ITxPoolConfig txPoolConfig, ILogger logger) : IIncomingTxFilter { - private readonly ILogger _logger; - private static readonly UInt256 _minBlobsPriorityFee = 1.GWei(); - - public PriorityFeeTooLowFilter(ILogger logger) - { - _logger = logger; - } + private readonly UInt256 _minBlobsPriorityFee = txPoolConfig.MinBlobTxPriorityFee; + private readonly bool _currentBlobBaseFeeRequired = txPoolConfig.CurrentBlobBaseFeeRequired; public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandlingOptions handlingOptions) { - if (tx.SupportsBlobs && tx.MaxPriorityFeePerGas < _minBlobsPriorityFee) + if (!tx.SupportsBlobs) + { + return AcceptTxResult.Accepted; + } + + if (tx.MaxPriorityFeePerGas < _minBlobsPriorityFee) { Metrics.PendingTransactionsTooLowPriorityFee++; - if (_logger.IsTrace) _logger.Trace($"Skipped adding transaction {tx.ToString(" ")}, too low priority fee with options {handlingOptions} from {new StackTrace()}"); - return AcceptTxResult.FeeTooLow.WithMessage($"MaxPriorityFeePerGas for blob transaction needs to be at least {_minBlobsPriorityFee} (1 GWei), is {tx.MaxPriorityFeePerGas}."); + if (logger.IsTrace) logger.Trace($"Skipped adding transaction {tx.ToString(" ")}, too low priority fee with options {handlingOptions} from {new StackTrace()}"); + return AcceptTxResult.FeeTooLow.WithMessage($"MaxPriorityFeePerGas for blob transaction needs to be at least {_minBlobsPriorityFee}, is {tx.MaxPriorityFeePerGas}."); + } + + if (_currentBlobBaseFeeRequired && tx.MaxFeePerBlobGas < chainHeadInfoProvider.CurrentFeePerBlobGas) + { + Metrics.PendingTransactionsTooLowFeePerBlobGas++; + if (logger.IsTrace) logger.Trace($"Skipped adding transaction {tx.ToString(" ")}, too low blob fee per gas with options {handlingOptions} from {new StackTrace()}"); + return AcceptTxResult.FeeTooLow.WithMessage($"MaxFeePerBlobGas needs to be at least equal to CurrentFeePerBlobGas ({chainHeadInfoProvider.CurrentFeePerBlobGas}), is {tx.MaxFeePerBlobGas}."); } return AcceptTxResult.Accepted; diff --git a/src/Nethermind/Nethermind.TxPool/HashCache.cs b/src/Nethermind/Nethermind.TxPool/HashCache.cs index 429bd42a2246..8ce7dd5d3fe9 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/IBlobTxStorage.cs b/src/Nethermind/Nethermind.TxPool/IBlobTxStorage.cs index 7adb61d7c05d..44ffac43dbc9 100644 --- a/src/Nethermind/Nethermind.TxPool/IBlobTxStorage.cs +++ b/src/Nethermind/Nethermind.TxPool/IBlobTxStorage.cs @@ -1,14 +1,14 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Collections.Generic; using Nethermind.Core; +using Nethermind.Core.Collections; namespace Nethermind.TxPool; public interface IBlobTxStorage : ITxStorage { bool TryGetBlobTransactionsFromBlock(long blockNumber, out Transaction[]? blockBlobTransactions); - void AddBlobTransactionsFromBlock(long blockNumber, IList blockBlobTransactions); + void AddBlobTransactionsFromBlock(long blockNumber, in ArrayPoolListRef blockBlobTransactions); void DeleteBlobTransactionsFromBlock(long blockNumber); } diff --git a/src/Nethermind/Nethermind.TxPool/IChainHeadInfoProvider.cs b/src/Nethermind/Nethermind.TxPool/IChainHeadInfoProvider.cs index d06bb32e6627..b673bf3eeadd 100644 --- a/src/Nethermind/Nethermind.TxPool/IChainHeadInfoProvider.cs +++ b/src/Nethermind/Nethermind.TxPool/IChainHeadInfoProvider.cs @@ -4,7 +4,6 @@ using System; using Nethermind.Core; using Nethermind.Core.Specs; -using Nethermind.Evm; using Nethermind.Evm.State; using Nethermind.Int256; diff --git a/src/Nethermind/Nethermind.TxPool/ITxPool.cs b/src/Nethermind/Nethermind.TxPool/ITxPool.cs index f4d703bf45b4..9759c6bd9f8f 100644 --- a/src/Nethermind/Nethermind.TxPool/ITxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/ITxPool.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.Crypto; using Nethermind.Int256; +using Nethermind.Network.Contract.Messages; namespace Nethermind.TxPool { @@ -38,6 +39,7 @@ public interface ITxPool void AddPeer(ITxPoolPeer peer); void RemovePeer(PublicKey nodeId); bool ContainsTx(Hash256 hash, TxType txType); + AnnounceResult NotifyAboutTx(Hash256 txhash, IMessageHandler retryHandler); AcceptTxResult SubmitTx(Transaction tx, TxHandlingOptions handlingOptions); bool RemoveTransaction(Hash256? hash); Transaction? GetBestTx(); @@ -51,7 +53,8 @@ bool TryGetBlobAndProofV0(byte[] blobVersionedHash, bool TryGetBlobAndProofV1(byte[] blobVersionedHash, [NotNullWhen(true)] out byte[]? blob, [NotNullWhen(true)] out byte[][]? cellProofs); - int GetBlobCounts(byte[][] blobVersionedHashes); + int TryGetBlobsAndProofsV1(byte[][] requestedBlobVersionedHashes, + byte[]?[] blobs, ReadOnlyMemory[] proofs); UInt256 GetLatestPendingNonce(Address address); event EventHandler NewDiscovered; event EventHandler NewPending; @@ -60,5 +63,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/ITxPoolConfig.cs b/src/Nethermind/Nethermind.TxPool/ITxPoolConfig.cs index 73da62109f03..d3adcc58273a 100644 --- a/src/Nethermind/Nethermind.TxPool/ITxPoolConfig.cs +++ b/src/Nethermind/Nethermind.TxPool/ITxPoolConfig.cs @@ -1,7 +1,8 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Config; +using Nethermind.Int256; namespace Nethermind.TxPool; @@ -51,6 +52,9 @@ public interface ITxPoolConfig : IConfig Description = "The max blob transaction size allowed, excluding blobs, in bytes.")] long? MaxBlobTxSize { get; set; } + [ConfigItem(DefaultValue = "true", Description = "Whether to require the max fee per blob gas to be greater than or equal to the current blob base fee when adding a blob transaction to the pool.")] + bool CurrentBlobBaseFeeRequired { get; set; } + [ConfigItem(DefaultValue = "false", Description = "Enable transformation of blob txs with network wrapper in version 0x0 (blob proof) to version 0x1 (cell proofs)", HiddenFromDocs = true)] @@ -67,4 +71,8 @@ public interface ITxPoolConfig : IConfig [ConfigItem(DefaultValue = "true", Description = "Add local transactions to persistent broadcast.")] bool PersistentBroadcastEnabled { get; set; } + + [ConfigItem(DefaultValue = "0", + Description = "The minimum priority fee in wei for blob transactions to be accepted into the transaction pool.")] + UInt256 MinBlobTxPriorityFee { get; set; } } diff --git a/src/Nethermind/Nethermind.TxPool/ITxStorage.cs b/src/Nethermind/Nethermind.TxPool/ITxStorage.cs index 251d723b9e05..beb3268f0bd8 100644 --- a/src/Nethermind/Nethermind.TxPool/ITxStorage.cs +++ b/src/Nethermind/Nethermind.TxPool/ITxStorage.cs @@ -9,9 +9,12 @@ namespace Nethermind.TxPool; +public readonly record struct TxLookupKey(ValueHash256 Hash, Address Sender, UInt256 Timestamp); + public interface ITxStorage { bool TryGet(in ValueHash256 hash, Address sender, in UInt256 timestamp, [NotNullWhen(true)] out Transaction? transaction); + int TryGetMany(TxLookupKey[] keys, int count, Transaction?[] results); IEnumerable GetAll(); void Add(Transaction transaction); void Delete(in ValueHash256 hash, in UInt256 timestamp); diff --git a/src/Nethermind/Nethermind.TxPool/LightTransaction.cs b/src/Nethermind/Nethermind.TxPool/LightTransaction.cs index 98b3de83b667..3cf8a3fdd619 100644 --- a/src/Nethermind/Nethermind.TxPool/LightTransaction.cs +++ b/src/Nethermind/Nethermind.TxPool/LightTransaction.cs @@ -28,7 +28,7 @@ public LightTransaction(Transaction fullTx) GasBottleneck = fullTx.GasBottleneck; Timestamp = fullTx.Timestamp; PoolIndex = fullTx.PoolIndex; - ProofVersion = (fullTx.NetworkWrapper as ShardBlobNetworkWrapper)?.Version ?? default; + ProofVersion = fullTx.GetProofVersion(); _size = fullTx.GetLength(); } @@ -63,5 +63,7 @@ public LightTransaction( _size = size; } - public ProofVersion ProofVersion { get; set; } + public ProofVersion? ProofVersion { get; set; } + + public override ProofVersion? GetProofVersion() => ProofVersion; } diff --git a/src/Nethermind/Nethermind.TxPool/Metrics.cs b/src/Nethermind/Nethermind.TxPool/Metrics.cs index ca85e8a84b9b..b85a23bbb123 100644 --- a/src/Nethermind/Nethermind.TxPool/Metrics.cs +++ b/src/Nethermind/Nethermind.TxPool/Metrics.cs @@ -41,6 +41,10 @@ public static class Metrics [Description("Number of pending transactions received that were ignored because of priority fee lower than minimal requirement.")] public static long PendingTransactionsTooLowPriorityFee { get; set; } + [CounterMetric] + [Description("Number of pending transactions received that were ignored because of fee per blob gas lower than minimal requirement.")] + public static long PendingTransactionsTooLowFeePerBlobGas { get; set; } + [CounterMetric] [Description("Number of pending transactions received that were ignored because of fee lower than the lowest fee in transaction pool.")] public static long PendingTransactionsTooLowFee { get; set; } diff --git a/src/Nethermind/Nethermind.TxPool/Nethermind.TxPool.csproj b/src/Nethermind/Nethermind.TxPool/Nethermind.TxPool.csproj index 7c667ed34e8a..a229c249e605 100644 --- a/src/Nethermind/Nethermind.TxPool/Nethermind.TxPool.csproj +++ b/src/Nethermind/Nethermind.TxPool/Nethermind.TxPool.csproj @@ -6,6 +6,7 @@ + @@ -14,6 +15,7 @@ + diff --git a/src/Nethermind/Nethermind.TxPool/NonceManager.cs b/src/Nethermind/Nethermind.TxPool/NonceManager.cs index 074b7687f6c9..b69692ce71fc 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/NullBlobTxStorage.cs b/src/Nethermind/Nethermind.TxPool/NullBlobTxStorage.cs index 6eeaf9df3538..e7bf5bd0f406 100644 --- a/src/Nethermind/Nethermind.TxPool/NullBlobTxStorage.cs +++ b/src/Nethermind/Nethermind.TxPool/NullBlobTxStorage.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Int256; @@ -20,6 +21,8 @@ public bool TryGet(in ValueHash256 hash, Address sender, in UInt256 timestamp, [ return false; } + public int TryGetMany(TxLookupKey[] keys, int count, Transaction?[] results) => 0; + public IEnumerable GetAll() => Array.Empty(); public void Add(Transaction transaction) { } @@ -32,7 +35,7 @@ public bool TryGetBlobTransactionsFromBlock(long blockNumber, out Transaction[]? return false; } - public void AddBlobTransactionsFromBlock(long blockNumber, IList blockBlobTransactions) { } + public void AddBlobTransactionsFromBlock(long blockNumber, in ArrayPoolListRef blockBlobTransactions) { } public void DeleteBlobTransactionsFromBlock(long blockNumber) { } } diff --git a/src/Nethermind/Nethermind.TxPool/NullTxPool.cs b/src/Nethermind/Nethermind.TxPool/NullTxPool.cs index 31ae2339fdb8..40c78fd606c4 100644 --- a/src/Nethermind/Nethermind.TxPool/NullTxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/NullTxPool.cs @@ -1,12 +1,13 @@ -// 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.Diagnostics.CodeAnalysis; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Int256; +using Nethermind.Network.Contract.Messages; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace Nethermind.TxPool { @@ -82,10 +83,12 @@ public bool TryGetBlobAndProofV1(byte[] blobVersionedHash, return false; } - public int GetBlobCounts(byte[][] blobVersionedHashes) => 0; + public int TryGetBlobsAndProofsV1(byte[][] requestedBlobVersionedHashes, + byte[]?[] blobs, ReadOnlyMemory[] proofs) => 0; public UInt256 GetLatestPendingNonce(Address address) => 0; + public AnnounceResult NotifyAboutTx(Hash256 txhash, IMessageHandler retryHandler) => AnnounceResult.RequestRequired; public event EventHandler NewDiscovered { @@ -111,5 +114,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 new file mode 100644 index 000000000000..9a797f733b86 --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool/RetryCache.cs @@ -0,0 +1,211 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using ConcurrentCollections; +using Microsoft.Extensions.ObjectPool; +using Nethermind.Core; +using Nethermind.Core.Caching; +using Nethermind.Logging; +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; + +namespace Nethermind.TxPool; + +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 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, 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; + + _mainLoopTask = Task.Run(async () => + { + PeriodicTimer timer = new(TimeSpan.FromMilliseconds(_checkMs)); + + while (await timer.WaitForNextTickAsync(token)) + { + try + { + while (!token.IsCancellationRequested && _expiringQueue.TryPeek(out (TResourceId ResourceId, DateTimeOffset ExpiresAfter) item) && item.ExpiresAfter <= DateTimeOffset.UtcNow) + { + if (_expiringQueue.TryDequeue(out item)) + { + Interlocked.Decrement(ref _expiringQueueCounter); + + if (_retryRequests.TryRemove(item.ResourceId, out ConcurrentHashSet>? requests)) + { + try + { + 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); + } + } + } + finally + { + _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(in TResourceId resourceId, IMessageHandler handler) + { + if (_token.IsCancellationRequested) + { + return AnnounceResult.RequestRequired; + } + + if (_expiringQueueCounter > _expiringQueueLimit) + { + if (_logger.IsDebug) _logger.Warn($"{nameof(TResourceId)} retry queue is full"); + + return AnnounceResult.RequestRequired; + } + + if (!_requestingResources.Contains(resourceId)) + { + AnnounceResult result = AnnounceResult.Delayed; + _retryRequests.AddOrUpdate(resourceId, (resourceId, retryHandler) => AnnounceAdd(resourceId, retryHandler, out result), _announceUpdate, handler); + return result; + } + + 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"); + + if (requests.Count < _maxRetryRequests) + { + requests.Add(retryHandler); + } + + return requests; + } + + public void Received(in TResourceId resourceId) + { + if (_logger.IsTrace) _logger.Trace($"Received {resourceId}"); + + 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 +{ + 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/SpecDrivenTxGossipPolicy.cs b/src/Nethermind/Nethermind.TxPool/SpecDrivenTxGossipPolicy.cs index 5f994b1773cd..fd0a9d0ca7bd 100644 --- a/src/Nethermind/Nethermind.TxPool/SpecDrivenTxGossipPolicy.cs +++ b/src/Nethermind/Nethermind.TxPool/SpecDrivenTxGossipPolicy.cs @@ -10,5 +10,5 @@ public class SpecDrivenTxGossipPolicy(IChainHeadInfoProvider chainHeadInfoProvid private IChainHeadInfoProvider ChainHeadInfoProvider { get; } = chainHeadInfoProvider; public bool ShouldGossipTransaction(Transaction tx) => - !tx.SupportsBlobs || (tx.NetworkWrapper as ShardBlobNetworkWrapper)?.Version == ChainHeadInfoProvider.CurrentProofVersion; + !tx.SupportsBlobs || tx.GetProofVersion() == ChainHeadInfoProvider.CurrentProofVersion; } diff --git a/src/Nethermind/Nethermind.TxPool/TransactionExtensions.cs b/src/Nethermind/Nethermind.TxPool/TransactionExtensions.cs index 4bcf0e2e96c7..82bff3123f62 100644 --- a/src/Nethermind/Nethermind.TxPool/TransactionExtensions.cs +++ b/src/Nethermind/Nethermind.TxPool/TransactionExtensions.cs @@ -93,12 +93,5 @@ internal static bool IsOverflowInTxCostAndValue(this Transaction tx, out UInt256 => IsOverflowWhenAddingTxCostToCumulative(tx, UInt256.Zero, out txCost); public static bool IsInMempoolForm(this Transaction tx) => tx.NetworkWrapper is not null; - - public static ProofVersion GetProofVersion(this Transaction mempoolTx) => mempoolTx switch - { - LightTransaction lt => lt.ProofVersion, - { NetworkWrapper: ShardBlobNetworkWrapper { Version: ProofVersion v } } => v, - _ => ProofVersion.V0, - }; } } diff --git a/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs b/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs index 2c3ec6bbca67..9b481c5b10fd 100644 --- a/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs +++ b/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs @@ -68,21 +68,24 @@ internal class TxBroadcaster : IDisposable private readonly TimeSpan _minTimeBetweenPersistedTxBroadcast = TimeSpan.FromSeconds(1); private readonly ILogger _logger; + private readonly ITimestamper _timestamper; public TxBroadcaster(IComparer comparer, ITimerFactory timerFactory, ITxPoolConfig txPoolConfig, IChainHeadInfoProvider chainHeadInfoProvider, ILogManager? logManager, - ITxGossipPolicy? transactionsGossipPolicy = null) + ITxGossipPolicy? transactionsGossipPolicy = null, + ITimestamper? timestamper = null) { _txPoolConfig = txPoolConfig; _headInfo = chainHeadInfoProvider; + _timestamper = timestamper ?? Timestamper.Default; _txGossipPolicy = transactionsGossipPolicy ?? ShouldGossip.Instance; // Allocate closure once _gossipFilter = _txGossipPolicy.ShouldGossipTransaction; _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - _persistentTxs = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, comparer, logManager); + _persistentTxs = new TxDistinctSortedPool(txPoolConfig.Size, comparer, logManager); _accumulatedTemporaryTxs = new ResettableList(512, 4); _txsToSend = new ResettableList(512, 4); @@ -172,7 +175,7 @@ internal void BroadcastPersistentTxs() return; } - DateTimeOffset now = DateTimeOffset.UtcNow; + DateTimeOffset now = _timestamper.UtcNowOffset; if (_lastPersistedTxBroadcast + _minTimeBetweenPersistedTxBroadcast > now) { if (_logger.IsTrace) _logger.Trace($"Minimum time between persistent tx broadcast not reached."); diff --git a/src/Nethermind/Nethermind.TxPool/TxNonceTxPoolReserveSealer.cs b/src/Nethermind/Nethermind.TxPool/TxNonceTxPoolReserveSealer.cs deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.NonceInfo.cs b/src/Nethermind/Nethermind.TxPool/TxPool.NonceInfo.cs deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index 4d3814b51f9c..4846479dd29f 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -1,16 +1,19 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Autofac.Features.AttributeFilters; using CkzgLib; using Nethermind.Core; using Nethermind.Core.Caching; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; +using Nethermind.Core.Messages; using Nethermind.Core.Specs; using Nethermind.Core.Timers; using Nethermind.Crypto; using Nethermind.Int256; using Nethermind.Logging; +using Nethermind.Network.Contract.Messages; using Nethermind.TxPool.Collections; using Nethermind.TxPool.Filters; using System; @@ -21,8 +24,6 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; -using Autofac.Features.AttributeFilters; -using Nethermind.Core.Messages; using static Nethermind.TxPool.Collections.TxDistinctSortedPool; using ITimer = Nethermind.Core.Timers.ITimer; @@ -36,6 +37,8 @@ namespace Nethermind.TxPool /// public class TxPool : ITxPool, IAsyncDisposable { + private readonly RetryCache _retryCache; + private readonly IIncomingTxFilter[] _preHashFilters; private readonly IIncomingTxFilter[] _postHashFilters; @@ -121,6 +124,7 @@ public TxPool(IEthereumEcdsa ecdsa, _specProvider = _headInfo.SpecProvider; SupportsBlobs = _txPoolConfig.BlobsSupport != BlobsSupportMode.Disabled; _cts = new(); + _retryCache = new RetryCache(logManager, requestingCacheSize: MemoryAllowance.TxHashCacheSize / 10, token: _cts.Token); MemoryAllowance.MemPoolSize = txPoolConfig.Size; @@ -131,7 +135,7 @@ public TxPool(IEthereumEcdsa ecdsa, _broadcaster = new TxBroadcaster(comparer, TimerFactory.Default, txPoolConfig, chainHeadInfoProvider, logManager, transactionsGossipPolicy); TxPoolHeadChanged += _broadcaster.OnNewHead; - _transactions = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, comparer, logManager); + _transactions = new TxDistinctSortedPool(txPoolConfig.Size, comparer, logManager); _transactions.Removed += OnRemovedTx; _blobTransactions = txPoolConfig.BlobsSupport.IsPersistentStorage() @@ -147,7 +151,7 @@ public TxPool(IEthereumEcdsa ecdsa, new NotSupportedTxFilter(txPoolConfig, _logger), new SizeTxFilter(txPoolConfig, _logger), new GasLimitTxFilter(_headInfo, txPoolConfig, logManager), - new PriorityFeeTooLowFilter(_logger), + new PriorityFeeTooLowFilter(_headInfo, txPoolConfig, _logger), new FeeTooLowFilter(_headInfo, _transactions, _blobTransactions, thereIsPriorityContract, _logger), new MalformedTxFilter(_specProvider, validator, _logger) ]; @@ -221,8 +225,9 @@ public bool TryGetBlobAndProofV1(byte[] blobVersionedHash, [NotNullWhen(true)] out byte[][]? cellProofs) => _blobTransactions.TryGetBlobAndProofV1(blobVersionedHash, out blob, out cellProofs); - public int GetBlobCounts(byte[][] blobVersionedHashes) - => _blobTransactions.GetBlobCounts(blobVersionedHashes); + public int TryGetBlobsAndProofsV1(byte[][] requestedBlobVersionedHashes, + byte[]?[] blobs, ReadOnlyMemory[] proofs) + => _blobTransactions.TryGetBlobsAndProofsV1(requestedBlobVersionedHashes, blobs, proofs); private void OnRemovedTx(object? sender, SortedPool.SortedPoolRemovedEventArgs args) { @@ -360,7 +365,7 @@ private void ReAddReorganisedTransactions(Block? previousBlock) private void RemoveProcessedTransactions(Block block) { Transaction[] blockTransactions = block.Transactions; - using ArrayPoolList blobTxsToSave = new((int)_specProvider.GetSpec(block.Header).MaxBlobCount); + using ArrayPoolListRef blobTxsToSave = new((int)_specProvider.GetSpec(block.Header).MaxBlobCount); long discoveredForPendingTxs = 0; long discoveredForHashCache = 0; long notInMempoool = 0; @@ -400,11 +405,6 @@ private void RemoveProcessedTransactions(Block block) } } - if (blockTx.Type == TxType.SetCode) - { - eip7702Txs++; - } - bool isKnown = IsKnown(txHash); if (!isKnown) { @@ -483,6 +483,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 @@ -533,6 +573,11 @@ public AcceptTxResult SubmitTx(Transaction tx, TxHandlingOptions handlingOptions _newHeadLock.ExitReadLock(); } + if (accepted != AcceptTxResult.Invalid) + { + _retryCache.Received(tx.Hash!); + } + if (accepted) { // Clear proper snapshot @@ -556,9 +601,9 @@ private void TryConvertProofVersion(Transaction tx) { if (_txPoolConfig.ProofsTranslationEnabled && tx is { SupportsBlobs: true, NetworkWrapper: ShardBlobNetworkWrapper { Version: ProofVersion.V0 } wrapper } - && _headInfo.CurrentProofVersion == ProofVersion.V1) + && _headInfo.CurrentProofVersion is ProofVersion.V1) { - using ArrayPoolList cellProofs = new(Ckzg.CellsPerExtBlob * wrapper.Blobs.Length); + using ArrayPoolListRef cellProofs = new(Ckzg.CellsPerExtBlob * wrapper.Blobs.Length); foreach (byte[] blob in wrapper.Blobs) { @@ -572,6 +617,11 @@ private void TryConvertProofVersion(Transaction tx) } } + 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) { IIncomingTxFilter[] filters = _preHashFilters; @@ -854,18 +904,15 @@ public bool RemoveTransaction(Hash256? hash) return false; } - if (hasBeenRemoved) - { - RemovedPending?.Invoke(this, new TxEventArgs(transaction)); + RemovedPending?.Invoke(this, new TxEventArgs(transaction)); - RemovePendingDelegations(transaction); - } + RemovePendingDelegations(transaction); _broadcaster.StopBroadcast(hash); if (_logger.IsTrace) _logger.Trace($"Removed a transaction: {hash}"); - return hasBeenRemoved; + return true; } public bool ContainsTx(Hash256 hash, TxType txType) => txType == TxType.Blob @@ -951,13 +998,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; } @@ -1069,20 +1117,21 @@ private static void WriteTxPoolReport(in ILogger logger) 2. Tx Too Large: {Metrics.PendingTransactionsSizeTooLarge,24:N0} 3. GasLimitTooHigh: {Metrics.PendingTransactionsGasLimitTooHigh,24:N0} 4. TooLow PriorityFee: {Metrics.PendingTransactionsTooLowPriorityFee,24:N0} -5. Too Low Fee: {Metrics.PendingTransactionsTooLowFee,24:N0} -6. Malformed: {Metrics.PendingTransactionsMalformed,24:N0} -7. Null Hash: {Metrics.PendingTransactionsNullHash,24:N0} -8. Duplicate: {Metrics.PendingTransactionsKnown,24:N0} -9. Unknown Sender: {Metrics.PendingTransactionsUnresolvableSender,24:N0} -10. Conflicting TxType: {Metrics.PendingTransactionsConflictingTxType,24:N0} -11. NonceTooFarInFuture {Metrics.PendingTransactionsNonceTooFarInFuture,24:N0} -12. Zero Balance: {Metrics.PendingTransactionsZeroBalance,24:N0} -13. Balance < tx.value: {Metrics.PendingTransactionsBalanceBelowValue,24:N0} -14. Balance Too Low: {Metrics.PendingTransactionsTooLowBalance,24:N0} -15. Nonce used: {Metrics.PendingTransactionsLowNonce,24:N0} -16. Nonces skipped: {Metrics.PendingTransactionsNonceGap,24:N0} -17. Failed replacement {Metrics.PendingTransactionsPassedFiltersButCannotReplace,24:N0} -18. Cannot Compete: {Metrics.PendingTransactionsPassedFiltersButCannotCompeteOnFees,24:N0} +5. TooLow FeePerBlobGa:{Metrics.PendingTransactionsTooLowFeePerBlobGas,24:N0} +6. Too Low Fee: {Metrics.PendingTransactionsTooLowFee,24:N0} +7. Malformed: {Metrics.PendingTransactionsMalformed,24:N0} +8. Null Hash: {Metrics.PendingTransactionsNullHash,24:N0} +9. Duplicate: {Metrics.PendingTransactionsKnown,24:N0} +10. Unknown Sender: {Metrics.PendingTransactionsUnresolvableSender,24:N0} +11. Conflicting TxType: {Metrics.PendingTransactionsConflictingTxType,24:N0} +12. NonceTooFarInFuture {Metrics.PendingTransactionsNonceTooFarInFuture,24:N0} +13. Zero Balance: {Metrics.PendingTransactionsZeroBalance,24:N0} +14. Balance < tx.value: {Metrics.PendingTransactionsBalanceBelowValue,24:N0} +15. Balance Too Low: {Metrics.PendingTransactionsTooLowBalance,24:N0} +16. Nonce used: {Metrics.PendingTransactionsLowNonce,24:N0} +17. Nonces skipped: {Metrics.PendingTransactionsNonceGap,24:N0} +18. Failed replacement {Metrics.PendingTransactionsPassedFiltersButCannotReplace,24:N0} +19. Cannot Compete: {Metrics.PendingTransactionsPassedFiltersButCannotCompeteOnFees,24:N0} ------------------------------------------------ Validated via State: {Metrics.PendingTransactionsWithExpensiveFiltering,24:N0} ------------------------------------------------ diff --git a/src/Nethermind/Nethermind.TxPool/TxPoolConfig.cs b/src/Nethermind/Nethermind.TxPool/TxPoolConfig.cs index 34da82493892..861c6d238529 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPoolConfig.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPoolConfig.cs @@ -1,30 +1,32 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core.Extensions; +using Nethermind.Int256; -namespace Nethermind.TxPool +namespace Nethermind.TxPool; + +public class TxPoolConfig : ITxPoolConfig { - public class TxPoolConfig : ITxPoolConfig - { - public int PeerNotificationThreshold { get; set; } = 5; - public int MinBaseFeeThreshold { get; set; } = 70; - public int Size { get; set; } = 2048; - public BlobsSupportMode BlobsSupport { get; set; } = BlobsSupportMode.StorageWithReorgs; - public int PersistentBlobStorageSize { get; set; } = 16 * 1024; // theoretical max - 13GB (128KB * 6 * 16384); for one-blob txs - 2GB (128KB * 1 * 16384); - // practical max - something between, but closer to 2GB than 12GB. Geth is limiting it to 10GB. - // every day about 21600 blobs will be included (7200 blocks per day * 3 blob target) - public int BlobCacheSize { get; set; } = 256; - public int InMemoryBlobPoolSize { get; set; } = 512; // it is used when persistent pool is disabled - public int MaxPendingTxsPerSender { get; set; } = 0; - public int MaxPendingBlobTxsPerSender { get; set; } = 16; - public int HashCacheSize { get; set; } = 512 * 1024; - public long? GasLimit { get; set; } = null; - public long? MaxTxSize { get; set; } = 128.KiB(); - public long? MaxBlobTxSize { get; set; } = 1.MiB(); - public bool ProofsTranslationEnabled { get; set; } = false; - public int? ReportMinutes { get; set; } = null; - public bool AcceptTxWhenNotSynced { get; set; } = false; - public bool PersistentBroadcastEnabled { get; set; } = true; - } + public int PeerNotificationThreshold { get; set; } = 5; + public int MinBaseFeeThreshold { get; set; } = 70; + public int Size { get; set; } = 2048; + public BlobsSupportMode BlobsSupport { get; set; } = BlobsSupportMode.StorageWithReorgs; + public int PersistentBlobStorageSize { get; set; } = 16 * 1024; // theoretical max - 13GB (128KB * 6 * 16384); for one-blob txs - 2GB (128KB * 1 * 16384); + // practical max - something between, but closer to 2GB than 12GB. Geth is limiting it to 10GB. + // every day about 21600 blobs will be included (7200 blocks per day * 3 blob target) + public int BlobCacheSize { get; set; } = 256; + public int InMemoryBlobPoolSize { get; set; } = 512; // it is used when persistent pool is disabled + public int MaxPendingTxsPerSender { get; set; } = 0; + public int MaxPendingBlobTxsPerSender { get; set; } = 16; + public int HashCacheSize { get; set; } = 512 * 1024; + public long? GasLimit { get; set; } = null; + public long? MaxTxSize { get; set; } = 128.KiB(); + public long? MaxBlobTxSize { get; set; } = 1.MiB(); + public bool ProofsTranslationEnabled { get; set; } = false; + public int? ReportMinutes { get; set; } = null; + public bool AcceptTxWhenNotSynced { get; set; } = false; + public bool PersistentBroadcastEnabled { get; set; } = true; + public bool CurrentBlobBaseFeeRequired { get; set; } = true; + public UInt256 MinBlobTxPriorityFee { get; set; } = 0; } diff --git a/src/Nethermind/Nethermind.TxPool/TxPoolInfoProvider.cs b/src/Nethermind/Nethermind.TxPool/TxPoolInfoProvider.cs index 277ba4042714..df17e8c65d11 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPoolInfoProvider.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPoolInfoProvider.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using System.Linq; using Nethermind.Core; diff --git a/src/Nethermind/Nethermind.TxPool/TxPoolSender.cs b/src/Nethermind/Nethermind.TxPool/TxPoolSender.cs index 231156f560fc..e201090e4e76 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 a2cabaa11eda..d357511536c0 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 d802ecb7b107..e1f9605b6213 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 bc056c21f843..d0d2743a4a7b 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 cd941753bc78..e5ca68166a80 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/IWallet.cs b/src/Nethermind/Nethermind.Wallet/IWallet.cs index 2860c4d1ec1c..eb9f86f1da28 100644 --- a/src/Nethermind/Nethermind.Wallet/IWallet.cs +++ b/src/Nethermind/Nethermind.Wallet/IWallet.cs @@ -7,7 +7,6 @@ using System.Text; using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; using Nethermind.Serialization.Rlp; namespace Nethermind.Wallet diff --git a/src/Nethermind/Nethermind.Wallet/ProtectedKeyStoreWallet.cs b/src/Nethermind/Nethermind.Wallet/ProtectedKeyStoreWallet.cs index c33deb9ee7a0..2afe410ddfa9 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.Wallet/WalletExtensions.cs b/src/Nethermind/Nethermind.Wallet/WalletExtensions.cs index b987870fdb95..2debbc941aae 100644 --- a/src/Nethermind/Nethermind.Wallet/WalletExtensions.cs +++ b/src/Nethermind/Nethermind.Wallet/WalletExtensions.cs @@ -4,11 +4,7 @@ using System; using System.Linq; using System.Security; -using System.Security.Cryptography; -using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Crypto; -using Nethermind.Serialization.Rlp; namespace Nethermind.Wallet { diff --git a/src/Nethermind/Nethermind.Xdc.Test/BlockInfoTests.cs b/src/Nethermind/Nethermind.Xdc.Test/BlockInfoTests.cs new file mode 100644 index 000000000000..ced27d8d1c34 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/BlockInfoTests.cs @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Xdc.Test.Helpers; +using Nethermind.Xdc.Types; +using NUnit.Framework; +using System.Threading.Tasks; + +namespace Nethermind.Xdc.Test; + +internal class BlockInfoTests +{ + [Test] + public async Task VerifyGenesisV2Block() + { + XdcTestBlockchain xdcTestBlockchain = await XdcTestBlockchain.Create(); + XdcBlockHeader genesisBlock = (XdcBlockHeader)xdcTestBlockchain.BlockTree.FindHeader(xdcTestBlockchain.BlockTree.Genesis!.Number + 1)!; + + BlockRoundInfo blockInfo = new BlockRoundInfo(genesisBlock.Hash!, 1, genesisBlock.Number); + + var result = XdcExtensions.ValidateBlockInfo(blockInfo, genesisBlock); + + Assert.That(result, Is.True); + } + + [Test] + public async Task RoundMismatch_Fails() + { + XdcTestBlockchain xdcTestBlockchain = await XdcTestBlockchain.Create(); + XdcBlockHeader headBlock = (XdcBlockHeader)xdcTestBlockchain.BlockTree.Head!.Header!; + + BlockRoundInfo blockInfo = new BlockRoundInfo(headBlock.Hash!, headBlock.ExtraConsensusData!.BlockRound - 1, headBlock.Number); + + var result = XdcExtensions.ValidateBlockInfo(blockInfo, headBlock); + + Assert.That(result, Is.False); + } + + + [Test] + public async Task HashMismatch_Fails() + { + XdcTestBlockchain xdcTestBlockchain = await XdcTestBlockchain.Create(); + XdcBlockHeader headBlock = (XdcBlockHeader)xdcTestBlockchain.BlockTree.Head!.Header!; + XdcBlockHeader parentBlock = (XdcBlockHeader)xdcTestBlockchain.BlockTree.FindHeader(headBlock.ParentHash!)!; + + BlockRoundInfo blockInfo = new BlockRoundInfo(parentBlock.Hash!, headBlock.ExtraConsensusData!.BlockRound, headBlock.Number); + + var result = XdcExtensions.ValidateBlockInfo(blockInfo, headBlock); + + Assert.That(result, Is.False); + } + + + [Test] + public async Task NumberMismatch_Fails() + { + XdcTestBlockchain xdcTestBlockchain = await XdcTestBlockchain.Create(); + XdcBlockHeader headBlock = (XdcBlockHeader)xdcTestBlockchain.BlockTree.Head!.Header!; + XdcBlockHeader parentBlock = (XdcBlockHeader)xdcTestBlockchain.BlockTree.FindHeader(headBlock.ParentHash!)!; + + BlockRoundInfo blockInfo = new BlockRoundInfo(headBlock.Hash!, headBlock.ExtraConsensusData!.BlockRound, parentBlock.Number); + + var result = XdcExtensions.ValidateBlockInfo(blockInfo, headBlock); + + Assert.That(result, Is.False); + } + + + [Test] + public async Task NoMismatch_Pass() + { + XdcTestBlockchain xdcTestBlockchain = await XdcTestBlockchain.Create(); + XdcBlockHeader headBlock = (XdcBlockHeader)xdcTestBlockchain.BlockTree.Head!.Header!; + + BlockRoundInfo blockInfo = new BlockRoundInfo(headBlock.Hash!, headBlock.ExtraConsensusData!.BlockRound, headBlock.Number); + + var result = XdcExtensions.ValidateBlockInfo(blockInfo, headBlock); + + Assert.That(result, Is.True); + } +} + diff --git a/src/Nethermind/Nethermind.Xdc.Test/Build.XdcBlockHeaderBuilder.cs b/src/Nethermind/Nethermind.Xdc.Test/Build.XdcBlockHeaderBuilder.cs deleted file mode 100644 index baef9f528ccb..000000000000 --- a/src/Nethermind/Nethermind.Xdc.Test/Build.XdcBlockHeaderBuilder.cs +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Core.Test.Builders; - -public static class BuildExtentions -{ - public static XdcBlockHeaderBuilder XdcBlockHeader(this Build build) - { - return new XdcBlockHeaderBuilder(); - } -} diff --git a/src/Nethermind/Nethermind.Xdc.Test/Contracts/MasternodeVotingContractTests.cs b/src/Nethermind/Nethermind.Xdc.Test/Contracts/MasternodeVotingContractTests.cs new file mode 100644 index 000000000000..8966f334d9b6 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/Contracts/MasternodeVotingContractTests.cs @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Autofac; +using FluentAssertions; +using Nethermind.Abi; +using Nethermind.Blockchain; +using Nethermind.Consensus.Processing; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; +using Nethermind.Core.Test; +using Nethermind.Core.Test.Builders; +using Nethermind.Core.Test.Db; +using Nethermind.Crypto; +using Nethermind.Db; +using Nethermind.Evm; +using Nethermind.Evm.State; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Specs; +using Nethermind.Specs.Forks; +using Nethermind.Xdc.Contracts; +using NSubstitute; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using static Nethermind.Consensus.Processing.AutoReadOnlyTxProcessingEnvFactory; + +namespace Nethermind.Xdc.Test; + +internal class MasternodeVotingContractTests +{ + [Test] + public void GetCandidatesAndStake_GenesisSetup_CanReadExpectedCandidates() + { + PrivateKey sender = TestItem.PrivateKeyA; + PrivateKey signer = TestItem.PrivateKeyB; + Address codeSource = TestItem.AddressC; + + ISpecProvider specProvider = new TestSpecProvider(Shanghai.Instance); + IDbProvider memDbProvider = TestMemDbProvider.Init(); + IWorldState stateProvider = TestWorldStateFactory.CreateForTest(memDbProvider, LimboLogs.Instance); + + IReleaseSpec finalSpec = specProvider.GetFinalSpec(); + BlockHeader genesis; + using (IDisposable _ = stateProvider.BeginScope(IWorldState.PreGenesis)) + { + stateProvider.CreateAccount(sender.Address, 1.Ether()); + byte[] code = XdcContractData.XDCValidatorBin(); + stateProvider.CreateAccountIfNotExists(codeSource, 0); + stateProvider.InsertCode(codeSource, ValueKeccak.Compute(code), code, Shanghai.Instance); + + Dictionary storage = GenesisAllocation; + foreach (KeyValuePair kvp in storage) + { + StorageCell cell = new(codeSource, UInt256.Parse(kvp.Key)); + stateProvider.Set(cell, Bytes.FromHexString(kvp.Value)); + } + + stateProvider.Commit(specProvider.GenesisSpec); + stateProvider.CommitTree(0); + + genesis = Build.A.XdcBlockHeader().WithStateRoot(stateProvider.StateRoot).TestObject; + } + + EthereumCodeInfoRepository codeInfoRepository = new(stateProvider); + VirtualMachine virtualMachine = new(new TestBlockhashProvider(specProvider), specProvider, LimboLogs.Instance); + EthereumTransactionProcessor transactionProcessor = new(BlobBaseFeeCalculator.Instance, specProvider, stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); + + 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); + + foreach (Address candidate in candidates) + { + UInt256 stake = masterVoting.GetCandidateStake(genesis, candidate); + stake.Should().Be(10_000_000.Ether()); + } + } + + private static Dictionary GenesisAllocation = + new Dictionary + { + ["0x0000000000000000000000000000000000000000000000000000000000000007"] = "0x0000000000000000000000000000000000000000000000000000000000000001", + ["0x0000000000000000000000000000000000000000000000000000000000000008"] = "0x0000000000000000000000000000000000000000000000000000000000000003", + ["0x0000000000000000000000000000000000000000000000000000000000000009"] = "0x0000000000000000000000000000000000000000000000000000000000000003", + ["0x000000000000000000000000000000000000000000000000000000000000000a"] = "0x0000000000000000000000000000000000000000000000000000000000000001", + ["0x000000000000000000000000000000000000000000000000000000000000000b"] = "0x000000000000000000000000000000000000000000084595161401484a000000", + ["0x000000000000000000000000000000000000000000000000000000000000000c"] = "0x00000000000000000000000000000000000000000000054b40b1f852bda00000", + ["0x000000000000000000000000000000000000000000000000000000000000000d"] = "0x0000000000000000000000000000000000000000000000000000000000000012", + ["0x000000000000000000000000000000000000000000000000000000000000000e"] = "0x000000000000000000000000000000000000000000000000000000000013c680", + ["0x000000000000000000000000000000000000000000000000000000000000000f"] = "0x0000000000000000000000000000000000000000000000000000000000069780", + ["0x1cb68bf63bb3b55abf504ef789bb06e8b2b266a334ca39892e163225a47b8267"] = "0x000000000000000000000000000000000000000000084595161401484a000000", + ["0x2c6b8fd5b2b39958a7e5a98eebf2c1c31122e89c7961ce1025e69a3d3f07fd20"] = "0x0000000000000000000000000000000000000000000000000000000000000001", + ["0x3639e2dfabac2c6baff147abd66f76b8e526e974a9a2a14163169aa03d2f8d4b"] = "0x0000000000000000000000000000000000000000000000000000000000000001", + ["0x473ba2a6d1aa200b3118a8abc51fe248a479e882e6c655ae014d9c66fbc181ed"] = "0x00000000000000000000000025c65b4b379ac37cf78357c4915f73677022eaff", + ["0x473ba2a6d1aa200b3118a8abc51fe248a479e882e6c655ae014d9c66fbc181ee"] = "0x000000000000000000000000c7d49d0a2cf198deebd6ce581af465944ec8b2bb", + ["0x473ba2a6d1aa200b3118a8abc51fe248a479e882e6c655ae014d9c66fbc181ef"] = "0x000000000000000000000000cfccdea1006a5cfa7d9484b5b293b46964c265c0", + ["0x53dbb2c13e64ef254df4bb7c7b541e84dd24870927f98f151db88daa464fb4dc"] = "0x000000000000000000000000381047523972c9fdc3aa343e0b96900a8e2fa765", + ["0x67a3292220e327ce969d100d7e4d83dd4b05efa763a5e4cdb04e0c0107736472"] = "0x000000000000000000000001381047523972c9fdc3aa343e0b96900a8e2fa765", + ["0x67a3292220e327ce969d100d7e4d83dd4b05efa763a5e4cdb04e0c0107736473"] = "0x000000000000000000000000000000000000000000084595161401484a000000", + ["0x78dfe8da08db00fe2cd4ddbd11f9cb7e4245ce35275d7734678593942034e181"] = "0x000000000000000000000001381047523972c9fdc3aa343e0b96900a8e2fa765", + ["0x78dfe8da08db00fe2cd4ddbd11f9cb7e4245ce35275d7734678593942034e182"] = "0x000000000000000000000000000000000000000000084595161401484a000000", + ["0x90e333b6971c3ecd09a0da09b031d63cdd2dc213d199a66955a8bf7df8a8142d"] = "0x000000000000000000000000000000000000000000084595161401484a000000", + ["0xa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688"] = "0x000000000000000000000000381047523972c9fdc3aa343e0b96900a8e2fa765", + ["0xac80bed7555f6f181a34915490d97d0bfe2c0e116d1c73b34523ca0d9749955c"] = "0x000000000000000000000000381047523972c9fdc3aa343e0b96900a8e2fa765", + ["0xae7e2a864ae923819e93a9f6183bc7ca0dcee93a0759238acd92344ad3216228"] = "0x000000000000000000000000000000000000000000084595161401484a000000", + ["0xb375859c4c97d60e8a699586dc5dd215f38f99e40430bb9261f085ee694ffb2c"] = "0x0000000000000000000000000000000000000000000000000000000000000001", + ["0xd5d5b62da76a3a9f2df0e9276cbaf8973a778bf41f7f4942e06243f195493e99"] = "0x000000000000000000000000381047523972c9fdc3aa343e0b96900a8e2fa765", + ["0xec8699f61c2c8bbdbc66463590788e526c60046dda98e8c70df1fb756050baa4"] = "0x0000000000000000000000000000000000000000000000000000000000000003", + ["0xf3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3"] = "0x00000000000000000000000025c65b4b379ac37cf78357c4915f73677022eaff", + ["0xf3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee4"] = "0x000000000000000000000000c7d49d0a2cf198deebd6ce581af465944ec8b2bb", + ["0xf3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee5"] = "0x000000000000000000000000cfccdea1006a5cfa7d9484b5b293b46964c265c0", + ["0xf4dd36495f675c407ac8f8d6dd8cc40162c854dba3ce4ce8919af34d0b1ed47c"] = "0x000000000000000000000001381047523972c9fdc3aa343e0b96900a8e2fa765", + ["0xf4dd36495f675c407ac8f8d6dd8cc40162c854dba3ce4ce8919af34d0b1ed47d"] = "0x000000000000000000000000000000000000000000084595161401484a000000" + }; +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/Contracts/XdcAbiLoadTests.cs b/src/Nethermind/Nethermind.Xdc.Test/Contracts/XdcAbiLoadTests.cs new file mode 100644 index 000000000000..8190abbeb53c --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/Contracts/XdcAbiLoadTests.cs @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions.Json; +using Nethermind.Blockchain.Contracts.Json; +using Nethermind.Xdc.Contracts; +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using System; + +namespace Nethermind.Xdc.Test.Contracts; + +internal class XdcAbiLoadTests +{ + [TestCase(typeof(MasternodeVotingContract))] + public void Can_load_contract(Type contractType) + { + var parser = new AbiDefinitionParser(); + var json = AbiDefinitionParser.LoadContract(contractType); + var contract = parser.Parse(json); + var serialized = AbiDefinitionParser.Serialize(contract); + JToken.Parse(serialized).Should().ContainSubtree(json); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/EpochSwitchManagerTests.cs b/src/Nethermind/Nethermind.Xdc.Test/EpochSwitchManagerTests.cs new file mode 100644 index 000000000000..8d950c090283 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/EpochSwitchManagerTests.cs @@ -0,0 +1,707 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Blockchain; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Types; +using NSubstitute; +using NUnit.Framework; +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Text; + +namespace Nethermind.Xdc.Test; + +internal class EpochSwitchManagerTests +{ + private static readonly ImmutableArray
SignerAddresses = [TestItem.AddressA, TestItem.AddressB]; + private static readonly ImmutableArray
PenalizedAddresses = [TestItem.AddressC, TestItem.AddressD]; + private static readonly ImmutableArray
StandbyAddresses = [TestItem.AddressE, TestItem.AddressF]; + private static readonly ImmutableArray SignerSignatures = [TestItem.RandomSignatureA, TestItem.RandomSignatureB]; + + private IEpochSwitchManager _epochSwitchManager; + private IBlockTree _tree; + private ISpecProvider _config; + private ISnapshotManager _snapshotManager; + + [SetUp] + public void Setup() + { + _tree = Substitute.For(); + _config = Substitute.For(); + _snapshotManager = Substitute.For(); + _epochSwitchManager = new EpochSwitchManager(_config, _tree, _snapshotManager); + } + + [Test] + public void IsEpochSwitchAtBlock_ShouldReturnTrue_WhenBlockNumberIsSwitchBlock() + { + // Arrange + var switchBlock = 10; + var epochLength = 5; + + XdcReleaseSpec releaseSpec = new() + { + EpochLength = epochLength, + SwitchBlock = switchBlock, + V2Configs = [new V2ConfigParams()] + }; + + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + + XdcBlockHeader header = Build.A.XdcBlockHeader() + .TestObject; + + header.Number = (long)switchBlock; + // Act + bool result = _epochSwitchManager.IsEpochSwitchAtBlock(header); + // Assert + + Assert.That(result, Is.True); + } + + [Test] + public void IsEpochSwitchAtBlock_ShouldReturnFalseWhenHeaderExtraDataFails() + { + // Arrange + var switchBlock = 10; + var epochLength = 5; + + XdcReleaseSpec releaseSpec = new() + { + EpochLength = epochLength, + SwitchBlock = switchBlock, + V2Configs = [new V2ConfigParams()] + }; + + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + + XdcBlockHeader header = Build.A.XdcBlockHeader() + .TestObject; + + header.Number = (long)switchBlock + 1; + header.ExtraData = Encoding.UTF8.GetBytes("InvalidExtraData"); + + // Act + bool result = _epochSwitchManager.IsEpochSwitchAtBlock(header); + // Assert + Assert.That(result, Is.False); + } + + [Test] + public void IsEpochSwitchAtBlock_ShouldReturnTrue_WhenProposedHeaderNumberIsSwitchBlock() + { + // Arrange + var gapNumber = 0ul; + + XdcReleaseSpec releaseSpec = new() + { + EpochLength = (int)5, + SwitchBlock = 101, + V2Configs = [new V2ConfigParams()] + }; + + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + + XdcBlockHeader chainHead = GetChainOfBlocks(_tree, _snapshotManager, releaseSpec, 100); + + XdcBlockHeader proposedHeader = Build.A.XdcBlockHeader() + .TestObject; + + proposedHeader.Number = (long)releaseSpec.SwitchBlock; + proposedHeader.ParentHash = chainHead.Hash; + + QuorumCertificate qc = new QuorumCertificate(new BlockRoundInfo(chainHead.Hash!, chainHead.ExtraConsensusData!.BlockRound, chainHead.Number), SignerSignatures.ToArray(), gapNumber); + ExtraFieldsV2 extraFieldsV2 = new ExtraFieldsV2(chainHead.ExtraConsensusData!.BlockRound + 1, qc); + proposedHeader.ExtraConsensusData = extraFieldsV2; + + // Act + bool result = _epochSwitchManager.IsEpochSwitchAtBlock(proposedHeader); + // Assert + Assert.That(result, Is.True); + } + + [Test] + public void IsEpochSwitchAtBlock_ShouldReturnTrue_WhenParentRoundIsLessThanEpochStartRound() + { + // Arrange + XdcReleaseSpec releaseSpec = new() + { + EpochLength = (int)5, + SwitchBlock = 0, + V2Configs = [new V2ConfigParams()], + SwitchEpoch = 2 + }; + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + + XdcBlockHeader chainHead = GetChainOfBlocks(_tree, _snapshotManager, releaseSpec, 99); + + XdcBlockHeader proposedHeader = Build.A.XdcBlockHeader() + .TestObject; + + proposedHeader.Number = (long)chainHead.Number + 1; + proposedHeader.ParentHash = chainHead.Hash; + + QuorumCertificate qc = new QuorumCertificate(new BlockRoundInfo(chainHead.Hash!, chainHead.ExtraConsensusData!.BlockRound, chainHead.Number), SignerSignatures.ToArray(), 1); + ExtraFieldsV2 extraFieldsV2 = new ExtraFieldsV2(chainHead.ExtraConsensusData!.BlockRound + 1, qc); + proposedHeader.ExtraConsensusData = extraFieldsV2; + // Act + bool result = _epochSwitchManager.IsEpochSwitchAtBlock(proposedHeader); + // Assert + Assert.That(chainHead.ExtraConsensusData!.BlockRound, Is.LessThan(extraFieldsV2.BlockRound)); + + Assert.That(result, Is.True); + } + + [Test] + public void IsEpochSwitchAtBlock_ShouldReturnFalse_WhenParentRoundIsGreaterThanEpochStartRound() + { + // Arrange + XdcReleaseSpec releaseSpec = new() + { + EpochLength = (int)5, + SwitchBlock = 0, + V2Configs = [new V2ConfigParams()], + SwitchEpoch = 2 + }; + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + + XdcBlockHeader chainHead = GetChainOfBlocks(_tree, _snapshotManager, releaseSpec, 101); + + XdcBlockHeader proposedHeader = Build.A.XdcBlockHeader() + .TestObject; + + proposedHeader.Number = (long)chainHead.Number + 1; + proposedHeader.ParentHash = chainHead.Hash; + + QuorumCertificate qc = new QuorumCertificate(new BlockRoundInfo(chainHead.Hash!, chainHead.ExtraConsensusData!.BlockRound, chainHead.Number), SignerSignatures.ToArray(), 1); + ExtraFieldsV2 extraFieldsV2 = new ExtraFieldsV2(chainHead.ExtraConsensusData!.BlockRound - 1, qc); + proposedHeader.ExtraConsensusData = extraFieldsV2; + // Act + bool result = _epochSwitchManager.IsEpochSwitchAtBlock(proposedHeader); + // Assert + Assert.That(chainHead.ExtraConsensusData!.BlockRound, Is.GreaterThan(extraFieldsV2.BlockRound)); + + Assert.That(result, Is.False); + } + + [Test] + public void IsEpochSwitchAtBlock_ShouldReturnFalse_WhenParentRoundIsEqualToEpochStartRound() + { + // Arrange + XdcReleaseSpec releaseSpec = new() + { + EpochLength = (int)5, + SwitchBlock = 0, + V2Configs = [new V2ConfigParams()], + SwitchEpoch = 2 + }; + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + + XdcBlockHeader chainHead = GetChainOfBlocks(_tree, _snapshotManager, releaseSpec, 100); + + XdcBlockHeader proposedHeader = Build.A.XdcBlockHeader() + .TestObject; + + proposedHeader.Number = (long)chainHead.Number + 1; + proposedHeader.ParentHash = chainHead.Hash; + + QuorumCertificate qc = new QuorumCertificate(new BlockRoundInfo(chainHead.Hash!, chainHead.ExtraConsensusData!.BlockRound, chainHead.Number), SignerSignatures.ToArray(), 1); + ExtraFieldsV2 extraFieldsV2 = new ExtraFieldsV2(chainHead.ExtraConsensusData!.BlockRound, qc); + proposedHeader.ExtraConsensusData = extraFieldsV2; + // Act + bool result = _epochSwitchManager.IsEpochSwitchAtBlock(proposedHeader); + // Assert + Assert.That(chainHead.ExtraConsensusData!.BlockRound, Is.EqualTo(extraFieldsV2.BlockRound)); + + Assert.That(result, Is.False); + } + + [Test] + public void IsEpochSwitchAtRound_ShouldReturnTrue_WhenParentIsSwitchBlock() + { + // Arrange + var switchBlock = 10; + var epochLength = 5; + var currRound = 2; + var headerHash = Keccak.Zero; + XdcReleaseSpec releaseSpec = new() + { + EpochLength = epochLength, + SwitchBlock = switchBlock, + V2Configs = [new V2ConfigParams()] + }; + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + + XdcBlockHeader parentHeader = Build.A.XdcBlockHeader() + .TestObject; + parentHeader.Hash = headerHash; + parentHeader.Number = switchBlock; + + bool result = _epochSwitchManager.IsEpochSwitchAtRound((ulong)currRound, parentHeader); + // Assert + Assert.That(result, Is.True); + } + + [Test] + public void IsEpochSwitchAtRound_ShouldReturnFalse_WhenExtraConsensusDataIsNull() + { + // Arrange + var switchBlock = 10; + var epochLength = 5; + var headerHash = Keccak.Zero; + XdcReleaseSpec releaseSpec = new() + { + EpochLength = (int)epochLength, + SwitchBlock = switchBlock, + V2Configs = [new V2ConfigParams()] + }; + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + XdcBlockHeader parentHeader = Build.A.XdcBlockHeader() + .TestObject; + parentHeader.Hash = headerHash; + parentHeader.Number = (long)switchBlock - 1; + parentHeader.ExtraConsensusData = null; + + bool result = _epochSwitchManager.IsEpochSwitchAtRound(1, parentHeader); + // Assert + Assert.That(result, Is.False); + } + + [Test] + public void IsEpochSwitchAtRound_ShouldReturnFalse_WhenParentRoundIsGreaterThanBlockRound() + { + // Arrange + var currRound = 42ul; + XdcReleaseSpec releaseSpec = new() + { + EpochLength = (int)5, + SwitchBlock = 0, + V2Configs = [new V2ConfigParams()] + }; + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + XdcBlockHeader chainHead = GetChainOfBlocks(_tree, _snapshotManager, releaseSpec, 101); + + bool result = _epochSwitchManager.IsEpochSwitchAtRound(currRound, chainHead); + // Assert + Assert.That(result, Is.False); + Assert.That(chainHead.ExtraConsensusData!.BlockRound, Is.GreaterThan(currRound)); + } + + [Test] + public void IsEpochSwitchAtRound_ShouldReturnTrue_WhenParentRoundIsLessThanEpochStartRound() + { + // Arrange + XdcReleaseSpec releaseSpec = new() + { + EpochLength = (int)5, + SwitchBlock = 0, + V2Configs = [new V2ConfigParams()], + SwitchEpoch = 2 + }; + + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + + // 99 is chosen so that parent round is less than epoch start round + XdcBlockHeader chainHead = GetChainOfBlocks(_tree, _snapshotManager, releaseSpec, 99); + + XdcBlockHeader proposedHeader = Build.A.XdcBlockHeader() + .TestObject; + + proposedHeader.Number = (long)chainHead.Number + 1; + proposedHeader.ParentHash = chainHead.Hash; + + QuorumCertificate qc = new QuorumCertificate(new BlockRoundInfo(chainHead.Hash!, chainHead.ExtraConsensusData!.BlockRound, chainHead.Number), SignerSignatures.ToArray(), 1); + ExtraFieldsV2 extraFieldsV2 = new ExtraFieldsV2(chainHead.ExtraConsensusData!.BlockRound + 1, qc); + proposedHeader.ExtraConsensusData = extraFieldsV2; + + ulong currentEpochNumber = (ulong)releaseSpec.SwitchEpoch + extraFieldsV2.BlockRound / (ulong)releaseSpec.EpochLength; + ulong currentEpochStartRound = currentEpochNumber * (ulong)releaseSpec.EpochLength; + + bool result = _epochSwitchManager.IsEpochSwitchAtRound(extraFieldsV2.BlockRound, chainHead); + + // Assert + Assert.That(chainHead.ExtraConsensusData!.BlockRound, Is.EqualTo(extraFieldsV2.BlockRound - 1)); + Assert.That(currentEpochStartRound, Is.GreaterThan(chainHead.ExtraConsensusData!.BlockRound)); + Assert.That(chainHead.ExtraConsensusData!.BlockRound, Is.LessThan(extraFieldsV2.BlockRound)); + Assert.That(result, Is.True); + } + + [Test] + public void IsEpochSwitchAtRound_ShouldReturnFalse_WhenParentRoundIsEqualToEpochStartRound() + { + // Arrange + XdcReleaseSpec releaseSpec = new() + { + EpochLength = (int)5, + SwitchBlock = 0, + V2Configs = [new V2ConfigParams()], + SwitchEpoch = 2 + }; + + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + + // 101 is chosen that parent is at epoch and child is not at epoch + XdcBlockHeader chainHead = GetChainOfBlocks(_tree, _snapshotManager, releaseSpec, 100); + + XdcBlockHeader proposedHeader = Build.A.XdcBlockHeader() + .TestObject; + + proposedHeader.Number = (long)chainHead.Number + 1; + proposedHeader.ParentHash = chainHead.Hash; + + QuorumCertificate qc = new QuorumCertificate(new BlockRoundInfo(chainHead.Hash!, chainHead.ExtraConsensusData!.BlockRound, chainHead.Number), SignerSignatures.ToArray(), 1); + ExtraFieldsV2 extraFieldsV2 = new ExtraFieldsV2(chainHead.ExtraConsensusData!.BlockRound + 1, qc); + proposedHeader.ExtraConsensusData = extraFieldsV2; + + ulong currentEpochNumber = ((ulong)releaseSpec.SwitchEpoch + extraFieldsV2.BlockRound) / (ulong)releaseSpec.EpochLength; + ulong currentEpochStartRound = currentEpochNumber * (ulong)releaseSpec.EpochLength; + + bool result = _epochSwitchManager.IsEpochSwitchAtRound(extraFieldsV2.BlockRound, chainHead); + + // Assert + Assert.That(chainHead.ExtraConsensusData!.BlockRound, Is.EqualTo(extraFieldsV2.BlockRound - 1)); + Assert.That(currentEpochStartRound, Is.EqualTo(chainHead.ExtraConsensusData!.BlockRound)); + Assert.That(chainHead.ExtraConsensusData!.BlockRound, Is.LessThan(extraFieldsV2.BlockRound)); + Assert.That(result, Is.False); + } + + [Test] + public void IsEpochSwitchAtRound_ShouldReturnFalse_WhenParentRoundIsGreaterThanEpochStartRound() + { + // Arrange + XdcReleaseSpec releaseSpec = new() + { + EpochLength = (int)5, + SwitchBlock = 0, + V2Configs = [new V2ConfigParams()], + SwitchEpoch = 2 + }; + + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + + // Create chain head at round 101 so that parent round is greater than epoch start round + XdcBlockHeader chainHead = GetChainOfBlocks(_tree, _snapshotManager, releaseSpec, 101); + + XdcBlockHeader proposedHeader = Build.A.XdcBlockHeader() + .TestObject; + + proposedHeader.Number = (long)chainHead.Number + 1; + proposedHeader.ParentHash = chainHead.Hash; + + QuorumCertificate qc = new QuorumCertificate(new BlockRoundInfo(chainHead.Hash!, chainHead.ExtraConsensusData!.BlockRound, chainHead.Number), SignerSignatures.ToArray(), 1); + ExtraFieldsV2 extraFieldsV2 = new ExtraFieldsV2(chainHead.ExtraConsensusData!.BlockRound + 1, qc); + proposedHeader.ExtraConsensusData = extraFieldsV2; + + ulong currentEpochNumber = ((ulong)releaseSpec.SwitchEpoch + extraFieldsV2.BlockRound) / (ulong)releaseSpec.EpochLength; + ulong currentEpochStartRound = currentEpochNumber * (ulong)releaseSpec.EpochLength; + + bool result = _epochSwitchManager.IsEpochSwitchAtRound(extraFieldsV2.BlockRound, chainHead); + + // Assert + Assert.That(chainHead.ExtraConsensusData!.BlockRound, Is.EqualTo(extraFieldsV2.BlockRound - 1)); + Assert.That(currentEpochStartRound, Is.LessThan(chainHead.ExtraConsensusData!.BlockRound)); + Assert.That(chainHead.ExtraConsensusData!.BlockRound, Is.LessThan(extraFieldsV2.BlockRound)); + Assert.That(result, Is.False); + } + + [Test] + public void GetEpochSwitchInfo_ShouldReturnNullIfBlockHashIsNotInTree() + { + var switchBlock = 10; + var epochLength = 4; + var parentHash = Keccak.EmptyTreeHash; + _tree.FindHeader(parentHash).Returns((XdcBlockHeader?)null); + + XdcReleaseSpec releaseSpec = new() + { + EpochLength = (int)epochLength, + SwitchBlock = switchBlock, + V2Configs = [new V2ConfigParams()] + }; + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + + var result = _epochSwitchManager.GetEpochSwitchInfo(parentHash); + Assert.That(result, Is.Null); + } + + [Test] + public void GetEpochSwitchInfo_ShouldReturnEpochNumbersIfBlockIsAtEpoch_BlockNumber_Is_Zero() + { + long blockNumber = 0; + Hash256 hash256 = Keccak.Zero; + + long epochLength = 5; + long expectedEpochNumber = blockNumber / epochLength; + + Address[] signers = [TestItem.AddressA, TestItem.AddressB]; + EpochSwitchInfo expected = new(signers, [], [], new BlockRoundInfo(hash256, 0, (long)blockNumber)); + + XdcReleaseSpec releaseSpec = new() + { + EpochLength = (int)epochLength, + SwitchBlock = blockNumber, + V2Configs = [new V2ConfigParams()], + GenesisMasterNodes = signers, + }; + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + + XdcBlockHeader header = Build.A.XdcBlockHeader() + .TestObject; + header.Hash = hash256; + header.Number = blockNumber; + header.ExtraData = FillExtraDataForTests([TestItem.AddressA, TestItem.AddressB]); + + _snapshotManager.GetSnapshotByBlockNumber(blockNumber, Arg.Any()).Returns(new Snapshot(header.Number, header.Hash!, signers)); + + _tree.FindHeader(blockNumber).Returns(header); + var result = _epochSwitchManager.GetEpochSwitchInfo(header); + result.Should().BeEquivalentTo(expected); + } + + [Test] + public void GetEpochSwitchInfo_ShouldReturnEpochSwitchIfBlockIsAtEpoch() + { + + XdcReleaseSpec releaseSpec = new() + { + EpochLength = 5, + SwitchBlock = 0, + V2Configs = [new V2ConfigParams()] + }; + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + + XdcBlockHeader chainHead = GetChainOfBlocks(_tree, _snapshotManager, releaseSpec, 100); + var parentHeader = (XdcBlockHeader)_tree.FindHeader(chainHead.ParentHash!)!; + + EpochSwitchInfo expected = new(SignerAddresses.ToArray(), StandbyAddresses.ToArray(), PenalizedAddresses.ToArray(), new BlockRoundInfo(chainHead.Hash!, chainHead.ExtraConsensusData!.BlockRound, chainHead.Number)); + expected.EpochSwitchParentBlockInfo = new(parentHeader.Hash!, parentHeader.ExtraConsensusData!.BlockRound, parentHeader.Number); + + var result = _epochSwitchManager.GetEpochSwitchInfo(chainHead.Hash!); + + Assert.That(result, Is.Not.Null); + result.Should().BeEquivalentTo(expected); + } + + [Test] + public void GetEpochSwitchInfo_ShouldReturnEpochNumbersIfParentBlockIsAtEpoch() + { + ulong blockNumber = 100; + ulong epochLength = 5; + ulong expectedEpochNumber = (ulong)blockNumber / epochLength; + + + XdcReleaseSpec releaseSpec = new() + { + EpochLength = (int)epochLength, + SwitchBlock = 0, + V2Configs = [new V2ConfigParams()] + }; + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + + // 101 is chosen that parent is at epoch and child is not at epoch + XdcBlockHeader chainHead = GetChainOfBlocks(_tree, _snapshotManager, releaseSpec, (int)blockNumber + 1); + + var parentHeader = (XdcBlockHeader)_tree.FindHeader((long)blockNumber)!; + EpochSwitchInfo expected = new( + parentHeader.ValidatorsAddress!.Value.ToArray(), + StandbyAddresses.ToArray(), + parentHeader.PenaltiesAddress!.Value.ToArray(), + new BlockRoundInfo(parentHeader.Hash!, parentHeader.ExtraConsensusData!.BlockRound, (long)blockNumber)); + + expected.EpochSwitchParentBlockInfo = new(parentHeader.ParentHash!, parentHeader.ExtraConsensusData.BlockRound - (ulong)1, parentHeader.Number - 1); + + var result = _epochSwitchManager.GetEpochSwitchInfo(chainHead.Hash!); + Assert.That(result, Is.Not.Null); + result.Should().BeEquivalentTo(expected); + } + + [Test] + public void GetEpochSwitchInfo_ShouldReturnNullIfBlockIsAtEpochAndSnapshotIsNull() + { + long blockNumber = 10; + long epochLength = 5; + long expectedEpochNumber = blockNumber / epochLength; + XdcReleaseSpec releaseSpec = new() + { + EpochLength = (int)epochLength, + SwitchBlock = blockNumber, + V2Configs = [new V2ConfigParams()] + }; + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + + XdcBlockHeader header = Build.A.XdcBlockHeader() + .TestObject; + header.Number = blockNumber; + header.ExtraData = FillExtraDataForTests([TestItem.AddressA, TestItem.AddressB]); + header.Hash = TestItem.KeccakA; + + _snapshotManager.GetSnapshotByBlockNumber(blockNumber, Arg.Any()).Returns((Snapshot)null!); + + _tree.FindHeader(blockNumber).Returns(header); + _tree.FindHeader(header.Hash).Returns(header); + + var result = _epochSwitchManager.GetEpochSwitchInfo(header.Hash); + Assert.That(result, Is.Null); + } + + [Test] + public void GetBlockByEpochNumber_ShouldReturnNullIfNoBlockFound() + { + // Arrange + var epochNumber = 10ul; + XdcReleaseSpec releaseSpec = new() + { + EpochLength = (int)5, + SwitchBlock = 0, + V2Configs = [new V2ConfigParams()] + }; + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + + // Act + var result = _epochSwitchManager.GetBlockByEpochNumber(epochNumber); + // Assert + Assert.That(result, Is.Null); + } + + [Test] + public void GetBlockByEpochNumber_ShouldReturnBlockIfFound() + { + // Arrange + var epochLength = 5ul; + var epochNumber = 7ul; + XdcReleaseSpec releaseSpec = new() + { + EpochLength = (int)epochLength, + SwitchBlock = 0, + V2Configs = [new V2ConfigParams()] + }; + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + + XdcBlockHeader chainHead = GetChainOfBlocks(_tree, _snapshotManager, releaseSpec, 100); + + var headBlock = new Block(chainHead); + _tree.Head.Returns(headBlock); + + int expectedBlockNumber = (int)(epochNumber * epochLength); + + var result = _epochSwitchManager.GetBlockByEpochNumber(epochNumber); + + Assert.That(result?.BlockNumber, Is.EqualTo(expectedBlockNumber)); + } + + [Test] + public void GetTimeoutCertificateEpochInfo_ShouldReturnEpochSwitchInfoForEpochContainingTcRound() + { + var epochLength = 5; + XdcReleaseSpec releaseSpec = new() + { + EpochLength = epochLength, + SwitchBlock = 0, + V2Configs = [new V2ConfigParams()] + }; + _config.GetSpec(Arg.Any()).Returns(releaseSpec); + + XdcBlockHeader chainHead = GetChainOfBlocks(_tree, _snapshotManager, releaseSpec, 20); + + var headBlock = new Block(chainHead); + _tree.Head.Returns(headBlock); + + // TC round 12 is within epoch that started at round 10 + var timeoutCertificate = new TimeoutCertificate(12, [], 0); + EpochSwitchInfo? result = _epochSwitchManager.GetTimeoutCertificateEpochInfo(timeoutCertificate); + result.Should().NotBeNull(); + result!.EpochSwitchBlockInfo.Round.Should().Be(10); + } + + private XdcBlockHeader GetChainOfBlocks(IBlockTree tree, ISnapshotManager snapManager, IXdcReleaseSpec spec, int length, int startRound = 0) + { + int i = startRound; + XdcBlockHeader block = CreateV2RegenesisBlock(spec); + do + { + if (i != startRound) + { + block = GenNormalBlock(spec, block!); + } + + if ((block.ExtraConsensusData?.BlockRound ?? 0ul) % (ulong)spec.EpochLength == 0) + { + snapManager.GetSnapshotByBlockNumber(block.Number, Arg.Any()).Returns(new Snapshot(block.Number, block.Hash!, [.. StandbyAddresses, .. SignerAddresses])); + } + + tree.FindHeader(block.Hash!).Returns(block); + tree.FindHeader(block.Number).Returns(block); + + } while (i++ < length); + + return block; + } + + private XdcBlockHeader GenNormalBlock(IXdcReleaseSpec spec, XdcBlockHeader? parent) + { + ulong newRound = 0; + Hash256? parentHash = null; + ulong prevRound = 0; + long blockNumber = 0; + if (parent is not null) + { + newRound = 1 + (parent.ExtraConsensusData?.BlockRound ?? 0); + blockNumber = 1 + parent.Number; + prevRound = parent.ExtraConsensusData?.BlockRound ?? 0; + parentHash = parent.Hash; + + } + Hash256 newBlockHash = Keccak.Compute(BitConverter.GetBytes(blockNumber).PadLeft(32)); + + + QuorumCertificate qc = new QuorumCertificate(new BlockRoundInfo(parent?.Hash ?? Keccak.Zero, prevRound, parent?.Number ?? 0), SignerSignatures.ToArray(), (ulong)spec.Gap); + ExtraFieldsV2 extraFieldsV2 = new ExtraFieldsV2((ulong)newRound, qc); + + XdcBlockHeader header = Build.A.XdcBlockHeader() + .TestObject; + header.Hash = newBlockHash; + header.Number = blockNumber; + header.ExtraConsensusData = extraFieldsV2; + header.ParentHash = parentHash; + + header.ValidatorsAddress = SignerAddresses; + header.PenaltiesAddress = PenalizedAddresses; + + return header; + } + + private XdcBlockHeader CreateV2RegenesisBlock(IXdcReleaseSpec spec) + { + Address[] signers = [TestItem.AddressA, TestItem.AddressB]; + + var header = (XdcBlockHeader)Build.A.XdcBlockHeader() + .WithNumber((long)spec.SwitchBlock) + .WithExtraData(FillExtraDataForTests(signers)) //2 master nodes + .WithParentHash(Keccak.EmptyTreeHash) + .TestObject; + + header.PenaltiesAddress = [TestItem.AddressC]; + return header; + } + + private byte[] FillExtraDataForTests(Address[] nextEpochCandidates) + { + var length = Address.Size * nextEpochCandidates?.Length ?? 0; + var extraData = new byte[XdcConstants.ExtraVanity + length + XdcConstants.ExtraSeal]; + + for (int i = 0; i < nextEpochCandidates!.Length; i++) + { + Array.Copy(nextEpochCandidates[i].Bytes, 0, extraData, XdcConstants.ExtraVanity + i * Address.Size, Address.Size); + } + + return extraData; + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/ExtraConsensusDataDecoderTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ExtraConsensusDataDecoderTests.cs index c4e3c6a06461..acc6363ea794 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/ExtraConsensusDataDecoderTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/ExtraConsensusDataDecoderTests.cs @@ -10,6 +10,8 @@ using NUnit.Framework; namespace Nethermind.Xdc.Test; + +[Parallelizable(ParallelScope.All)] internal class ExtraConsensusDataDecoderTests { [TestCase("0xec01eae5a02671d34ee512c8a06f194dca9801ecfa8eb6a3590d1b73e50666b07f53b8958180820384c08201c2")] @@ -31,7 +33,7 @@ public void Decode_XdcExtraDataRlp_IsEquivalentAfterReencoding(string extraDataR [TestCase(false)] public void Decode_XdcExtraDataRlp_IsEquivalentAfterReencoding(bool useRlpStream) { - ExtraFieldsV2 extraFields = new ExtraFieldsV2(1, new QuorumCert(new BlockRoundInfo(Hash256.Zero, 1, 1), [new Signature(new byte[64], 0), new Signature(new byte[64], 0), new Signature(new byte[64], 0)], 0)); + ExtraFieldsV2 extraFields = new ExtraFieldsV2(1, new QuorumCertificate(new BlockRoundInfo(Hash256.Zero, 1, 1), [new Signature(new byte[64], 0), new Signature(new byte[64], 0), new Signature(new byte[64], 0)], 0)); ExtraConsensusDataDecoder decoder = new(); var stream = new RlpStream(decoder.GetLength(extraFields)); decoder.Encode(stream, extraFields); @@ -51,4 +53,17 @@ public void Decode_XdcExtraDataRlp_IsEquivalentAfterReencoding(bool useRlpStream decodedExtraData.Should().BeEquivalentTo(extraFields); } + [Test] + public void Decode_QCIsNull_CanDecodeNormally() + { + ExtraFieldsV2 extraFieldsV2 = new ExtraFieldsV2(1, null!); + ExtraConsensusDataDecoder decoder = new(); + + Rlp encodedExtraData = decoder.Encode(extraFieldsV2); + + ExtraFieldsV2 unencoded = decoder.Decode(new RlpStream(encodedExtraData.Bytes)); + + unencoded.Should().BeEquivalentTo(extraFieldsV2); + } + } diff --git a/src/Nethermind/Nethermind.Xdc.Test/Helpers/QuorumCertificateBuilder.cs b/src/Nethermind/Nethermind.Xdc.Test/Helpers/QuorumCertificateBuilder.cs new file mode 100644 index 000000000000..068a8684b3e4 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/QuorumCertificateBuilder.cs @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Xdc.Types; + +namespace Nethermind.Xdc.Test.Helpers; + +public class QuorumCertificateBuilder : BuilderBase +{ + public QuorumCertificateBuilder() + { + TestObjectInternal = new QuorumCertificate(new BlockRoundInfo(Hash256.Zero, 1, 1), [new Signature(new byte[65]), new Signature(new byte[65])], 1); + } + + public QuorumCertificateBuilder WithBlockInfo(BlockRoundInfo blockInfo) + { + TestObjectInternal.ProposedBlockInfo = blockInfo; + return this; + } + + public QuorumCertificateBuilder WithSignatures(params Signature[] signatures) + { + TestObjectInternal.Signatures = signatures; + return this; + } + + public QuorumCertificateBuilder WithGapNumber(ulong gapNumber) + { + TestObjectInternal.GapNumber = gapNumber; + return this; + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/Helpers/TestRandomSigner.cs b/src/Nethermind/Nethermind.Xdc.Test/Helpers/TestRandomSigner.cs new file mode 100644 index 000000000000..5f184d4b74a3 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/TestRandomSigner.cs @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Consensus; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Crypto; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Nethermind.Xdc.Test.Helpers; + +internal class TestRandomSigner(List masternodeCandidates) : ISigner +{ + private readonly Random _rnd = new Random(); + private readonly EthereumEcdsa _ecdsa = new EthereumEcdsa(0); + public PrivateKey? Key { get; private set; } + + public Address Address => Key!.Address; + + public bool CanSign => true; + + public Signature Sign(in ValueHash256 message) + { + Key = masternodeCandidates[_rnd.Next(masternodeCandidates.Count)]; + return _ecdsa.Sign(Key, in message); + } + + public ValueTask Sign(Transaction tx) + { + throw new NotImplementedException(); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/Helpers/TestXdcBlockProducer.cs b/src/Nethermind/Nethermind.Xdc.Test/Helpers/TestXdcBlockProducer.cs new file mode 100644 index 000000000000..f558167652b7 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/TestXdcBlockProducer.cs @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Config; +using Nethermind.Consensus; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Producers; +using Nethermind.Consensus.Transactions; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Crypto; +using Nethermind.Evm.State; +using Nethermind.Logging; +using Nethermind.Xdc.Spec; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Nethermind.Xdc.Test.Helpers; + +internal class TestXdcBlockProducer( + ISigner signer, + CandidateContainer candidateContainer, + IEpochSwitchManager epochSwitchManager, + ISnapshotManager snapshotManager, + IXdcConsensusContext xdcContext, + ITxSource txSource, + IBlockchainProcessor processor, + ISealer sealer, + IBlockTree blockTree, + IWorldState stateProvider, + IGasLimitCalculator? gasLimitCalculator, + ITimestamper? timestamper, + ISpecProvider specProvider, + ILogManager logManager, + IDifficultyCalculator? difficultyCalculator, + IBlocksConfig? blocksConfig) : XdcBlockProducer(epochSwitchManager, snapshotManager, xdcContext, txSource, processor, sealer, blockTree, stateProvider, gasLimitCalculator, timestamper, specProvider, logManager, difficultyCalculator, blocksConfig) +{ + private readonly Signer signer = (Signer)signer; + + protected override BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAttributes payloadAttributes) + { + if (parent is not XdcBlockHeader xdcParent) + throw new ArgumentException($"Must be a {nameof(XdcBlockHeader)}", nameof(parent)); + var prepared = (XdcBlockHeader)base.PrepareBlockHeader(parent, payloadAttributes); + + IXdcReleaseSpec headSpec = _specProvider.GetXdcSpec(prepared, xdcContext.CurrentRound); + var leader = GetLeaderAddress(xdcParent, xdcContext.CurrentRound, headSpec); + signer.SetSigner(candidateContainer.MasternodeCandidates.First(k => k.Address == leader)); + prepared.Beneficiary = leader; + return prepared; + } + + private Address GetLeaderAddress(XdcBlockHeader currentHead, ulong round, IXdcReleaseSpec spec) + { + Address[] masternodes; + if (epochSwitchManager.IsEpochSwitchAtRound(round, currentHead)) + { + (masternodes, _) = snapshotManager.CalculateNextEpochMasternodes(currentHead.Number + 1, currentHead.Hash!, spec); + } + else + { + var epochSwitchInfo = epochSwitchManager.GetEpochSwitchInfo(currentHead); + masternodes = epochSwitchInfo!.Masternodes; + } + + int currentLeaderIndex = ((int)round % spec.EpochLength % masternodes.Length); + return masternodes[currentLeaderIndex]; + } +} + +internal class CandidateContainer(List candidates) +{ + public List MasternodeCandidates { get; set; } = candidates; +} 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 000000000000..0de5fe077c0e --- /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/XdcBlockHeaderBuilder.cs b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcBlockHeaderBuilder.cs index 345a9e69b080..38edac2ea101 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcBlockHeaderBuilder.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcBlockHeaderBuilder.cs @@ -10,7 +10,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection.PortableExecutable; namespace Nethermind.Core.Test.Builders; @@ -29,15 +28,15 @@ public XdcBlockHeaderBuilder() Address.Zero, UInt256.One, 1, - 30_000_000, + XdcConstants.DefaultTargetGasLimit, 1_700_000_000, - new byte[] { 1, 2, 3 }) + []) { StateRoot = Keccak.EmptyTreeHash, TxRoot = Keccak.EmptyTreeHash, ReceiptsRoot = Keccak.EmptyTreeHash, Bloom = Bloom.Empty, - GasUsed = 21_000, + GasUsed = Transaction.BaseTxGasCost, MixHash = Keccak.Compute("mix_hash"), Nonce = 0, Validators = new byte[20 * 2], @@ -63,9 +62,9 @@ public XdcBlockHeaderBuilder WithGeneratedExtraConsensusData(IEnumerable signatures = keys.Select(k => ecdsa.Sign(k, Keccak.Compute(qcEncoder.Encode(quorumForSigning, RlpBehaviors.ForSealing).Bytes))); - QuorumCert quorumCert = new QuorumCert(blockRoundInfo, [.. signatures], 450); + QuorumCertificate quorumCert = new QuorumCertificate(blockRoundInfo, [.. signatures], 450); ExtraFieldsV2 extraFieldsV2 = new ExtraFieldsV2(1, quorumCert); EncodeExtraData(extraFieldsV2); @@ -84,12 +83,24 @@ public XdcBlockHeaderBuilder WithExtraConsensusData(ExtraFieldsV2 extraFieldsV2) return this; } + public new XdcBlockHeaderBuilder WithParentHash(Hash256 parentHash) + { + XdcTestObjectInternal.ParentHash = parentHash; + return this; + } + public new XdcBlockHeaderBuilder WithBaseFee(UInt256 baseFee) { TestObjectInternal.BaseFeePerGas = baseFee; return this; } + public new XdcBlockHeaderBuilder WithNumber(long blockNumber) + { + TestObjectInternal.Number = blockNumber; + return this; + } + public new XdcBlockHeaderBuilder WithHash(Hash256 hash256) { TestObjectInternal.Hash = hash256; diff --git a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcModuleTestOverrides.cs b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcModuleTestOverrides.cs new file mode 100644 index 000000000000..aa53e0acc76b --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcModuleTestOverrides.cs @@ -0,0 +1,160 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Autofac; +using Nethermind.Api; +using Nethermind.Config; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Scheduler; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Modules; +using Nethermind.JsonRpc; +using Nethermind.KeyStore; +using Nethermind.Logging; +using Nethermind.Network; +using Nethermind.Serialization.Json; +using Nethermind.Serialization.Rlp; +using Nethermind.TxPool; +using Nethermind.Wallet; +using Nethermind.Xdc.Contracts; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Types; +using NSubstitute; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading.Tasks; +using Module = Autofac.Module; + +namespace Nethermind.Xdc.Test.Helpers; + +/// +/// Create a reasonably complete nethermind configuration. +/// It should not really have any test specific configuration which is set by `TestEnvironmentModule`. +/// May not work without `TestEnvironmentModule`. +/// +/// +/// +public class XdcModuleTestOverrides(IConfigProvider configProvider, ILogManager logManager) : Module +{ + protected override void Load(ContainerBuilder builder) + { + IInitConfig initConfig = configProvider.GetConfig(); + + base.Load(builder); + builder + .AddModule(new XdcModule()) + .AddModule(new PseudoNetworkModule()) + .AddModule(new TestBlockProcessingModule()) + + .AddSingleton() + + // add missing components + .AddSingleton() + .AddSingleton() + + // Environments + .AddSingleton((blockProcessingContext, chainHeadInfoProvider) => new BackgroundTaskScheduler( + blockProcessingContext.BranchProcessor, + chainHeadInfoProvider, + initConfig.BackgroundTaskConcurrency, + initConfig.BackgroundTaskMaxNumber, + logManager)) + .AddSingleton(new ProcessExitSource(default)) + .AddSingleton() + + // Crypto + .AddSingleton(Substitute.For()) + .AddSingleton() + .AddSingleton(Substitute.For()) + + // Rpc + .AddSingleton() + ; + + + // Yep... this global thing need to work. + builder.RegisterBuildCallback((_) => + { + var assembly = Assembly.GetAssembly(typeof(NetworkNodeDecoder)); + if (assembly is not null) + Rlp.RegisterDecoders(assembly, canOverrideExistingDecoders: true); + }); + } + + internal class RandomPenaltyHandler(ISpecProvider specProvider) : IPenaltyHandler + { + readonly Dictionary _penaltiesCache = new(); + public Address[] Penalize(long number, Hash256 currentHash, Address[] candidates, int count = 2) + { + var spec = specProvider.GetFinalSpec() as IXdcReleaseSpec ?? throw new ArgumentException("Must have XDC spec configured."); + if (number == spec.SwitchBlock) + { + return Array.Empty
(); + } + if (_penaltiesCache.ContainsKey(currentHash)) + { + return _penaltiesCache[currentHash]; + } + var nodesCount = candidates.Length; + List
penalized = new(); + + Random rand = new(); + while (penalized.Count < count && penalized.Count < nodesCount) + { + Address candidate = candidates[rand.Next(nodesCount)]; + if (!penalized.Contains(candidate)) + penalized.Add(candidate); + } + _penaltiesCache[currentHash] = penalized.ToArray(); + return _penaltiesCache[currentHash]; + } + public Address[] HandlePenalties(long number, Hash256 currentHash, Address[] candidates) + => Penalize(number, currentHash, candidates, 7); + } + + internal class TrustyForensics : IForensicsProcessor + { + public Task DetectEquivocationInVotePool(Vote vote, IEnumerable votePool) + { + return Task.CompletedTask; + } + + public (Hash256 AncestorHash, IList FirstPath, IList SecondPath) FindAncestorBlockHash(BlockRoundInfo firstBlockInfo, BlockRoundInfo secondBlockInfo) + { + return (Hash256.Zero, new List(), new List()); + } + + public Task ForensicsMonitoring(IEnumerable headerQcToBeCommitted, QuorumCertificate incomingQC) + { + return Task.CompletedTask; + } + + public Task ProcessForensics(QuorumCertificate incomingQC) + { + return Task.CompletedTask; + } + + public Task ProcessVoteEquivocation(Vote incomingVote) + { + return Task.CompletedTask; + } + + public Task SendForensicProof(QuorumCertificate firstQc, QuorumCertificate secondQc) + { + return Task.CompletedTask; + } + + public Task SendVoteEquivocationProof(Vote vote1, Vote vote2, Address signer) + { + return Task.CompletedTask; + } + + public Task SetCommittedQCs(IEnumerable headers, QuorumCertificate incomingQC) + { + return Task.CompletedTask; + } + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcSubnetBlockHeaderBuilder.cs b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcSubnetBlockHeaderBuilder.cs new file mode 100644 index 000000000000..7cd919de8615 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcSubnetBlockHeaderBuilder.cs @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Core.Crypto; +using Nethermind.Crypto; +using Nethermind.Int256; +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc; +using Nethermind.Xdc.RLP; +using Nethermind.Xdc.Types; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Nethermind.Core.Test.Builders; + +public class XdcSubnetBlockHeaderBuilder : XdcBlockHeaderBuilder +{ + private XdcSubnetBlockHeader XdcTestObjectInternal => (XdcSubnetBlockHeader)TestObjectInternal; + + public new XdcSubnetBlockHeader TestObject => (XdcSubnetBlockHeader)base.TestObject; + + + public XdcSubnetBlockHeaderBuilder() + { + TestObjectInternal = new XdcSubnetBlockHeader( + Keccak.Compute("parent"), + Keccak.OfAnEmptySequenceRlp, + Address.Zero, + UInt256.One, + 1, + XdcConstants.DefaultTargetGasLimit, + 1_700_000_000, + []) + { + StateRoot = Keccak.EmptyTreeHash, + TxRoot = Keccak.EmptyTreeHash, + ReceiptsRoot = Keccak.EmptyTreeHash, + Bloom = Bloom.Empty, + GasUsed = Transaction.BaseTxGasCost, + MixHash = Keccak.Compute("mix_hash"), + Nonce = 0, + Validator = new byte[65], + Validators = new byte[20 * 2], + NextValidators = new byte[20 * 2], + Penalties = Array.Empty(), + }; + } + public XdcSubnetBlockHeaderBuilder WithNextValidators(byte[] nextValidators) + { + XdcTestObjectInternal.NextValidators = nextValidators; + return this; + } + public XdcSubnetBlockHeaderBuilder WithNextValidators(Address[] nextValidators) + { + XdcTestObjectInternal.NextValidators = nextValidators.SelectMany(a => a.Bytes).ToArray(); + return this; + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs new file mode 100644 index 000000000000..18c46daf5ecb --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs @@ -0,0 +1,614 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Autofac; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Find; +using Nethermind.Blockchain.Receipts; +using Nethermind.Config; +using Nethermind.Consensus; +using Nethermind.Consensus.Comparers; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Producers; +using Nethermind.Consensus.Rewards; +using Nethermind.Consensus.Validators; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Blockchain; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Db; +using Nethermind.Evm; +using Nethermind.Evm.State; +using Nethermind.Facade.Find; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Network; +using Nethermind.Serialization.Json; +using Nethermind.Serialization.Rlp; +using Nethermind.Specs; +using Nethermind.Specs.Forks; +using Nethermind.State; +using Nethermind.State.Repositories; +using Nethermind.TxPool; +using Nethermind.TxPool.Filters; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.TxPool; +using Nethermind.Xdc.Types; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Nethermind.Xdc.Test.Helpers; + + +public class XdcTestBlockchain : TestBlockchain +{ + private readonly Random _random = new(); + private readonly bool _useHotStuffModule; + + public static async Task Create(int blocksToAdd = 3, bool useHotStuffModule = false, Action? configurer = null) + { + XdcTestBlockchain chain = new(useHotStuffModule); + await chain.Build(configurer); + + var fromXdcContainer = (FromContainer)chain.Container.Resolve(); + + Configuration testConfiguration = fromXdcContainer.Configuration; + + if (testConfiguration.SuggestGenesisOnStart) + { + // 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; + chain.QuorumCertificateManager.Initialize((XdcBlockHeader)chain.BlockTree.Head!.Header); + } + + await chain.AddBlocks(blocksToAdd, true); + + return chain; + } + + public List MasterNodeCandidates { get; } + public List RandomKeys { get; } + public IXdcConsensusContext XdcContext => Container.Resolve(); + public IEpochSwitchManager EpochSwitchManager => _fromXdcContainer.EpochSwitchManager; + public IQuorumCertificateManager QuorumCertificateManager => _fromXdcContainer.QuorumCertificateManager; + public ITimeoutCertificateManager TimeoutCertificateManager => _fromXdcContainer.TimeoutCertificateManager; + public ISnapshotManager SnapshotManager => _fromXdcContainer.SnapshotManager; + public IVotesManager VotesManager => _fromXdcContainer.VotesManager; + internal TestRandomSigner RandomSigner { get; } + internal XdcHotStuff ConsensusModule => (XdcHotStuff)BlockProducerRunner; + + protected XdcTestBlockchain(bool useHotStuffModule) + { + var keys = new PrivateKeyGenerator().Generate(210).ToList(); + MasterNodeCandidates = keys.Take(200).ToList(); + RandomKeys = keys.Skip(200).ToList(); + RandomSigner = new TestRandomSigner(MasterNodeCandidates); + _useHotStuffModule = useHotStuffModule; + } + + public Signer Signer => (Signer)_fromXdcContainer.Signer; + + private FromXdcContainer _fromXdcContainer = null!; + public class FromXdcContainer( + Lazy stateReader, + Lazy ethereumEcdsa, + Lazy nonceManager, + Lazy receiptStorage, + Lazy txPool, + Lazy worldStateManager, + Lazy blockPreprocessorStep, + Lazy blockTree, + Lazy blockFinder, + Lazy logFinder, + Lazy chainHeadInfoProvider, + Lazy dbProvider, + Lazy specProvider, + Lazy sealEngine, + Lazy transactionComparerProvider, + Lazy poSSwitcher, + Lazy chainLevelInfoRepository, + Lazy mainProcessingContext, + Lazy readOnlyTxProcessingEnvFactory, + Lazy blockProducerEnvFactory, + Lazy configuration, + Lazy testBlockchainUtil, + Lazy poWTestBlockchainUtil, + Lazy manualTimestamper, + Lazy blockProductionTrigger, + Lazy shareableTxProcessorSource, + Lazy sealer, + Lazy forkInfo, + Lazy epochSwitchManager, + Lazy quorumCertificateManager, + Lazy timeoutCertificateManager, + Lazy snapshotManager, + Lazy votesManager, + Lazy signer + ) : FromContainer(stateReader, ethereumEcdsa, nonceManager, receiptStorage, txPool, worldStateManager, blockPreprocessorStep, blockTree, blockFinder, logFinder, chainHeadInfoProvider, dbProvider, specProvider, sealEngine, transactionComparerProvider, poSSwitcher, chainLevelInfoRepository, mainProcessingContext, readOnlyTxProcessingEnvFactory, blockProducerEnvFactory, configuration, testBlockchainUtil, poWTestBlockchainUtil, manualTimestamper, blockProductionTrigger, shareableTxProcessorSource, sealer, forkInfo) + { + public IEpochSwitchManager EpochSwitchManager => epochSwitchManager.Value; + public IQuorumCertificateManager QuorumCertificateManager => quorumCertificateManager.Value; + public ITimeoutCertificateManager TimeoutCertificateManager => timeoutCertificateManager.Value; + public ISnapshotManager SnapshotManager => snapshotManager.Value; + public ISigner Signer => signer.Value; + public IVotesManager VotesManager => votesManager.Value; + } + // Please don't add any new parameter to this method. Pass any customization via autofac's configuration + // or override method or a utility function that wrap around the autofac configuration. + // Try to use plugin's module where possible to make sure prod and test components are wired similarly. + protected override Task Build(Action? configurer = null) + { + JsonSerializer = new EthereumJsonSerializer(); + + IConfigProvider configProvider = new ConfigProvider([.. CreateConfigs()]); + + ContainerBuilder builder = ConfigureContainer(new ContainerBuilder(), configProvider); + configurer?.Invoke(builder); + + Container = builder.Build(); + + _fromXdcContainer = Container.Resolve(); + _fromContainer = (FromContainer)_fromXdcContainer; + + BlockchainProcessor.Start(); + + BlockProducer = CreateTestBlockProducer(); + BlockProducerRunner ??= CreateBlockProducerRunner(); + + Suggester = new XdcBlockSuggester(BlockTree, BlockProducerRunner); + + return Task.FromResult((TestBlockchain)this); + } + + protected override ContainerBuilder ConfigureContainer(ContainerBuilder builder, IConfigProvider configProvider) + { + var container = base.ConfigureContainer(builder, configProvider) + .AddModule(new XdcModuleTestOverrides(configProvider, LimboLogs.Instance)) + .AddSingleton( + new TestSpecProvider(WrapReleaseSpec(Shanghai.Instance)) + { + AllowTestChainOverride = false, + }) + .AddSingleton() + .AddSingleton() + .AddSingleton() + .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(); + //Set the first signer to be a non master node to avoid accidental block proposals + return new Signer(spec.ChainId, TestItem.PrivateKeyA, logManager); + }) + .AddSingleton((_) => BlockProducer) + //.AddSingleton((_) => BlockProducerRunner) + .AddSingleton() + .AddSingleton() + + .AddSingleton((ctx) => + { + var gossipPolicy = ctx.Resolve(); + + var compoundPolicy = new CompositeTxGossipPolicy(); + if (gossipPolicy != null) + { + compoundPolicy.Policies.Add(gossipPolicy); + } + + compoundPolicy.Policies.Add(new XdcTxGossipPolicy(SpecProvider, ctx.Resolve())); + + Nethermind.TxPool.TxPool txPool = new(ctx.Resolve()!, + ctx.Resolve() ?? NullBlobTxStorage.Instance, + ctx.Resolve(), + ctx.Resolve(), + ctx.Resolve(), + ctx.Resolve(), + new XdcTransactionComparerProvider(SpecProvider, BlockTree).GetDefaultComparer(), + compoundPolicy, + new SignTransactionFilter(Signer, BlockTree, SpecProvider), + ctx.Resolve() + ); + + return txPool; + }) + + .AddSingleton(new ProcessExitSource(TestContext.CurrentContext.CancellationToken)) + + .AddSingleton((cfg) => new TestBlockchainUtil.Config(cfg.SlotTime)) + + + + .AddSingleton((ctx) => new PoWTestBlockchainUtil( + ctx.Resolve(), + ctx.Resolve(), + ctx.Resolve(), + ctx.Resolve(), + ctx.Resolve(), + ctx.Resolve().SlotTime + )); + + return container; + } + + private IXdcReleaseSpec WrapReleaseSpec(IReleaseSpec spec) + { + var xdcSpec = XdcReleaseSpec.FromReleaseSpec(spec); + + xdcSpec.GenesisMasterNodes = MasterNodeCandidates.Take(30).Select(k => k.Address).ToArray(); + xdcSpec.EpochLength = 900; + xdcSpec.Gap = 450; + xdcSpec.SwitchEpoch = 0; + xdcSpec.SwitchBlock = 0; + xdcSpec.MasternodeReward = 2.0; // 2 Ether per masternode + xdcSpec.ProtectorReward = 1.0; // 1 Ether per protector + xdcSpec.ObserverReward = 0.5; // 0.5 Ether per observer + xdcSpec.MinimumMinerBlockPerEpoch = 1; + xdcSpec.LimitPenaltyEpoch = 2; + xdcSpec.MinimumSigningTx = 1; + xdcSpec.GasLimitBoundDivisor = 1024; + + xdcSpec.BlackListedAddresses = + [ + new Address("0x00000000000000000000000000000000b1Ac701"), + new Address("0x00000000000000000000000000000000b1Ac702"), + new Address("0x00000000000000000000000000000000b1Ac703"), + new Address("0x00000000000000000000000000000000b1Ac704"), + new Address("0x00000000000000000000000000000000b1Ac705"), + new Address("0x00000000000000000000000000000000b1Ac706"), + new Address("0x00000000000000000000000000000000b1Ac707"), + ]; + xdcSpec.MergeSignRange = 15; + + xdcSpec.BlockSignerContract = new Address("0x00000000000000000000000000000000b000089"); + xdcSpec.RandomizeSMCBinary = new Address("0x00000000000000000000000000000000b000090"); + xdcSpec.FoundationWallet = new Address("0x0000000000000000000000000000000000000068"); + + V2ConfigParams[] v2ConfigParams = [ + new V2ConfigParams { + SwitchRound = 0, + MaxMasternodes = 30, + CertThreshold = 0.667, + TimeoutSyncThreshold = 3, + TimeoutPeriod = 3000, + MinePeriod = 2 + }, + new V2ConfigParams { + SwitchRound = 5, + MaxMasternodes = 30, + CertThreshold = 0.667, + TimeoutSyncThreshold = 3, + TimeoutPeriod = 3000, + MinePeriod = 2 + }, + new V2ConfigParams { + SwitchRound = 10, + MaxMasternodes = 30, + CertThreshold = 0.667, + TimeoutSyncThreshold = 3, + TimeoutPeriod = 3000, + MinePeriod = 2 + }, + new V2ConfigParams { + SwitchRound = 15, + MaxMasternodes = 30, + CertThreshold = 0.667, + TimeoutSyncThreshold = 3, + TimeoutPeriod = 3000, + MinePeriod = 2 + }, + new V2ConfigParams { + SwitchRound = 20, + MaxMasternodes = 30, + CertThreshold = 0.667, + TimeoutSyncThreshold = 3, + TimeoutPeriod = 3000, + MinePeriod = 2 + } + ]; + + xdcSpec.V2Configs = v2ConfigParams.ToList(); + xdcSpec.ValidateChainId = false; + xdcSpec.Reward = 5000; + return xdcSpec; + } + + protected override IBlockProducer CreateTestBlockProducer() + { + IBlockProducerEnv env = BlockProducerEnvFactory.Create(); + return new TestXdcBlockProducer( + Signer, + Container.Resolve(), + EpochSwitchManager, + SnapshotManager, + XdcContext, + env.TxSource, + env.ChainProcessor, + Sealer, + BlockTree, + env.ReadOnlyStateProvider, + new FollowOtherMiners(SpecProvider), + Timestamper, + SpecProvider, + LogManager, + ConstantDifficulty.One, + BlocksConfig); + } + + protected override IBlockProducerRunner CreateBlockProducerRunner() + { + if (_useHotStuffModule) + { + return Container.Resolve(); + } + return base.CreateBlockProducerRunner(); + } + + private class XdcTestGenesisBuilder( + ISpecProvider specProvider, + IWorldState state, + IGenesisPostProcessor[] postProcessors, + Configuration testConfiguration, + List masterNodeCandidates + ) : IGenesisBuilder + { + public Block Build() + { + state.CreateAccount(TestItem.AddressA, testConfiguration.AccountInitialValue); + 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(); + + var genesisSpec = specProvider.GenesisSpec as IXdcReleaseSpec; + + state.CreateAccount(finalSpec.BlockSignerContract, 100_000); + state.CreateAccount(finalSpec.RandomizeSMCBinary, 100_000); + + var dummyCode = Prepare.EvmCode + .STOP() + .Done; + var dummyCodeHashcode = Keccak.Compute(dummyCode); + + state.InsertCode(finalSpec.BlockSignerContract, dummyCodeHashcode, dummyCode, genesisSpec!, true); + state.InsertCode(finalSpec.RandomizeSMCBinary, dummyCodeHashcode, dummyCode, genesisSpec!, true); + + XdcBlockHeaderBuilder xdcBlockHeaderBuilder = new(); + + var genesisBlock = new Block(xdcBlockHeaderBuilder + .WithValidators(finalSpec.GenesisMasterNodes) + .WithNumber(finalSpec.SwitchBlock) + .WithGasUsed(0) + .TestObject); + + foreach (IGenesisPostProcessor genesisPostProcessor in postProcessors) + { + genesisPostProcessor.PostProcess(genesisBlock); + } + + state.Commit(specProvider.GenesisSpec!); + state.CommitTree(0); + genesisBlock.Header.StateRoot = state.StateRoot; + genesisBlock.Header.Hash = genesisBlock.Header.CalculateHash(); + 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)); + } + + public void StartHotStuffModule() + { + if (!_useHotStuffModule) + throw new InvalidOperationException("Must be using HotStuff module"); + BlockProducerRunner.Start(); + } + + public Task StopHotStuffModule() + { + if (!_useHotStuffModule) + throw new InvalidOperationException("Must be using HotStuff module"); + return BlockProducerRunner.StopAsync(); + } + + public async Task AddBlocks(int count, bool withTransaction = false) + { + UInt256 nonce = 0; + + for (var i = 0; i < count; i++) + { + if (withTransaction) + await AddBlock(CreateTransactionBuilder().WithNonce(nonce++).TestObject); + else + await AddBlock(); + } + } + + + 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); + CreateAndCommitQC((XdcBlockHeader)b.Header); + + return b; + } + + public async Task AddBlockWithoutCommitQc(params Transaction[] txs) + { + await base.AddBlock(txs); + return BlockTree.Head!; + } + + public async Task TriggerAndSimulateBlockProposalAndVoting() + { + await TriggerBlockProposal(); + await SimulateVoting(); + } + + public async Task SimulateVoting() + { + if (!_useHotStuffModule) + throw new InvalidOperationException($"Can only be used when using the {nameof(XdcHotStuff)} module"); + var head = (XdcBlockHeader)BlockTree.Head!.Header; + var spec = SpecProvider.GetXdcSpec(head, XdcContext.CurrentRound); + var leader = ConsensusModule.GetLeaderAddress(head, XdcContext.CurrentRound, spec); + + EpochSwitchInfo epochSwitchInfo = EpochSwitchManager.GetEpochSwitchInfo(head)!; + long epochSwitchNumber = epochSwitchInfo.EpochSwitchBlockInfo.BlockNumber; + long gapNumber = epochSwitchNumber == 0 ? 0 : Math.Max(0, epochSwitchNumber - epochSwitchNumber % spec.EpochLength - spec.Gap); + + VoteDecoder voteDecoder = new VoteDecoder(); + + var newHeadWaitHandle = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var newRoundWaitHandle = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + XdcContext.NewRoundSetEvent += OnNewRound; + try + { + //by setting the correct signer the block producer runner should trigger trying to propose a block + Signer.SetSigner(MasterNodeCandidates.First(k => k.Address == leader)); + int count = 0; + //Simulate voting until a new head is detected + while (!newRoundWaitHandle.Task.IsCompleted) + { + count++; + if (count > 300) + { + break; + } + //Will cast a random master candidate vote for the head block and when vote threshold is reached the block should be proposed + var vote = new Vote(new BlockRoundInfo(head.Hash!, head.ExtraConsensusData?.BlockRound ?? XdcContext.CurrentRound, head.Number), (ulong)gapNumber); + SignRandom(vote); + var voteTask = this.VotesManager.OnReceiveVote(vote); + } + //Voting will trigger QC creation which triggers new round + var finishedTask = await Task.WhenAny(newRoundWaitHandle.Task, Task.Delay(5_000)); + if (finishedTask != newRoundWaitHandle.Task) + Assert.Fail("After 300 votes no new head could be detected. Something is wrong."); + } + finally + { + XdcContext.NewRoundSetEvent -= OnNewRound; + } + + void OnNewRound(object? sender, NewRoundEventArgs e) + { + newRoundWaitHandle.SetResult(); + } + void SignRandom(Vote vote) + { + KeccakRlpStream stream = new(); + voteDecoder.Encode(stream, vote, RlpBehaviors.ForSealing); + vote.Signature = RandomSigner.Sign(stream.GetValueHash()); + vote.Signer = RandomSigner.Address; + } + } + + public async Task TriggerBlockProposal() + { + if (!_useHotStuffModule) + throw new InvalidOperationException("Requires HotStuff module"); + var head = (XdcBlockHeader)BlockTree.Head!.Header; + var spec = SpecProvider.GetXdcSpec(head, XdcContext.CurrentRound); + + var newHeadWaitHandle = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + BlockTree.NewHeadBlock += OnNewHead; + try + { + //by setting the correct signer the block producer runner should trigger trying to propose a block + var leader = ConsensusModule.GetLeaderAddress(head, XdcContext.CurrentRound, spec); + Signer.SetSigner(MasterNodeCandidates.First(k => k.Address == leader)); + + var waitingForHead = await Task.WhenAny(newHeadWaitHandle.Task, Task.Delay(10_000)); + if (waitingForHead != newHeadWaitHandle.Task) + Assert.Fail("Timed out waiting for new head after setting leader as signer."); + } + finally + { + BlockTree.NewHeadBlock -= OnNewHead; + } + + void OnNewHead(object? sender, BlockEventArgs e) + { + newHeadWaitHandle.SetResult(); + } + } + + public void CreateAndCommitQC(XdcBlockHeader header) + { + var headSpec = SpecProvider.GetXdcSpec(header, XdcContext.CurrentRound); + EpochSwitchInfo switchInfo = EpochSwitchManager.GetEpochSwitchInfo(header.Hash!)!; + + var gap = (ulong)Math.Max(0, switchInfo.EpochSwitchBlockInfo.BlockNumber - switchInfo.EpochSwitchBlockInfo.BlockNumber % headSpec.EpochLength - headSpec.Gap); + PrivateKey[] masterNodes = TakeRandomMasterNodes(headSpec, switchInfo); + var headQc = XdcTestHelper.CreateQc(new BlockRoundInfo(header.Hash!, header.ExtraConsensusData?.BlockRound ?? XdcContext.CurrentRound, header.Number), gap, + masterNodes); + QuorumCertificateManager.CommitCertificate(headQc); + } + + public PrivateKey[] TakeRandomMasterNodes(IXdcReleaseSpec headSpec, EpochSwitchInfo switchInfo) + { + return switchInfo + .Masternodes + .OrderBy(x => _random.Next()) + .Take((int)(Math.Ceiling(switchInfo.Masternodes.Length * headSpec.CertThreshold))) + .Select(a => MasterNodeCandidates.First(c => a == c.Address)) + .ToArray(); + } + + private TransactionBuilder CreateTransactionBuilder() + { + TransactionBuilder txBuilder = BuildSimpleTransaction; + + Block? head = BlockFinder.Head; + if (head is not null) + { + IReleaseSpec headReleaseSpec = SpecProvider.GetSpec(head.Header); + + if (headReleaseSpec.IsEip1559Enabled && headReleaseSpec.Eip1559TransitionBlock <= head.Number) + { + UInt256 nextFee = headReleaseSpec.BaseFeeCalculator.Calculate(head.Header, headReleaseSpec); + txBuilder = txBuilder + .WithType(TxType.EIP1559) + .WithMaxFeePerGasIfSupports1559(nextFee * 2); + } + } + + return txBuilder; + } +} 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 000000000000..ded856ea9b0e --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestDepositContract.cs @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Evm.TransactionProcessing; +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(); + } + public Address GetCandidateOwnerDuringProcessing(ITransactionProcessor transactionProcessor, 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 new file mode 100644 index 000000000000..9b6021e10fa3 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestHelper.cs @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; +using Nethermind.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.RLP; +using Nethermind.Xdc.Types; +using System.Collections.Generic; +using System.Linq; + +namespace Nethermind.Xdc.Test; + +internal static class XdcTestHelper +{ + private static readonly EthereumEcdsa ecdsa = new EthereumEcdsa(0); + private static readonly VoteDecoder decoder = new VoteDecoder(); + + public static PrivateKey[] GeneratePrivateKeys(int count) + { + var keyBuilder = new PrivateKeyGenerator(); + return keyBuilder.Generate(count).ToArray(); + } + + public static QuorumCertificate CreateQc(BlockRoundInfo roundInfo, ulong gapNumber, PrivateKey[] keys) + { + var qcEncoder = new VoteDecoder(); + + IEnumerable signatures = CreateVoteSignatures(roundInfo, gapNumber, keys); + + return new QuorumCertificate(roundInfo, signatures.ToArray(), gapNumber); + } + + 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); + return ecdsa.Sign(k, stream.GetValueHash()); + }).ToArray(); + return signatures.ToArray(); + } + + public static Timeout BuildSignedTimeout(PrivateKey key, ulong round, ulong gap) + { + var decoder = new TimeoutDecoder(); + var timeout = new Timeout(round, signature: null, gap); + Rlp rlp = decoder.Encode(timeout, Nethermind.Serialization.Rlp.RlpBehaviors.ForSealing); + var hash = Keccak.Compute(rlp.Bytes).ValueHash256; + var signature = new EthereumEcdsa(0).Sign(key, hash); + return new Timeout(round, signature, gap) { Signer = key.Address }; + } + + public static Vote BuildSignedVote( + BlockRoundInfo info, ulong gap, PrivateKey key) + { + var vote = new Vote(info, gap); + var stream = new KeccakRlpStream(); + decoder.Encode(stream, vote, RlpBehaviors.ForSealing); + vote.Signature = ecdsa.Sign(key, stream.GetValueHash()); + vote.Signer = key.Address; + return vote; + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/HeaderVerificationTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/HeaderVerificationTests.cs new file mode 100644 index 000000000000..a228172df53b --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/HeaderVerificationTests.cs @@ -0,0 +1,327 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Autofac; +using Nethermind.Consensus; +using Nethermind.Consensus.Validators; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.RLP; +using Nethermind.Xdc.Test.Helpers; +using Nethermind.Xdc.Types; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using ISigner = Nethermind.Consensus.ISigner; + +namespace Nethermind.Xdc.Test.ModuleTests; + +internal class HeaderVerificationTests +{ + private XdcTestBlockchain xdcTestBlockchain; + private IHeaderValidator xdcHeaderValidator; + private ISigner xdcSigner; + private ExtraConsensusDataDecoder extraConsensusDataDecoder; + + [SetUp] + public async Task Setup() + { + xdcTestBlockchain = await XdcTestBlockchain.Create(); + xdcHeaderValidator = xdcTestBlockchain.Container.Resolve(); + xdcSigner = xdcTestBlockchain.Container.Resolve(); + extraConsensusDataDecoder = new(); + } + + [Test] + public void Block_With_Invalid_Qc_Fails() + { + // test case needs reverification of what actually is going on (this is only a draft for now) + + var invalidRoundBlock = GetLastHeader(false); + var invalidRoundBlockParent = (XdcBlockHeader)xdcTestBlockchain.BlockTree.FindHeader(invalidRoundBlock.ParentHash!)!; + + var proposedBlockInfo = new BlockRoundInfo(invalidRoundBlockParent.Hash!, invalidRoundBlockParent.ExtraConsensusData!.BlockRound, invalidRoundBlockParent.Number); + + 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)) + .ToList(); + + List signatures = []; + foreach (var signer in validSigners) + { + Sign(voteForSign, signer); + signatures.Add(voteForSign.Signature!); + } + + var quorumCert = new QuorumCertificate(proposedBlockInfo, signatures.ToArray(), 1); + + var extra = new ExtraFieldsV2(proposedBlockInfo.Round, quorumCert); + var extraInBytes = extraConsensusDataDecoder.Encode(extra).Bytes; + + invalidRoundBlock.ExtraData = extraInBytes; + var result = xdcHeaderValidator.Validate(invalidRoundBlock, invalidRoundBlockParent); + Assert.That(result, Is.False); + } + + [Test] + public async Task Block_With_Illegitimate_Signer_Fails() + { + var previousSigner = xdcSigner.Key; + + var coinbaseValidatorMismatchBlock = GetLastBlock(false); + var coinbaseValidatorMismatchBlockParent = xdcTestBlockchain.BlockTree.FindHeader(coinbaseValidatorMismatchBlock.ParentHash!); + + var notQualifiedSigner = TestItem.PrivateKeyA; // private key + ((Signer)xdcSigner).SetSigner(notQualifiedSigner); + await xdcTestBlockchain.SealEngine.SealBlock(coinbaseValidatorMismatchBlock, default); + + ((Signer)xdcSigner).SetSigner(previousSigner); + var result = xdcHeaderValidator.Validate(coinbaseValidatorMismatchBlock.Header, coinbaseValidatorMismatchBlockParent!); + Assert.That(result, Is.False); + } + + [Test] + public void EpochSwitchBlock_With_NotLegit_PenaltiesSet_Fails() + { + var penaltiesNotLegit = GetLastHeader(true); + var penaltiesNotLegitParent = xdcTestBlockchain.BlockTree.FindHeader(penaltiesNotLegit.ParentHash!); + penaltiesNotLegit.Penalties = [.. penaltiesNotLegit.Penalties!, .. TestItem.AddressA.Bytes]; + var result = xdcHeaderValidator.Validate(penaltiesNotLegit, penaltiesNotLegitParent!); + Assert.That(result, Is.False); + } + + [Test] + public void EpochSwitchBlock_With_NotLegit_ValidatorsSet_Fails() + { + var validatorsNotLegit = GetLastHeader(true); + var validatorsNotLegitParent = xdcTestBlockchain.BlockTree.FindHeader(validatorsNotLegit.ParentHash!); + validatorsNotLegit.Validators = [.. validatorsNotLegit.Validators!, .. TestItem.AddressA.Bytes]; + var result = xdcHeaderValidator.Validate(validatorsNotLegit, validatorsNotLegitParent!); + Assert.That(result, Is.False); + } + + [Test] + public void EpochSwitchBlock_With_Invalid_ValidatorsSet_Fails() + { + var invalidValidatorsSignerBlock = GetLastHeader(true); + var invalidValidatorsSignerBlockParent = xdcTestBlockchain.BlockTree.FindHeader(invalidValidatorsSignerBlock.ParentHash!); + invalidValidatorsSignerBlock.Validators = [123]; + var result = xdcHeaderValidator.Validate(invalidValidatorsSignerBlock, invalidValidatorsSignerBlockParent!); + Assert.That(result, Is.False); + } + + [Test] + public void EpochSwitchBlock_With_Invalid_Nonce_Fails() + { + var invalidAuthNonceBlock = GetLastHeader(true); + var invalidAuthNonceBlockParent = xdcTestBlockchain.BlockTree.FindHeader(invalidAuthNonceBlock.ParentHash!); + invalidAuthNonceBlock.Nonce = 123; + var result = xdcHeaderValidator.Validate(invalidAuthNonceBlock, invalidAuthNonceBlockParent!); + Assert.That(result, Is.False); + } + + [Test] + public void Block_Mined_TooFast_After_Parent_Fails() + { + var tooFastMinedBlock = GetLastHeader(false); + var tooFastMinedBlockParent = xdcTestBlockchain.BlockTree.FindHeader(tooFastMinedBlock.ParentHash!); + tooFastMinedBlock.Timestamp = (ulong)(tooFastMinedBlockParent!.Timestamp + 1); // mined 1 second after parent + var result = xdcHeaderValidator.Validate(tooFastMinedBlock, tooFastMinedBlockParent); + Assert.That(result, Is.False); + } + + [Test] + public void Block_With_Invalid_Difficulty_Fails() + { + var invalidDifficultyBlock = GetLastHeader(false); + var invalidDifficultyBlockParent = xdcTestBlockchain.BlockTree.FindHeader(invalidDifficultyBlock.ParentHash!); + invalidDifficultyBlock.Difficulty = 2; + var result = xdcHeaderValidator.Validate(invalidDifficultyBlock, invalidDifficultyBlockParent!); + Assert.That(result, Is.False); + } + + [Test] + public void EpochSwitchBlock_With_Empty_ValidatorSet_Fails() + { + var emptyValidatorsBlock = GetLastHeader(true); + var emptyValidatorsBlockParent = xdcTestBlockchain.BlockTree.FindHeader(emptyValidatorsBlock.ParentHash!); + emptyValidatorsBlock.Validators = []; + var result = xdcHeaderValidator.Validate(emptyValidatorsBlock, emptyValidatorsBlockParent!); + Assert.That(result, Is.False); + } + + [Test] + public void Block_With_InvalidParent_Fails() + { + var parentNotExistBlock = GetLastHeader(true); + parentNotExistBlock.ParentHash = TestItem.KeccakA; + var parentNotExistBlockParent = xdcTestBlockchain.BlockTree.FindHeader(parentNotExistBlock.ParentHash!); + Assert.Throws(() => xdcHeaderValidator.Validate(parentNotExistBlock, parentNotExistBlockParent!)); + } + + [Test] + public void NonEpochBlock_With_Penalties_Fails() + { + var invalidPenaltiesExistBlock = GetLastHeader(false); + invalidPenaltiesExistBlock.Penalties = TestItem.AddressF.Bytes; + var invalidPenaltiesExistBlockParent = xdcTestBlockchain.BlockTree.FindHeader(invalidPenaltiesExistBlock.ParentHash!); + var result = xdcHeaderValidator.Validate(invalidPenaltiesExistBlock, invalidPenaltiesExistBlockParent!); + Assert.That(result, Is.False); + } + + [Test] + public void NonEpochBlock_Invalid_ValidatorsSet_Fails() + { + var invalidValidatorsExistBlock = GetLastHeader(false); + var invalidValidatorsExistBlockParent = xdcTestBlockchain.BlockTree.FindHeader(invalidValidatorsExistBlock.ParentHash!); + invalidValidatorsExistBlock.Validators = [123]; + var result = xdcHeaderValidator.Validate(invalidValidatorsExistBlock, invalidValidatorsExistBlockParent!); + Assert.That(result, Is.False); + } + + [Test] + public void Block_With_Invalid_QcExtra_Fails() + { + var invalidQcBlock = GetLastHeader(false); + var invalidQcBlockParent = xdcTestBlockchain.BlockTree.FindHeader(invalidQcBlock.ParentHash!); + invalidQcBlock.ExtraData = [(byte)Random.Shared.Next()]; + var result = xdcHeaderValidator.Validate(invalidQcBlock, invalidQcBlockParent!); + Assert.That(result, Is.False); + } + + [Test] + public void Block_From_Future_Fails() + { + var blockFromFutureBlock = GetLastHeader(false); + var blockFromFutureBlockParent = xdcTestBlockchain.BlockTree.FindHeader(blockFromFutureBlock.ParentHash!); + blockFromFutureBlock.Timestamp = (ulong)DateTime.UtcNow.ToUnixTimeSeconds() + 10000; + var result = xdcHeaderValidator.Validate(blockFromFutureBlock, blockFromFutureBlockParent!); + Assert.That(result, Is.False); + } + + [Test] + public void Block_Lacks_ValidatorField_Fails() + { + var noValidatorBlock = GetLastHeader(false); + var noValidatorBlockParent = xdcTestBlockchain.BlockTree.FindHeader(noValidatorBlock.ParentHash!); + noValidatorBlock.Validator = []; // empty + var result = xdcHeaderValidator.Validate(noValidatorBlock, noValidatorBlockParent!); + Assert.That(result, Is.False); + } + + [Test] + public void NonEpochSwitch_Block_With_ValidatorsSet() + { + var nonEpochSwitchWithValidators = GetLastHeader(false); + nonEpochSwitchWithValidators.Validators = xdcTestBlockchain.MasterNodeCandidates.SelectMany(addr => addr.Address.Bytes).ToArray(); // implement helper to return acc1 addr bytes + var nonEpochSwitchWithValidatorsParent = xdcTestBlockchain.BlockTree.FindHeader(nonEpochSwitchWithValidators.ParentHash!); + var result = xdcHeaderValidator.Validate(nonEpochSwitchWithValidators, nonEpochSwitchWithValidatorsParent); + Assert.That(result, Is.False); + } + + [Test] + public void Valid_EpochSwitch_Block_Passes_Validation() + { + var happyPathHeader = GetLastHeader(true); + var happyPathParent = xdcTestBlockchain.BlockTree.FindHeader(happyPathHeader.ParentHash!); + var result = xdcHeaderValidator.Validate(happyPathHeader, happyPathParent); + Assert.That(result, Is.True); + } + + [Test] + public void Valid_NonEpochSwitch_Block_Passes_Validation() + { + var happyPathHeader = GetLastHeader(false); + var happyPathParent = xdcTestBlockchain.BlockTree.FindHeader(happyPathHeader.ParentHash!); + + var result = xdcHeaderValidator.Validate(happyPathHeader, happyPathParent); + Assert.That(result, Is.True); + } + + [Test] + public void Block_With_QcSignature_Below_Threshold_Fails() + { + var invalidQcSignatureBlock = GetLastHeader(false); + var invalidQcSignatureBlockParent = (XdcBlockHeader)xdcTestBlockchain.BlockTree.FindHeader(invalidQcSignatureBlock.ParentHash!)!; + 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)) + .ToList(); + List signatures = []; + + double threshold = xdcTestBlockchain.SpecProvider.GetXdcSpec(invalidQcSignatureBlock).CertThreshold; + + // Sign with only half of the valid signers to be below threshold + foreach (var signer in validSigners.Take((int)threshold - 1)) + { + Sign(voteForSign, signer); + signatures.Add(voteForSign.Signature!); + } + + var quorumCert = new QuorumCertificate(proposedBlockInfo, signatures.ToArray(), 1); + var extra = new ExtraFieldsV2(proposedBlockInfo.Round, quorumCert); + var extraInBytes = extraConsensusDataDecoder.Encode(extra).Bytes; + invalidQcSignatureBlock.ExtraData = extraInBytes; + var result = xdcHeaderValidator.Validate(invalidQcSignatureBlock, invalidQcSignatureBlockParent); + Assert.That(result, Is.False); + } + + private void Sign(Vote vote, Consensus.ISigner signer) + { + var voteEncoder = new VoteDecoder(); + KeccakRlpStream stream = new(); + voteEncoder.Encode(stream, vote, RlpBehaviors.ForSealing); + vote.Signature = signer.Sign(stream.GetValueHash()); + vote.Signer = signer.Address; + } + + private XdcBlockHeader GetLastHeader(bool isEpochSwitch) + { + if (!isEpochSwitch) + { + return ClearCacheFields((XdcBlockHeader)xdcTestBlockchain.BlockTree.Head!.Header); + } + else + { + var currentHeader = (XdcBlockHeader)xdcTestBlockchain.BlockTree.Head!.Header; + while (currentHeader is not null) + { + if (xdcTestBlockchain.EpochSwitchManager.IsEpochSwitchAtBlock(currentHeader)) + { + return ClearCacheFields(currentHeader); + } + currentHeader = (XdcBlockHeader)xdcTestBlockchain.BlockTree.FindHeader(currentHeader.ParentHash!)!; + } + + throw new InvalidOperationException("No epoch switch block found in the chain."); + } + } + + private static XdcBlockHeader ClearCacheFields(XdcBlockHeader header) + { + header.Author = null; + return header; + } + private Block GetLastBlock(bool isEpochSwitch) + { + var header = GetLastHeader(isEpochSwitch); + var block = xdcTestBlockchain.BlockTree.FindBlock(header.Hash!); + if (block is null) + { + throw new InvalidOperationException("Block not found in the chain."); + } + + return block; + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/MineModuleTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/MineModuleTests.cs new file mode 100644 index 000000000000..0244ecfd6961 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/MineModuleTests.cs @@ -0,0 +1,152 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Core; +using Nethermind.Crypto; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Test.Helpers; +using NUnit.Framework; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace Nethermind.Xdc.Test.ModuleTests; + +[Parallelizable(ParallelScope.All)] +internal class MineModuleTests +{ + public async Task<(XdcTestBlockchain, XdcBlockTree)> Setup() + { + var blockchain = await XdcTestBlockchain.Create(useHotStuffModule: true); + var tree = (XdcBlockTree)blockchain.BlockTree; + + return (blockchain, tree); + } + + [Test] + public async Task TestUpdateMultipleMasterNodes() + { + var (blockchain, tree) = await Setup(); + + // this test is basically an emulation because our block producer test setup does not support saving snapshots yet + // add blocks until the next gap block + var spec = blockchain.SpecProvider.GetXdcSpec((XdcBlockHeader)tree.Head!.Header!); + + var oldHead = (XdcBlockHeader)tree.Head!.Header!; + var snapshotBefore = blockchain.SnapshotManager.GetSnapshotByBlockNumber(oldHead.Number, blockchain.SpecProvider.GetXdcSpec((XdcBlockHeader)tree.Head!.Header!)); + + Assert.That(snapshotBefore, Is.Not.Null); + Assert.That(snapshotBefore.NextEpochCandidates.Length, Is.EqualTo(30)); + + // simulate adding a new validator + var newValidator = new PrivateKeyGenerator().Generate(); + blockchain.MasterNodeCandidates.Add(newValidator); + + // mine the gap block that should trigger master node update + var gapBlock = await blockchain.AddBlock(); + while (!ISnapshotManager.IsTimeForSnapshot(tree.Head!.Header!.Number, spec)) + { + gapBlock = await blockchain.AddBlock(); + } + + var newHead = (XdcBlockHeader)tree.Head!.Header!; + Assert.That(newHead.Number, Is.EqualTo(gapBlock.Number)); + + var snapshotAfter = blockchain.SnapshotManager.GetSnapshotByGapNumber(newHead.Number); + + Assert.That(snapshotAfter, Is.Not.Null); + Assert.That(snapshotAfter.BlockNumber, Is.EqualTo(gapBlock.Number)); + Assert.That(snapshotAfter.NextEpochCandidates.Length, Is.EqualTo(blockchain.MasterNodeCandidates.Count)); + Assert.That(snapshotAfter.NextEpochCandidates.Contains(newValidator.Address), Is.True); + } + + [Test] + public async Task TestShouldMineOncePerRound() + { + var (xdcBlockchain, tree) = await Setup(); + + var _hotstuff = (XdcHotStuff)xdcBlockchain.BlockProducerRunner; + + var blocksProposed = 0; + xdcBlockchain.ConsensusModule.BlockProduced += (sender, args) => + { + blocksProposed++; + }; + + var newRoundWaitHandle = new TaskCompletionSource(); + xdcBlockchain.XdcContext.NewRoundSetEvent += (sender, args) => + { + newRoundWaitHandle.TrySetResult(); + }; + + xdcBlockchain.StartHotStuffModule(); + + var parentBlock = tree.Head!.Header!; + var masterNodesAddresses = xdcBlockchain.MasterNodeCandidates.Select(pv => pv.Address).ToArray(); + + var spec = xdcBlockchain.SpecProvider.GetXdcSpec((XdcBlockHeader)tree.Head!.Header!); + + await xdcBlockchain.TriggerAndSimulateBlockProposalAndVoting(); + + Task firstTask = await Task.WhenAny(newRoundWaitHandle.Task, Task.Delay(5_000)); + if (firstTask != newRoundWaitHandle.Task) + { + Assert.Fail("Timeout waiting for new round event"); + } + blocksProposed.Should().Be(1); + } + + [Test] + public async Task TestUpdateMasterNodes() + { + var (blockchain, tree) = await Setup(); + + blockchain.ChangeReleaseSpec((spec) => + { + spec.EpochLength = 90; + spec.Gap = 45; + }); + + IXdcReleaseSpec? spec = blockchain.SpecProvider.GetXdcSpec((XdcBlockHeader)tree.Head!.Header); + + var header = (XdcBlockHeader)blockchain.BlockTree.Head!.Header!; + spec = blockchain.SpecProvider.GetXdcSpec(header); + var snapshot = blockchain.SnapshotManager.GetSnapshotByBlockNumber(header.Number, spec); + + Assert.That(snapshot, Is.Not.Null); + Assert.That(snapshot.BlockNumber, Is.EqualTo(0)); + Assert.That(snapshot.NextEpochCandidates.Length, Is.EqualTo(30)); + + var gapBlock = await blockchain.AddBlock(); + while (!ISnapshotManager.IsTimeForSnapshot(tree.Head!.Header!.Number, spec)) + { + gapBlock = await blockchain.AddBlock(); + } + + Assert.That(gapBlock.Number, Is.EqualTo(spec.Gap)); + + header = (XdcBlockHeader)gapBlock.Header!; + spec = blockchain.SpecProvider.GetXdcSpec(header); + 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)); + + var epochSwitchBlock = await blockchain.AddBlock(); + while (!blockchain.EpochSwitchManager.IsEpochSwitchAtBlock((XdcBlockHeader)tree.Head!.Header!)) + { + epochSwitchBlock = await blockchain.AddBlock(); + } + + Assert.That(epochSwitchBlock.Number, Is.EqualTo(spec.EpochLength)); + + header = (XdcBlockHeader)epochSwitchBlock.Header!; + // --- Validate header fields + int validatorCount = header.Validators!.Length / Address.Size; + int penaltyCount = header.Penalties!.Length / Address.Size; + + Assert.That(validatorCount, Is.EqualTo(spec.MaxMasternodes)); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs new file mode 100644 index 000000000000..277fc1c5d9b3 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs @@ -0,0 +1,208 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Xdc.Test.Helpers; +using Nethermind.Xdc.Types; +using NUnit.Framework; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace Nethermind.Xdc.Test; + +internal class ProposedBlockTests +{ + [Test] + public async Task TestShouldSendVoteMsgAndCommitGreatGrandparentBlockAsync() + { + var blockChain = await XdcTestBlockchain.Create(2, true); + + await blockChain.AddBlockWithoutCommitQc(); + + var head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header; + var spec = blockChain.SpecProvider.GetXdcSpec(head, blockChain.XdcContext.CurrentRound); + + EpochSwitchInfo switchInfo = blockChain.EpochSwitchManager.GetEpochSwitchInfo(head)!; + PrivateKey[] masternodes = blockChain.TakeRandomMasterNodes(spec, switchInfo); + if (masternodes.Any((m) => m.Address == head.Beneficiary)) + { + //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]; + } + + BlockRoundInfo votingBlock = new BlockRoundInfo(head.Hash!, blockChain.XdcContext.CurrentRound, head.Number); + long gapNumber = switchInfo.EpochSwitchBlockInfo.BlockNumber == 0 ? 0 : Math.Max(0, switchInfo.EpochSwitchBlockInfo.BlockNumber - switchInfo.EpochSwitchBlockInfo.BlockNumber % spec.EpochLength - spec.Gap); + //We skip 1 vote so we are 1 under the vote threshold, proving that if the round advances the module cast a vote itself + foreach (var key in masternodes.Skip(1)) + { + var vote = XdcTestHelper.BuildSignedVote(votingBlock, (ulong)gapNumber, key); + await blockChain.VotesManager.HandleVote(vote); + } + + var newRoundWaitHandle = new TaskCompletionSource(); + blockChain.XdcContext.NewRoundSetEvent += (s, a) => { newRoundWaitHandle.SetResult(); }; + + //Starting here will trigger the final vote to be cast and round should advance + blockChain.StartHotStuffModule(); + + var waitTask = await Task.WhenAny(newRoundWaitHandle.Task, Task.Delay(5_000)); + if (waitTask != newRoundWaitHandle.Task) + { + Assert.Fail("Timed out waiting for the round to start. The vote threshold was not reached?"); + } + + var parentOfHead = blockChain.BlockTree.FindHeader(head.ParentHash!); + var grandParentOfHead = blockChain.BlockTree.FindHeader(parentOfHead!.ParentHash!); + + grandParentOfHead!.Hash!.Should().Be(blockChain.XdcContext.HighestCommitBlock.Hash); + } + + [Test] + public async Task TestShouldNotCommitIfRoundsNotContinousFor3Rounds() + { + var blockChain = await XdcTestBlockchain.Create(2, true); + + await blockChain.AddBlock(); + + blockChain.StartHotStuffModule(); + + await blockChain.TriggerBlockProposal(); + + await blockChain.SimulateVoting(); + + var beforeTimeoutFinalized = blockChain.XdcContext.HighestCommitBlock; + var beforeTimeoutQC = blockChain.XdcContext.HighestQC; + + //Simulate timeout + blockChain.XdcContext.SetNewRound(); + + await blockChain.TriggerBlockProposal(); + + await blockChain.SimulateVoting(); + + blockChain.XdcContext.HighestCommitBlock.Should().Be(beforeTimeoutFinalized); + blockChain.XdcContext.HighestQC.Should().NotBe(beforeTimeoutQC); + } + + [Test] + public async Task TestProposedBlockMessageHandlerSuccessfullyGenerateVote() + { + var blockChain = await XdcTestBlockchain.Create(2, true); + + await blockChain.AddBlockWithoutCommitQc(); + + var head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header; + var spec = blockChain.SpecProvider.GetXdcSpec(head, blockChain.XdcContext.CurrentRound); + + EpochSwitchInfo switchInfo = blockChain.EpochSwitchManager.GetEpochSwitchInfo(head)!; + PrivateKey[] masternodes = blockChain.TakeRandomMasterNodes(spec, switchInfo); + if (masternodes.Any((m) => m.Address == head.Beneficiary)) + { + //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]; + } + + BlockRoundInfo votingBlock = new BlockRoundInfo(head.Hash!, blockChain.XdcContext.CurrentRound, head.Number); + long gapNumber = switchInfo.EpochSwitchBlockInfo.BlockNumber == 0 ? 0 : Math.Max(0, switchInfo.EpochSwitchBlockInfo.BlockNumber - switchInfo.EpochSwitchBlockInfo.BlockNumber % spec.EpochLength - spec.Gap); + //We skip 1 vote so we are 1 under the vote threshold + foreach (var key in masternodes.Skip(1)) + { + var vote = XdcTestHelper.BuildSignedVote(votingBlock, (ulong)gapNumber, key); + await blockChain.VotesManager.HandleVote(vote); + } + + var beforeFinalVote = blockChain.XdcContext.HighestQC!; + //Our highest QC should be 1 number behind head + beforeFinalVote.ProposedBlockInfo.BlockNumber.Should().Be(head.Number - 1); + + var newRoundWaitHandle = new TaskCompletionSource(); + blockChain.XdcContext.NewRoundSetEvent += (s, a) => { newRoundWaitHandle.SetResult(); }; + + //Starting here will trigger the final vote to be cast + blockChain.StartHotStuffModule(); + + var waitTask = await Task.WhenAny(newRoundWaitHandle.Task, Task.Delay(5_000)); + if (waitTask != newRoundWaitHandle.Task) + { + Assert.Fail("Timed out waiting for the round to start. The vote threshold was not reached?"); + } + + blockChain.XdcContext.HighestQC!.ProposedBlockInfo.Hash.Should().Be(head.Hash!); + } + + [TestCase(1)] + [TestCase(10)] + [TestCase(30)] + public async Task CanBuildAFinalizedChain(int count) + { + var blockChain = await XdcTestBlockchain.Create(0, true); + blockChain.ChangeReleaseSpec((s) => + { + s.EpochLength = 90; + s.Gap = 45; + }); + + await blockChain.AddBlocks(3); + + blockChain.StartHotStuffModule(); + + var startBlock = blockChain.BlockTree.Head!.Header; + + for (int i = 1; i <= count; i++) + { + await blockChain.TriggerAndSimulateBlockProposalAndVoting(); + blockChain.BlockTree.Head.Number.Should().Be(startBlock.Number + i); + blockChain.XdcContext.HighestQC!.ProposedBlockInfo.BlockNumber.Should().Be(startBlock.Number + i); + blockChain.XdcContext.HighestCommitBlock.BlockNumber.Should().Be(blockChain.XdcContext.HighestQC!.ProposedBlockInfo.BlockNumber - 2); + } + } + + [Test] + public async Task TestProposedBlockMessageHandlerNotGenerateVoteIfSignerNotInMNlist() + { + var blockChain = await XdcTestBlockchain.Create(2, true); + + await blockChain.AddBlockWithoutCommitQc(); + + var head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header; + var spec = blockChain.SpecProvider.GetXdcSpec(head, blockChain.XdcContext.CurrentRound); + + EpochSwitchInfo switchInfo = blockChain.EpochSwitchManager.GetEpochSwitchInfo(head)!; + PrivateKey[] masternodes = blockChain.TakeRandomMasterNodes(spec, switchInfo); + if (masternodes.Any((m) => m.Address == head.Beneficiary)) + { + //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]; + } + + BlockRoundInfo votingBlock = new BlockRoundInfo(head.Hash!, blockChain.XdcContext.CurrentRound, head.Number); + long gapNumber = switchInfo.EpochSwitchBlockInfo.BlockNumber == 0 ? 0 : Math.Max(0, switchInfo.EpochSwitchBlockInfo.BlockNumber - switchInfo.EpochSwitchBlockInfo.BlockNumber % spec.EpochLength - spec.Gap); + //We skip 1 vote so we are 1 under the vote threshold + foreach (var key in masternodes.Skip(1)) + { + var vote = XdcTestHelper.BuildSignedVote(votingBlock, (ulong)gapNumber, key); + await blockChain.VotesManager.HandleVote(vote); + } + + //Setting the signer to a non master node + blockChain.Signer.SetSigner(TestItem.PrivateKeyA); + + var roundCountBeforeStart = blockChain.XdcContext.CurrentRound; + + //Should not cause any new vote to be cast + blockChain.StartHotStuffModule(); + + await Task.Delay(100); + + blockChain.XdcContext.CurrentRound.Should().Be(roundCountBeforeStart); + } +} 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 000000000000..becec46623a9 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/RewardTests.cs @@ -0,0 +1,421 @@ +// 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; + +[NonParallelizable] +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(); + + chain.ChangeReleaseSpec(spec => + { + spec.EpochLength = 50; + spec.MergeSignRange = 5; + spec.MergeSignRange = System.Math.Min(spec.MergeSignRange, spec.EpochLength / 2); + if (spec.MergeSignRange < 1) spec.MergeSignRange = 1; + }); + + masternodeVotingContract + .GetCandidateOwner(Arg.Any(), Arg.Any
()) + .Returns(ci => ci.ArgAt
(1)); + + var rewardCalculator = 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); + + long epochLength = spec.EpochLength; + long mergeSignRange = spec.MergeSignRange; + + long initialHeadNumber = chain.BlockTree.Head!.Number; + + // --- Part 1: create signing tx for header (E + mergeSignRange) included in next block --- + + long targetSignedHeaderNumberEPlusMerge = epochLength + mergeSignRange; + await chain.AddBlocks((int)(targetSignedHeaderNumberEPlusMerge - initialHeadNumber)); + + var signedHeaderEPlusMerge = chain.BlockTree.Head!.Header as XdcBlockHeader; + Assert.That(signedHeaderEPlusMerge, Is.Not.Null); + + // Note: SubmitTransactionSign signs with DI `ISigner`, not necessarily `chain.Signer` + await chain.AddBlock(BuildSigningTx( + spec, + signedHeaderEPlusMerge.Number, + signedHeaderEPlusMerge.Hash ?? signedHeaderEPlusMerge.CalculateHash().ToHash256(), + chain.Signer.Key!, + (long)chain.ReadOnlyState.GetNonce(chain.Signer.Address))); + + // --- Move to header (3E - mergeSignRange) to sign it later in Part 2 --- + + long blockNumberAfterIncludingSignTx = chain.BlockTree.Head!.Number; + long targetSignedHeader3EMinusMerge = 3 * epochLength - mergeSignRange; + await chain.AddBlocks((int)(targetSignedHeader3EMinusMerge - blockNumberAfterIncludingSignTx)); + + var signedHeader3EMinusMerge = chain.BlockTree.Head!.Header as XdcBlockHeader; + Assert.That(signedHeader3EMinusMerge, Is.Not.Null); + + // --- Evaluate rewards at checkpoint (3E) --- + + long headBeforeCheckpoint = chain.BlockTree.Head!.Number; + long checkpoint3E = 3 * epochLength; + await chain.AddBlocks((int)(checkpoint3E - headBeforeCheckpoint)); + + Block block3E = chain.BlockTree.Head!; + var header3E = block3E.Header as XdcBlockHeader; + Assert.That(header3E, Is.Not.Null); + + BlockReward[] rewardsAt3E = rewardCalculator.CalculateRewards(block3E); + + Address foundation = spec.FoundationWallet; + foundation.Should().NotBeNull(); + + Assert.That(rewardsAt3E, Has.Length.EqualTo(2)); + + UInt256 totalAt3E = rewardsAt3E.Aggregate(UInt256.Zero, (acc, r) => acc + r.Value); + UInt256 foundationRewardAt3E = rewardsAt3E.Single(r => r.Address == foundation).Value; + UInt256 ownerRewardAt3E = rewardsAt3E.Single(r => r.Address != foundation).Value; + + // Check 90/10 split on totals + Assert.That(foundationRewardAt3E, Is.EqualTo(totalAt3E / 10)); + Assert.That(ownerRewardAt3E, Is.EqualTo(totalAt3E * 90 / 100)); + + // === Part 2: signing hash in a different epoch still counts === + + // Place signing tx for the previously captured (3E - mergeSignRange) header in block (3E + mergeSignRange + 1) + long targetIncludingBlockForSecondSign = 3 * epochLength + mergeSignRange + 1; + long current = chain.BlockTree.Head!.Number; + await chain.AddBlocks((int)(targetIncludingBlockForSecondSign - current - 1)); // move so AddBlockMayHaveExtraTx produces the target + + // For 4E reward calculation, the masternodes come from the second epoch switch found + // when walking backwards from 4E. The signed header (3E - mergeSignRange) is in the + // range [2E+1, 3E), so its epoch switch info provides the relevant masternodes. + // Use a masternode from that epoch to ensure the signature is counted. + EpochSwitchInfo? epochSwitchInfoFor2E = chain.EpochSwitchManager.GetEpochSwitchInfo(signedHeader3EMinusMerge); + Assert.That(epochSwitchInfoFor2E, Is.Not.Null); + PrivateKey signerForPart2 = chain.MasterNodeCandidates.First(k => k.Address == epochSwitchInfoFor2E!.Masternodes[0]); + + // Set the chain's signer to our chosen masternode - required because + // SignTransactionFilter rejects signing txs from non-current-signers + chain.Signer.SetSigner(signerForPart2); + + await chain.AddBlock(BuildSigningTx( + spec, + signedHeader3EMinusMerge.Number, + signedHeader3EMinusMerge.Hash ?? signedHeader3EMinusMerge.CalculateHash().ToHash256(), + signerForPart2, + (long)chain.ReadOnlyState.GetNonce(signerForPart2.Address))); + + // --- Evaluate rewards at checkpoint (4E) --- + long checkpoint4E = 4 * epochLength; + current = chain.BlockTree.Head!.Number; + await chain.AddBlocks((int)(checkpoint4E - current)); + + Block block4E = chain.BlockTree.Head!; + var header4E = block4E.Header as XdcBlockHeader; + Assert.That(header4E, Is.Not.Null); + + BlockReward[] rewardsAt4E = rewardCalculator.CalculateRewards(block4E); + Assert.That(rewardsAt4E, Has.Length.EqualTo(2)); + + UInt256 totalAt4E = rewardsAt4E.Aggregate(UInt256.Zero, (acc, r) => acc + r.Value); + UInt256 foundationRewardAt4E = rewardsAt4E.Single(r => r.Address == foundation).Value; + UInt256 ownerRewardAt4E = rewardsAt4E.Single(r => r.Address != foundation).Value; + + Assert.That(foundationRewardAt4E, Is.EqualTo(totalAt4E / 10)); + Assert.That(ownerRewardAt4E, Is.EqualTo(totalAt4E * 90 / 100)); + + // === Part 3: if no signing tx, reward should be empty === + + long checkpoint5E = 5 * epochLength; + current = chain.BlockTree.Head!.Number; + await chain.AddBlocks((int)(checkpoint5E - current)); + + Block block5E = chain.BlockTree.Head!; + BlockReward[] rewardsAt5E = rewardCalculator.CalculateRewards(block5E); + rewardsAt5E.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(); + masternodeVotingContract + .GetCandidateOwner(Arg.Any(), Arg.Any
()) + .Returns(ci => ci.ArgAt
(1)); + + var rewardCalculator = 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); + + long epochLength = spec.EpochLength; + long mergeSignRange = spec.MergeSignRange; + + // - Insert 1 signing tx for header (E + mergeSignRange) signed by signerA into block (E + mergeSignRange + 1) + // - Insert 2 signing txs (one for header (E + mergeSignRange), one for header (2E - mergeSignRange)) signed by signerB into block (2E - 1) + // - Verify: rewards at (3E) split 1:2 between A:B with 90/10 owner/foundation + + long initialHeadNumber = chain.BlockTree.Head!.Number; + long targetHeaderEPlusMerge = epochLength + mergeSignRange; + await chain.AddBlocks((int)(targetHeaderEPlusMerge - initialHeadNumber)); + + var headerEPlusMerge = (XdcBlockHeader)chain.BlockTree.Head!.Header; + + EpochSwitchInfo? epochInfoAtEPlusMerge = chain.EpochSwitchManager.GetEpochSwitchInfo(headerEPlusMerge); + Assert.That(epochInfoAtEPlusMerge, Is.Not.Null); + _ = chain.TakeRandomMasterNodes(spec, epochInfoAtEPlusMerge); + + PrivateKey signerA = chain.Signer.Key!; + Address ownerA = signerA.Address; + + // Insert 1 signing tx for header (E + mergeSignRange) in block (E + mergeSignRange + 1) + Transaction txA = BuildSigningTx( + spec, + headerEPlusMerge.Number, + headerEPlusMerge.Hash ?? headerEPlusMerge.CalculateHash().ToHash256(), + signerA); + + await chain.AddBlock(txA); // advances by 1 + + long targetHeader2EMinusMerge = 2 * epochLength - mergeSignRange; + long currentHeadNumber = chain.BlockTree.Head!.Number; + await chain.AddBlocks((int)(targetHeader2EMinusMerge - currentHeadNumber)); + + var header2EMinusMerge = (XdcBlockHeader)chain.BlockTree.Head!.Header; + + // Create a block (2E - 1) containing 2 signing txs from signerB. + // Move to (2E - 2) so that AddBlock puts us at (2E - 1). + long targetBlock2EMinus1 = 2 * epochLength - 1; + long targetBlock2EMinus2 = targetBlock2EMinus1 - 1; + + currentHeadNumber = chain.BlockTree.Head!.Number; + await chain.AddBlocks((int)(targetBlock2EMinus2 - currentHeadNumber)); + + PrivateKey signerB = chain.Signer.Key!; + Address ownerB = signerB.Address; + + Assert.That(ownerA, Is.Not.EqualTo(ownerB)); + + Transaction txBForEPlusMerge = BuildSigningTx( + spec, + headerEPlusMerge.Number, + headerEPlusMerge.Hash!, + signerB); + + Transaction txBFor2EMinusMerge = BuildSigningTx( + spec, + header2EMinusMerge.Number, + header2EMinusMerge.Hash!, + signerB, + nonce: 1); + + await chain.AddBlock(txBForEPlusMerge, txBFor2EMinusMerge); // now at (2E - 1) + + long checkpoint3E = 3 * epochLength; + currentHeadNumber = chain.BlockTree.Head!.Number; + await chain.AddBlocks((int)(checkpoint3E - currentHeadNumber)); + + Block block3E = chain.BlockTree.Head!; + BlockReward[] rewards = rewardCalculator.CalculateRewards(block3E); + + Address foundation = spec.FoundationWallet; + Assert.That(rewards.Length, Is.EqualTo(3)); + + UInt256 totalRewards = (UInt256)spec.Reward * Unit.Ether; + + UInt256 signerAReward = totalRewards / 3; + UInt256 ownerAReward = signerAReward * 90 / 100; + UInt256 foundationReward = signerAReward / 10; + UInt256 signerBReward = totalRewards / 3 * 2; + 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 epochLength = 45; + const long totalRewardInEther = 250; + + const long mergeSignRange = 15; + const long firstSignedBlockNumber = mergeSignRange; + const long secondSignedBlockNumber = 2 * mergeSignRange; + const long firstIncludedTxBlockNumber = firstSignedBlockNumber + 1; + const long secondIncludedTxBlockNumber = secondSignedBlockNumber + 1; + + 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 % epochLength == 0); + + IXdcReleaseSpec xdcSpec = Substitute.For(); + xdcSpec.EpochLength.Returns((int)epochLength); + xdcSpec.FoundationWallet.Returns(foundationWalletAddr); + xdcSpec.BlockSignerContract.Returns(blockSignerContract); + xdcSpec.Reward.Returns(totalRewardInEther); + xdcSpec.SwitchBlock.Returns(0); + xdcSpec.MergeSignRange = mergeSignRange; + + ISpecProvider specProvider = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(xdcSpec); + + IBlockTree tree = Substitute.For(); + long chainSize = 2 * epochLength + 1; + + var blockHeaders = new XdcBlockHeader[chainSize]; + var blocks = new Block[chainSize]; + for (int i = 0; i <= epochLength * 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 `mergeSignRange` and `2*mergeSignRange` + // SignerB signs block `mergeSignRange` + var txsAtFirstIncludedBlock = new List + { + BuildSigningTx(xdcSpec, firstSignedBlockNumber, blockHeaders[firstSignedBlockNumber].Hash!, signerA, nonce: 1), + BuildSigningTx(xdcSpec, firstSignedBlockNumber, blockHeaders[firstSignedBlockNumber].Hash!, signerB, nonce: 2), + }; + + var txsAtSecondIncludedBlock = new List + { + BuildSigningTx(xdcSpec, secondSignedBlockNumber, blockHeaders[secondSignedBlockNumber].Hash!, signerA, nonce: 3), + }; + + blocks[firstIncludedTxBlockNumber] = new Block(blockHeaders[firstIncludedTxBlockNumber], new BlockBody(txsAtFirstIncludedBlock.ToArray(), null, null)); + blocks[secondIncludedTxBlockNumber] = new Block(blockHeaders[secondIncludedTxBlockNumber], new BlockBody(txsAtSecondIncludedBlock.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.ArgAt
(1)); + + var rewardCalculator = new XdcRewardCalculator(epochSwitchManager, specProvider, tree, votingContract); + BlockReward[] rewards = rewardCalculator.CalculateRewards(blocks.Last()); + + Assert.That(rewards, Has.Length.EqualTo(3)); + + 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)); + } + + [Test] + public void RewardCalculator_CalculateRewardsForSignersAndHolders_MatchesExpectedValues() + { + IMasternodeVotingContract masternodeVotingContract = Substitute.For(); + var rewardCalculator = new XdcRewardCalculator( + Substitute.For(), + Substitute.For(), + Substitute.For(), + masternodeVotingContract + ); + + var totalReward = UInt256.Parse("171000000000000000000"); + long totalSigner = 177, sign = 59; + var expectedReward = UInt256.Parse("56999999999999999983"); + + Assert.That(rewardCalculator.CalculateProportionalReward(sign, totalSigner, totalReward), Is.EqualTo(expectedReward)); + + var expectedAmountOwner = UInt256.Parse(("51299999999999999984")); + var expectedAmountFoundationWallet = UInt256.Parse(("5699999999999999998")); + bool ok = Address.TryParse("0x68d1e2F85e4583BeCc610b47Dd1b857850a4025A", out Address? signer); + Assert.That(ok, Is.True); + XdcBlockHeader header = Build.A.XdcBlockHeader().TestObject; + masternodeVotingContract.GetCandidateOwner(header, signer!).Returns(signer!); + (BlockReward holderReward, UInt256 foundationWalletReward) = rewardCalculator.DistributeRewards(signer!, expectedReward, header); + + Assert.That(holderReward.Value, Is.EqualTo(expectedAmountOwner)); + Assert.That(foundationWalletReward, Is.EqualTo(expectedAmountFoundationWallet)); + } + + + private static Transaction BuildSigningTx(IXdcReleaseSpec spec, long blockNumber, Hash256 blockHash, PrivateKey signer, long nonce = 0) + { + // These are protocol constants (not "test magic numbers"): + const int signingTxGasLimit = 200_000; + const int chainId = 0; + + return Build.A.Transaction + .WithChainId(chainId) + .WithNonce((UInt256)nonce) + .WithGasLimit(signingTxGasLimit) + .WithXdcSigningData(blockNumber, blockHash) + .ToBlockSignerContract(spec) + .SignedAndResolved(signer) + .TestObject; + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/SyncInfoDecoderTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/SyncInfoDecoderTests.cs new file mode 100644 index 000000000000..db199e8b09e4 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/SyncInfoDecoderTests.cs @@ -0,0 +1,175 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.RLP; +using Nethermind.Xdc.Types; +using NUnit.Framework; +using System.Collections; + +namespace Nethermind.Xdc.Test; + +[TestFixture, Parallelizable(ParallelScope.All)] +public class SyncInfoDecoderTests +{ + public static IEnumerable SyncInfoCases + { + get + { + yield return new TestCaseData( + new SyncInfo( + new QuorumCertificate( + new BlockRoundInfo(Hash256.Zero, 1, 1), + [new Signature(new byte[64], 0), new Signature(new byte[64], 0)], + 0 + ), + new TimeoutCertificate( + 1, + [new Signature(new byte[64], 0), new Signature(new byte[64], 0)], + 0 + ) + ), + true + ); + + yield return new TestCaseData( + new SyncInfo( + new QuorumCertificate( + new BlockRoundInfo(Hash256.Zero, 1, 1), + [], + 0 + ), + new TimeoutCertificate(1, [], 0) + ), + false + ); + + yield return new TestCaseData( + new SyncInfo( + new QuorumCertificate( + new BlockRoundInfo(Hash256.Zero, ulong.MaxValue, long.MaxValue), + [], + ulong.MaxValue + ), + new TimeoutCertificate(ulong.MaxValue, [], ulong.MaxValue) + ), + true + ); + } + } + + [TestCaseSource(nameof(SyncInfoCases))] + public void EncodeDecode_RoundTrip_Matches_AllFields(SyncInfo syncInfo, bool useRlpStream) + { + var decoder = new SyncInfoDecoder(); + + Rlp encoded = decoder.Encode(syncInfo); + var stream = new RlpStream(encoded.Bytes); + SyncInfo decoded; + + if (useRlpStream) + { + decoded = decoder.Decode(stream); + } + else + { + Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(stream.Data.AsSpan()); + decoded = decoder.Decode(ref decoderContext); + } + + decoded.Should().BeEquivalentTo(syncInfo); + } + + [Test] + public void Encode_UseBothRlpStreamAndValueDecoderContext_IsEquivalentAfterReencoding() + { + SyncInfo syncInfo = new( + new QuorumCertificate( + new BlockRoundInfo(Hash256.Zero, 1, 1), + [new Signature(new byte[64], 0), new Signature(new byte[64], 0), new Signature(new byte[64], 0)], + 0 + ), + new TimeoutCertificate( + 1, + [new Signature(new byte[64], 0), new Signature(new byte[64], 0)], + 0 + ) + ); + + SyncInfoDecoder decoder = new(); + RlpStream stream = new RlpStream(decoder.GetLength(syncInfo)); + decoder.Encode(stream, syncInfo); + stream.Position = 0; + + // Decode with RlpStream + SyncInfo decodedStream = decoder.Decode(stream); + stream.Position = 0; + + // Decode with ValueDecoderContext + Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(stream.Data.AsSpan()); + SyncInfo decodedContext = decoder.Decode(ref decoderContext); + + // Both should be equivalent to original + decodedStream.Should().BeEquivalentTo(syncInfo); + decodedContext.Should().BeEquivalentTo(syncInfo); + decodedStream.Should().BeEquivalentTo(decodedContext); + } + + [Test] + public void TotalLength_Equals_GetLength() + { + SyncInfo syncInfo = new( + new QuorumCertificate( + new BlockRoundInfo(Hash256.Zero, 42, 42), + [new Signature(new byte[64], 0)], + 10 + ), + new TimeoutCertificate( + 41, + [new Signature(new byte[64], 1)], + 10 + ) + ); + + var decoder = new SyncInfoDecoder(); + Rlp encoded = decoder.Encode(syncInfo); + + int expectedTotal = decoder.GetLength(syncInfo, RlpBehaviors.None); + Assert.That(encoded.Bytes.Length, Is.EqualTo(expectedTotal), + "Encoded total length should match GetLength()."); + } + + [Test] + public void Encode_Null_ReturnsEmptySequence() + { + var decoder = new SyncInfoDecoder(); + + Rlp encoded = decoder.Encode(null!); + + Assert.That(encoded, Is.EqualTo(Rlp.OfEmptySequence)); + } + + [Test] + public void Decode_Null_ReturnsNull() + { + var decoder = new SyncInfoDecoder(); + var stream = new RlpStream(Rlp.OfEmptySequence.Bytes); + + SyncInfo decoded = decoder.Decode(stream); + + Assert.That(decoded, Is.Null); + } + + [Test] + public void Decode_EmptyByteArray_ValueDecoderContext_ReturnsNull() + { + var decoder = new SyncInfoDecoder(); + Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(Rlp.OfEmptySequence.Bytes); + + SyncInfo decoded = decoder.Decode(ref decoderContext); + + Assert.That(decoded, Is.Null); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/TimeoutTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/TimeoutTests.cs new file mode 100644 index 000000000000..f3003eb68948 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/TimeoutTests.cs @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Xdc.Test.Helpers; +using Nethermind.Xdc.Types; +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Nethermind.Xdc.Test; + +public class TimeoutTests +{ + [Test] + public async Task TestCountdownTimeoutToSendTimeoutMessage() + { + var blockchain = await XdcTestBlockchain.Create(); + var tcManager = blockchain.TimeoutCertificateManager; + var ctx = blockchain.XdcContext; + tcManager.OnCountdownTimer(); + + Timeout expectedTimeoutMsg = XdcTestHelper.BuildSignedTimeout(blockchain.Signer.Key!, ctx.CurrentRound, 0); + + Assert.That(ctx.TimeoutCounter, Is.EqualTo(1)); + Assert.That(tcManager.GetTimeoutsCount(expectedTimeoutMsg), Is.EqualTo(1)); + } + + [Test] + public async Task TestCountdownTimeoutNotToSendTimeoutMessageIfNotInMasternodeList() + { + var blockchain = await XdcTestBlockchain.Create(); + // Create TCManager with a signer not in the Masternode list + var extraKey = blockchain.RandomKeys.First(); + + blockchain.Signer.SetSigner(TestItem.PrivateKeyA); + + blockchain.TimeoutCertificateManager.OnCountdownTimer(); + + // Since the signer is not in masternode list, method should return early + Assert.That(blockchain.XdcContext.TimeoutCounter, Is.EqualTo(0)); + } + + [Test] + public async Task TestTimeoutMessageHandlerSuccessfullyGenerateTC() + { + var blockchain = await XdcTestBlockchain.Create(); + + var ctx = blockchain.XdcContext; + var head = (XdcBlockHeader)blockchain.BlockTree.Head!.Header; + var spec = blockchain.SpecProvider.GetXdcSpec(head, ctx.CurrentRound); + var epoch = blockchain.EpochSwitchManager.GetEpochSwitchInfo(head)!; + var signers = blockchain.TakeRandomMasterNodes(spec, epoch); + var round = ctx.CurrentRound; + const ulong gap = 450; + var signatures = new List(); + + // Send N-1 timeouts -> should NOT reach threshold + for (int i = 0; i < signers.Length - 1; i++) + { + Timeout timeoutMsg = XdcTestHelper.BuildSignedTimeout(signers[i], round, gap); + await blockchain.TimeoutCertificateManager.HandleTimeoutVote(timeoutMsg); + signatures.Add(timeoutMsg.Signature!); + } + + // Sanity check: round hasn’t advanced, HighestTC not set to this round yet + Assert.That(ctx.CurrentRound, Is.EqualTo(round)); + Assert.That(ctx.HighestTC, Is.Null); + + // Send timeout message with wrong gap so it doesn't reach threshold yet + await blockchain.TimeoutCertificateManager.HandleTimeoutVote(XdcTestHelper.BuildSignedTimeout(signers.Last(), round, 1350)); + Assert.That(ctx.CurrentRound, Is.EqualTo(round)); + Assert.That(ctx.HighestTC, Is.Null); + + // One more timeout (reaches threshold) -> HighestTC set, round increments + Timeout lastTimeoutMsg = XdcTestHelper.BuildSignedTimeout(signers.Last(), round, gap); + await blockchain.TimeoutCertificateManager.HandleTimeoutVote(lastTimeoutMsg); + signatures.Add(lastTimeoutMsg.Signature!); + + var expectedTC = new TimeoutCertificate(round, signatures.ToArray(), gap); + ctx.HighestTC.Should().BeEquivalentTo(expectedTC); + Assert.That(ctx.CurrentRound, Is.EqualTo(round + 1)); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/VoteTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/VoteTests.cs new file mode 100644 index 000000000000..7d9d677ac972 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/VoteTests.cs @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Crypto; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Test.Helpers; +using Nethermind.Xdc.Types; +using NUnit.Framework; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace Nethermind.Xdc.Test; + +public class VoteTests +{ + [Test] + public async Task HandleVote_SuccessfullyGenerateAndProcessQc() + { + var blockchain = await XdcTestBlockchain.Create(); + var votesManager = CreateVotesManager(blockchain); + IXdcConsensusContext ctx = blockchain.XdcContext; + + // Add a new block with NO QC simulation + var freshBlock = await blockchain.AddBlockWithoutCommitQc(); + var header = (freshBlock.Header as XdcBlockHeader)!; + + var currentBlockHash = freshBlock?.Hash!; + IXdcReleaseSpec releaseSpec = blockchain.SpecProvider.GetXdcSpec(header, ctx.CurrentRound); + var switchInfo = blockchain.EpochSwitchManager.GetEpochSwitchInfo(header)!; + PrivateKey[] keys = blockchain.TakeRandomMasterNodes(releaseSpec, switchInfo); + var blockInfo = new BlockRoundInfo(currentBlockHash, ctx.CurrentRound, freshBlock!.Number); + var gap = (ulong)Math.Max(0, switchInfo.EpochSwitchBlockInfo.BlockNumber - switchInfo.EpochSwitchBlockInfo.BlockNumber % releaseSpec.EpochLength - releaseSpec.Gap); + + // Initial values to check + var initialLockQc = ctx.LockQC; + var initialRound = ctx.CurrentRound; + var initialHighestQc = ctx.HighestQC; + Assert.That(header.ExtraConsensusData!.BlockRound, Is.EqualTo(ctx.CurrentRound)); + + // Amount of votes processed in this loop will be 1 less than the required to reach the threshold + for (int i = 0; i < keys.Length - 1; i++) + { + var newVote = XdcTestHelper.BuildSignedVote(blockInfo, gap, keys[i]); + await votesManager.HandleVote(newVote); + } + // Check same values as before: qc has not yet been processed, so there should be no changes + ctx.LockQC.Should().BeEquivalentTo(initialLockQc); + Assert.That(ctx.CurrentRound, Is.EqualTo(initialRound)); + ctx.HighestQC.Should().BeEquivalentTo(initialHighestQc); + + // Create another vote which is signed by someone not from the master node list + var randomKey = blockchain.RandomKeys.First(); + var randomVote = XdcTestHelper.BuildSignedVote(blockInfo, gap, randomKey); + await votesManager.HandleVote(randomVote); + + // Again same check: vote is not valid so threshold is not yet reached + ctx.LockQC.Should().BeEquivalentTo(initialLockQc); + Assert.That(ctx.CurrentRound, Is.EqualTo(initialRound)); + ctx.HighestQC.Should().BeEquivalentTo(initialHighestQc); + + // Create a vote message that should trigger qc processing and increment the round + var lastVote = XdcTestHelper.BuildSignedVote(blockInfo, gap, keys.Last()); + await votesManager.HandleVote(lastVote); + // Check new round has been set + Assert.That(ctx.CurrentRound, Is.EqualTo(initialRound + 1)); + // The lockQC shall be the parent's QC round number + Assert.That(ctx.LockQC, Is.Not.Null); + Assert.That(ctx.LockQC.ProposedBlockInfo.Round, Is.EqualTo(initialRound - 1)); + // The highestQC proposedBlockInfo shall be the same as the one from its votes + ctx.HighestQC.ProposedBlockInfo.Should().BeEquivalentTo(blockInfo); + } + + private VotesManager CreateVotesManager(XdcTestBlockchain blockchain) + { + return new VotesManager( + blockchain.XdcContext, + blockchain.BlockTree, + blockchain.EpochSwitchManager, + blockchain.SnapshotManager, + blockchain.QuorumCertificateManager, + blockchain.SpecProvider, + blockchain.Signer, + NSubstitute.Substitute.For()); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/VotesManagerTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/VotesManagerTests.cs new file mode 100644 index 000000000000..f1ae4e1053c7 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/VotesManagerTests.cs @@ -0,0 +1,320 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Consensus; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Types; +using NSubstitute; +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Nethermind.Xdc.Test; + +[Parallelizable(ParallelScope.All)] +public class VotesManagerTests +{ + public static IEnumerable HandleVoteCases() + { + var keys = MakeKeys(22); + var keysForMasternodes = keys.Take(20).ToArray(); + var extraKeys = keys.Skip(20).ToArray(); + var masternodes = keysForMasternodes.Select(k => k.Address).ToArray(); + + ulong currentRound = 1; + XdcBlockHeader header = Build.A.XdcBlockHeader() + .WithExtraConsensusData(new ExtraFieldsV2(currentRound, new QuorumCertificate(new BlockRoundInfo(Hash256.Zero, 0, 0), null, 450))) + .TestObject; + var info = new BlockRoundInfo(header.Hash!, currentRound, header.Number); + + // Base case + yield return new TestCaseData(masternodes, header, currentRound, keysForMasternodes.Select(k => XdcTestHelper.BuildSignedVote(info, 450, k)).ToArray(), info, 1); + + // Not enough valid signers + var votes = keysForMasternodes.Take(12).Select(k => XdcTestHelper.BuildSignedVote(info, 450, k)).ToArray(); + var extraVotes = extraKeys.Select(k => XdcTestHelper.BuildSignedVote(info, 450, k)).ToArray(); + yield return new TestCaseData(masternodes, header, currentRound, votes.Concat(extraVotes).ToArray(), info, 0); + + // Wrong gap number generates different keys for the vote pool + var keysForVotes = keysForMasternodes.Take(14).ToArray(); + var votesWithDiffGap = new List(capacity: keysForVotes.Length); + for (var i = 0; i < keysForVotes.Length - 3; i++) votesWithDiffGap.Add(XdcTestHelper.BuildSignedVote(info, 450, keysForVotes[i])); + for (var i = keysForVotes.Length - 3; i < keysForVotes.Length; i++) votesWithDiffGap.Add(XdcTestHelper.BuildSignedVote(info, 451, keysForVotes[i])); + yield return new TestCaseData(masternodes, header, currentRound, votesWithDiffGap.ToArray(), info, 0); + } + + [TestCaseSource(nameof(HandleVoteCases))] + public async Task HandleVote_VariousScenarios_CommitsQcExpectedTimes(Address[] masternodes, XdcBlockHeader header, ulong currentRound, Vote[] votes, BlockRoundInfo info, int expectedCalls) + { + var context = new XdcConsensusContext(); + context.SetNewRound(currentRound); + IBlockTree blockTree = Substitute.For(); + blockTree.FindHeader(Arg.Any(), Arg.Any()).Returns(header); + + IEpochSwitchManager epochSwitchManager = Substitute.For(); + var epochSwitchInfo = new EpochSwitchInfo(masternodes, [], [], info); + epochSwitchManager + .GetEpochSwitchInfo(header) + .Returns(epochSwitchInfo); + + ISnapshotManager snapshotManager = Substitute.For(); + IQuorumCertificateManager quorumCertificateManager = Substitute.For(); + ISpecProvider specProvider = Substitute.For(); + IXdcReleaseSpec xdcReleaseSpec = Substitute.For(); + xdcReleaseSpec.CertThreshold.Returns(0.667); + specProvider.GetSpec(Arg.Any()).Returns(xdcReleaseSpec); + + ISigner signer = Substitute.For(); + IForensicsProcessor forensicsProcessor = Substitute.For(); + + var voteManager = new VotesManager(context, blockTree, epochSwitchManager, snapshotManager, quorumCertificateManager, + specProvider, signer, forensicsProcessor); + + foreach (var v in votes) + await voteManager.HandleVote(v); + + quorumCertificateManager.Received(expectedCalls).CommitCertificate(Arg.Any()); + } + + [Test] + public async Task HandleVote_HeaderMissing_ReturnsEarly() + { + var keys = MakeKeys(20); + var masternodes = keys.Select(k => k.Address).ToArray(); + + ulong currentRound = 1; + var context = new XdcConsensusContext { CurrentRound = currentRound }; + IBlockTree blockTree = Substitute.For(); + XdcBlockHeader header = Build.A.XdcBlockHeader() + .WithExtraConsensusData(new ExtraFieldsV2(currentRound, new QuorumCertificate(new BlockRoundInfo(Hash256.Zero, 0, 0), null, 450))) + .TestObject; + + var info = new BlockRoundInfo(header.Hash!, currentRound, header.Number); + IEpochSwitchManager epochSwitchManager = Substitute.For(); + var epochSwitchInfo = new EpochSwitchInfo(masternodes, [], [], info); + epochSwitchManager + .GetEpochSwitchInfo(header) + .Returns(epochSwitchInfo); + + ISnapshotManager snapshotManager = Substitute.For(); + IQuorumCertificateManager quorumCertificateManager = Substitute.For(); + ISpecProvider specProvider = Substitute.For(); + IXdcReleaseSpec xdcReleaseSpec = Substitute.For(); + xdcReleaseSpec.CertThreshold.Returns(0.667); + specProvider.GetSpec(Arg.Any()).Returns(xdcReleaseSpec); + + ISigner signer = Substitute.For(); + IForensicsProcessor forensicsProcessor = Substitute.For(); + + var voteManager = new VotesManager(context, blockTree, epochSwitchManager, snapshotManager, quorumCertificateManager, + specProvider, signer, forensicsProcessor); + + for (var i = 0; i < keys.Length - 1; i++) + await voteManager.HandleVote(XdcTestHelper.BuildSignedVote(info, gap: 450, keys[i])); + + quorumCertificateManager.DidNotReceive().CommitCertificate(Arg.Any()); + + // Now insert header and send one more + blockTree.FindHeader(header.Hash!, Arg.Any()).Returns(header); + await voteManager.HandleVote(XdcTestHelper.BuildSignedVote(info, 450, keys.Last())); + + quorumCertificateManager.Received(1).CommitCertificate(Arg.Any()); + } + + [TestCase(7UL, 0)] + [TestCase(6UL, 1)] + [TestCase(5UL, 1)] + [TestCase(4UL, 0)] + public async Task HandleVote_MsgRoundDifferentValues_ReturnsEarlyIfTooFarFromCurrentRound(ulong currentRound, + long expectedCount) + { + var ctx = new XdcConsensusContext { CurrentRound = currentRound }; + VotesManager votesManager = BuildVoteManager(ctx); + + // Dummy values, we only care about the round + var blockInfo = new BlockRoundInfo(Hash256.Zero, 6, 0); + var key = MakeKeys(1).First(); + var vote = XdcTestHelper.BuildSignedVote(blockInfo, 450, key); + await votesManager.HandleVote(vote); + Assert.That(votesManager.GetVotesCount(vote), Is.EqualTo(expectedCount)); + } + + public static IEnumerable FilterVoteCases() + { + var keys = MakeKeys(21); + var masternodes = keys.Take(20).Select(k => k.Address).ToArray(); + var blockInfo = new BlockRoundInfo(Hash256.Zero, 14, 915); + + // Disqualified as the round does not match + var vote = new Vote(blockInfo, 450); + yield return new TestCaseData(15UL, masternodes, vote, false); + + // Invalid signature + yield return new TestCaseData(14UL, masternodes, XdcTestHelper.BuildSignedVote(blockInfo, 450, keys.Last()), false); + + // Valid message + yield return new TestCaseData(14UL, masternodes, XdcTestHelper.BuildSignedVote(blockInfo, 450, keys.First()), true); + + // If snapshot missing should return false + yield return new TestCaseData(14UL, masternodes, XdcTestHelper.BuildSignedVote(blockInfo, 1350, keys.First()), false); + + } + + [TestCaseSource(nameof(FilterVoteCases))] + public void FilterVote(ulong currentRound, Address[] masternodes, Vote vote, bool expected) + { + var context = new XdcConsensusContext(); + context.SetNewRound(currentRound); + IBlockTree blockTree = Substitute.For(); + XdcBlockHeader header = Build.A.XdcBlockHeader() + .WithExtraConsensusData(new ExtraFieldsV2(currentRound, new QuorumCertificate(new BlockRoundInfo(Hash256.Zero, 0, 0), null, 0))) + .TestObject; + blockTree.Head.Returns(new Block(header)); + IEpochSwitchManager epochSwitchManager = Substitute.For(); + ISnapshotManager snapshotManager = Substitute.For(); + snapshotManager.GetSnapshotByGapNumber(450) + .Returns(new Snapshot(0, Hash256.Zero, masternodes)); + IQuorumCertificateManager quorumCertificateManager = Substitute.For(); + ISpecProvider specProvider = Substitute.For(); + IXdcReleaseSpec xdcReleaseSpec = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(xdcReleaseSpec); + ISigner signer = Substitute.For(); + IForensicsProcessor forensicsProcessor = Substitute.For(); + + var voteManager = new VotesManager(context, blockTree, epochSwitchManager, snapshotManager, quorumCertificateManager, + specProvider, signer, forensicsProcessor); + + Assert.That(voteManager.FilterVote(vote), Is.EqualTo(expected)); + } + + + [TestCase(5UL, 4UL, false)] // Current round different from blockInfoRound + [TestCase(5UL, 5UL, true)] // No LockQc + public void VerifyVotingRules_FirstChecks_ReturnsExpected(ulong currentRound, ulong blockInfoRound, bool expected) + { + var ctx = new XdcConsensusContext { CurrentRound = currentRound }; + VotesManager votesManager = BuildVoteManager(ctx); + + var blockInfo = new BlockRoundInfo(Hash256.Zero, blockInfoRound, 100); + var qc = new QuorumCertificate(blockInfo, null, 0); + + Assert.That(votesManager.VerifyVotingRules(blockInfo, qc), Is.EqualTo(expected)); + } + + [TestCase] + public async Task VerifyVotingRules_RoundWasVotedOn_ReturnsFalse() + { + var ctx = new XdcConsensusContext { CurrentRound = 1 }; + IBlockTree blockTree = Substitute.For(); + blockTree + .FindHeader(Arg.Any()) + .Returns(Build.A.XdcBlockHeader().TestObject); + VotesManager votesManager = BuildVoteManager(ctx, blockTree); + + var blockInfo = new BlockRoundInfo(Hash256.Zero, 1, 100); + var qc = new QuorumCertificate(blockInfo, null, 0); + await votesManager.CastVote(blockInfo); + + Assert.That(votesManager.VerifyVotingRules(blockInfo, qc), Is.False); + } + + [Test] + public void VerifyVotingRules_QcNewerThanLockQc_ReturnsTrue() + { + var lockQc = new QuorumCertificate(new BlockRoundInfo(Hash256.Zero, 4, 99), null, 0); + var ctx = new XdcConsensusContext { CurrentRound = 5, LockQC = lockQc }; + VotesManager votesManager = BuildVoteManager(ctx); + + var blockInfo = new BlockRoundInfo(Hash256.Zero, 5, 100); + var qc = new QuorumCertificate(blockInfo, null, 0); + + Assert.That(votesManager.VerifyVotingRules(blockInfo, qc), Is.True); + } + + public static IEnumerable ExtendingFromAncestorCases() + { + XdcBlockHeader[] headers = GenerateBlockHeaders(3, 99); + IBlockTree blockTree = Substitute.For(); + var headerByHash = headers.ToDictionary(h => h.Hash!, h => h); + + XdcBlockHeader nonRelatedHeader = Build.A.XdcBlockHeader().WithNumber(99).TestObject; + nonRelatedHeader.Hash ??= nonRelatedHeader.CalculateHash().ToHash256(); + headerByHash[nonRelatedHeader.Hash] = nonRelatedHeader; + + blockTree.FindHeader(Arg.Any()).Returns(args => + { + var hash = (Hash256)args[0]; + return headerByHash.TryGetValue(hash, out var header) ? header : null; + }); + + var blockInfo = new BlockRoundInfo(headers[2].Hash!, 5, headers[2].Number); + + var ancestorQc = new QuorumCertificate(new BlockRoundInfo(headers[0].Hash!, 3, headers[0].Number), null, 0); + yield return new TestCaseData(blockTree, ancestorQc, blockInfo, true); + + var nonRelatedQc = new QuorumCertificate(new BlockRoundInfo(nonRelatedHeader.Hash, 3, nonRelatedHeader.Number), null, 0); + yield return new TestCaseData(blockTree, nonRelatedQc, blockInfo, false); + } + + [TestCaseSource(nameof(ExtendingFromAncestorCases))] + public void VerifyVotingRules_CheckExtendingFromAncestor_ReturnsExpected(IBlockTree tree, QuorumCertificate lockQc, BlockRoundInfo blockInfo, bool expected) + { + var ctx = new XdcConsensusContext { CurrentRound = 5, LockQC = lockQc }; + VotesManager votesManager = BuildVoteManager(ctx, tree); + var qc = new QuorumCertificate(new BlockRoundInfo(Hash256.Zero, 3, 99), null, 0); + + Assert.That(votesManager.VerifyVotingRules(blockInfo, qc), Is.EqualTo(expected)); + } + + private static PrivateKey[] MakeKeys(int n) + { + var keyBuilder = new PrivateKeyGenerator(); + PrivateKey[] keys = keyBuilder.Generate(n).ToArray(); + return keys; + } + + private static VotesManager BuildVoteManager(IXdcConsensusContext ctx, IBlockTree? blockTree = null) + { + blockTree ??= Substitute.For(); + IEpochSwitchManager epochSwitchManager = Substitute.For(); + epochSwitchManager + .GetEpochSwitchInfo(Arg.Any()) + .Returns(new EpochSwitchInfo([], [], [], new BlockRoundInfo(Hash256.Zero, 0, 0))); + ISnapshotManager snapshotManager = Substitute.For(); + IQuorumCertificateManager quorumCertificateManager = Substitute.For(); + ISpecProvider specProvider = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(new XdcReleaseSpec() + { + V2Configs = [new V2ConfigParams()] + }); + ISigner signer = Substitute.For(); + IForensicsProcessor forensicsProcessor = Substitute.For(); + + return new VotesManager(ctx, blockTree, epochSwitchManager, snapshotManager, quorumCertificateManager, + specProvider, signer, forensicsProcessor); + } + + private static XdcBlockHeader[] GenerateBlockHeaders(int n, long blockNumber) + { + var headers = new XdcBlockHeader[n]; + var parentHash = Hash256.Zero; + var number = blockNumber; + for (int i = 0; i < n; i++, number++) + { + headers[i] = Build.A.XdcBlockHeader() + .WithNumber(number) + .WithParentHash(parentHash) + .TestObject; + parentHash = headers[i].CalculateHash().ToHash256(); + } + + return headers; + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs new file mode 100644 index 000000000000..4b545ba8cd02 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Xdc.Test.Helpers; +using NUnit.Framework; +using System.Threading.Tasks; + +namespace Nethermind.Xdc.Test; + +internal class XdcReorgModuleTests +{ + [Test] + public async Task TestNormalReorgWhenNotInvolveCommittedBlock() + { + var blockChain = await XdcTestBlockchain.Create(); + var startRound = blockChain.XdcContext.CurrentRound; + await blockChain.AddBlocks(3); + // Simulate timeout to make block rounds non-consecutive preventing finalization + blockChain.XdcContext.SetNewRound(blockChain.XdcContext.CurrentRound + 1); + await blockChain.AddBlocks(2); + var finalizedBlockInfo = blockChain.XdcContext.HighestCommitBlock; + finalizedBlockInfo.Round.Should().Be(blockChain.XdcContext.CurrentRound - 2 - 1 - 2); // Finalization is 2 round behind current plus 1 round timeout plus 2 rounds of blocks added + + var finalizedBlock = blockChain.BlockTree.FindHeader(finalizedBlockInfo.Hash); + + var forkBlock = await blockChain.BlockProducer.BuildBlock(finalizedBlock); + + blockChain.BlockTree.SuggestBlock(forkBlock!).Should().Be(Blockchain.AddBlockResult.Added); + } + + [Test] + public async Task BuildAValidForkOnFinalizedBlockAndAssertForkBecomesCanonical() + { + var blockChain = await XdcTestBlockchain.Create(3); + var startRound = blockChain.XdcContext.CurrentRound; + await blockChain.AddBlocks(10); + + var finalizedBlockInfo = blockChain.XdcContext.HighestCommitBlock; + finalizedBlockInfo.Round.Should().Be(blockChain.XdcContext.CurrentRound - 2); // Finalization is 2 rounds behind + + XdcBlockHeader? finalizedBlock = (XdcBlockHeader)blockChain.BlockTree.FindHeader(finalizedBlockInfo.Hash)!; + + var newHeadWaitHandle = new TaskCompletionSource(); + blockChain.BlockTree.NewHeadBlock += (_, args) => + { + newHeadWaitHandle.SetResult(); + }; + + 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; + } + + 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!); + //The new fork head should commit it's grandparent as finalized + 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!); + } + + [TestCase(5)] + [TestCase(900)] + [TestCase(901)] + public async Task TestShouldNotReorgCommittedBlock(int number) + { + var blockChain = await XdcTestBlockchain.Create(); + var startRound = blockChain.XdcContext.CurrentRound; + await blockChain.AddBlocks(number); + var finalizedBlockInfo = blockChain.XdcContext.HighestCommitBlock; + finalizedBlockInfo.Round.Should().Be(blockChain.XdcContext.CurrentRound - 2); // Finalization is 2 rounds behind + + var finalizedBlock = blockChain.BlockTree.FindHeader(finalizedBlockInfo.Hash)!; + var parentOfFinalizedBlock = blockChain.BlockTree.FindHeader(finalizedBlock.ParentHash!); + + var forkBlock = await blockChain.BlockProducer.BuildBlock(parentOfFinalizedBlock); + + blockChain.BlockTree.SuggestBlock(forkBlock!).Should().Be(Blockchain.AddBlockResult.InvalidBlock); + } + +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcTestBlockchainTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcTestBlockchainTests.cs new file mode 100644 index 000000000000..ebd5e81dc814 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcTestBlockchainTests.cs @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Autofac; +using Nethermind.Consensus.Validators; +using Nethermind.Core; +using Nethermind.Xdc.Test.Helpers; +using NUnit.Framework; +using System.Threading.Tasks; + +namespace Nethermind.Xdc.Test; + +internal class XdcTestBlockchainTests +{ + private XdcTestBlockchain _blockchain; + + [SetUp] + public async Task Setup() + { + _blockchain = await XdcTestBlockchain.Create(); + } + + [TearDown] + public void TearDown() + { + _blockchain?.Dispose(); + } + + [TestCase(180)] + [TestCase(91)] + public async Task SetupXdcChainAndValidateAllHeaders(int count) + { + //Shorten the epoch length so we can run the test faster + _blockchain.ChangeReleaseSpec((c) => + { + c.EpochLength = 90; + c.Gap = 45; + }); + + await _blockchain.AddBlocks(count); + IHeaderValidator headerValidator = _blockchain.Container.Resolve(); + BlockHeader parent = _blockchain.BlockTree.Genesis!; + for (int i = 1; i < _blockchain.BlockTree.Head!.Number; i++) + { + var block = _blockchain.BlockTree.FindBlock(i); + Assert.That(block, Is.Not.Null); + string? error; + Assert.That(headerValidator.Validate(block!.Header, parent, false, out error), Is.True, "Header validation failed: " + error); + parent = block.Header; + } + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/Nethermind.Xdc.Test.csproj b/src/Nethermind/Nethermind.Xdc.Test/Nethermind.Xdc.Test.csproj index 1843068955be..fd395e63a1b4 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/Nethermind.Xdc.Test.csproj +++ b/src/Nethermind/Nethermind.Xdc.Test/Nethermind.Xdc.Test.csproj @@ -7,6 +7,10 @@ $(NoWarn);NUnit1032 + + + + diff --git a/src/Nethermind/Nethermind.Xdc.Test/QuorumCertificateDecoderTests.cs b/src/Nethermind/Nethermind.Xdc.Test/QuorumCertificateDecoderTests.cs index 69a7a062645d..a11b9f53d2e5 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/QuorumCertificateDecoderTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/QuorumCertificateDecoderTests.cs @@ -9,6 +9,8 @@ using System.Collections; namespace Nethermind.Xdc.Test; + +[Parallelizable(ParallelScope.All)] internal class QuorumCertificateDecoderTests { @@ -16,20 +18,20 @@ public static IEnumerable QuorumCertificateCases { get { - yield return new TestCaseData(new QuorumCert(new BlockRoundInfo(Hash256.Zero, 1, 1), [new Signature(new byte[64], 0), new Signature(new byte[64], 0), new Signature(new byte[64], 0)], 0)); - yield return new TestCaseData(new QuorumCert(new BlockRoundInfo(Hash256.Zero, 1, 1), [], 0)); - yield return new TestCaseData(new QuorumCert(new BlockRoundInfo(Hash256.Zero, ulong.MaxValue, long.MaxValue), [], int.MaxValue)); + yield return new TestCaseData(new QuorumCertificate(new BlockRoundInfo(Hash256.Zero, 1, 1), [new Signature(new byte[64], 0), new Signature(new byte[64], 0), new Signature(new byte[64], 0)], 0)); + yield return new TestCaseData(new QuorumCertificate(new BlockRoundInfo(Hash256.Zero, 1, 1), [], 0)); + yield return new TestCaseData(new QuorumCertificate(new BlockRoundInfo(Hash256.Zero, ulong.MaxValue, long.MaxValue), [], int.MaxValue)); } } [TestCaseSource(nameof(QuorumCertificateCases))] - public void Encode_DifferentValues_IsEquivalentAfterReencoding(QuorumCert quorumCert) + public void Encode_DifferentValues_IsEquivalentAfterReencoding(QuorumCertificate quorumCert) { QuorumCertificateDecoder decoder = new(); RlpStream stream = new RlpStream(decoder.GetLength(quorumCert)); decoder.Encode(stream, quorumCert); stream.Position = 0; - QuorumCert decoded = decoder.Decode(stream); + QuorumCertificate decoded = decoder.Decode(stream); decoded.Should().BeEquivalentTo(quorumCert); } @@ -38,12 +40,12 @@ public void Encode_DifferentValues_IsEquivalentAfterReencoding(QuorumCert quorum [TestCase(false)] public void Encode_UseBothRlpStreamAndValueDecoderContext_IsEquivalentAfterReencoding(bool useRlpStream) { - QuorumCert quorumCert = new(new BlockRoundInfo(Hash256.Zero, 1, 1), [new Signature(new byte[64], 0), new Signature(new byte[64], 0), new Signature(new byte[64], 0)], 0); + QuorumCertificate quorumCert = new(new BlockRoundInfo(Hash256.Zero, 1, 1), [new Signature(new byte[64], 0), new Signature(new byte[64], 0), new Signature(new byte[64], 0)], 0); QuorumCertificateDecoder decoder = new(); RlpStream stream = new RlpStream(decoder.GetLength(quorumCert)); decoder.Encode(stream, quorumCert); stream.Position = 0; - QuorumCert decoded; + QuorumCertificate decoded; if (useRlpStream) { decoded = decoder.Decode(stream); diff --git a/src/Nethermind/Nethermind.Xdc.Test/QuorumCertificateManagerTest.cs b/src/Nethermind/Nethermind.Xdc.Test/QuorumCertificateManagerTest.cs new file mode 100644 index 000000000000..f21c14881a28 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/QuorumCertificateManagerTest.cs @@ -0,0 +1,306 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Logging; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Types; +using NSubstitute; +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; + +namespace Nethermind.Xdc.Test; + +[Parallelizable(ParallelScope.All)] +public class QuorumCertificateManagerTest +{ + [Test] + public void VerifyCertificate_CertificateIsNull_ThrowsArgumentNullException() + { + var quorumCertificateManager = new QuorumCertificateManager( + new XdcConsensusContext(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For()); + + Assert.That(() => quorumCertificateManager.VerifyCertificate(null!, Build.A.XdcBlockHeader().TestObject, out _), Throws.ArgumentNullException); + } + + [Test] + public void VerifyCertificate_HeaderIsNull_ThrowsArgumentNullException() + { + var quorumCertificateManager = new QuorumCertificateManager( + new XdcConsensusContext(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For()); + + Assert.That(() => quorumCertificateManager.VerifyCertificate(Build.A.QuorumCertificate().TestObject, null!, out _), Throws.ArgumentNullException); + } + + public static IEnumerable QcCases() + { + XdcBlockHeaderBuilder headerBuilder = Build.A.XdcBlockHeader().WithGeneratedExtraConsensusData(); + var keyBuilder = new PrivateKeyGenerator(); + //Base valid control case + PrivateKey[] keys = keyBuilder.Generate(20).ToArray(); + IEnumerable
masterNodes = keys.Select(k => k.Address); + yield return new TestCaseData(XdcTestHelper.CreateQc(new BlockRoundInfo(headerBuilder.TestObject.Hash!, 1, 1), 0, keys), headerBuilder, keys.Select(k => k.Address), true); + + //Not enough signatures + yield return new TestCaseData(XdcTestHelper.CreateQc(new BlockRoundInfo(headerBuilder.TestObject.Hash!, 1, 1), 0, keys.Take(13).ToArray()), headerBuilder, keys.Select(k => k.Address), false); + + //1 Vote is not master node + yield return new TestCaseData(XdcTestHelper.CreateQc(new BlockRoundInfo(headerBuilder.TestObject.Hash!, 1, 1), 0, keys), headerBuilder, keys.Skip(1).Select(k => k.Address), false); + + //Wrong gap number + yield return new TestCaseData(XdcTestHelper.CreateQc(new BlockRoundInfo(headerBuilder.TestObject.Hash!, 1, 1), 1, keys), headerBuilder, masterNodes, false); + + //Wrong block number in QC + yield return new TestCaseData(XdcTestHelper.CreateQc(new BlockRoundInfo(headerBuilder.TestObject.Hash!, 1, 2), 0, keys), headerBuilder, masterNodes, false); + + //Wrong hash in QC + yield return new TestCaseData(XdcTestHelper.CreateQc(new BlockRoundInfo(Hash256.Zero, 1, 1), 0, keys), headerBuilder, masterNodes, false); + + //Wrong round number in QC + yield return new TestCaseData(XdcTestHelper.CreateQc(new BlockRoundInfo(headerBuilder.TestObject.Hash!, 0, 1), 0, keys), headerBuilder, masterNodes, false); + } + + [TestCaseSource(nameof(QcCases))] + public void VerifyCertificate_QcWithDifferentParameters_ReturnsExpected(QuorumCertificate quorumCert, XdcBlockHeaderBuilder xdcBlockHeaderBuilder, IEnumerable
masternodes, bool expected) + { + IEpochSwitchManager epochSwitchManager = Substitute.For(); + epochSwitchManager + .GetEpochSwitchInfo(Arg.Any()) + .Returns(new EpochSwitchInfo(masternodes.ToArray(), [], [], new BlockRoundInfo(Hash256.Zero, 1, 10))); + epochSwitchManager + .GetEpochSwitchInfo(Arg.Any()) + .Returns(new EpochSwitchInfo(masternodes.ToArray(), [], [], new BlockRoundInfo(Hash256.Zero, 1, 10))); + ISpecProvider specProvider = Substitute.For(); + IXdcReleaseSpec xdcReleaseSpec = Substitute.For(); + xdcReleaseSpec.EpochLength.Returns(900); + xdcReleaseSpec.Gap.Returns(450); + xdcReleaseSpec.CertThreshold.Returns(0.667); + specProvider.GetSpec(Arg.Any()).Returns(xdcReleaseSpec); + var quorumCertificateManager = new QuorumCertificateManager( + new XdcConsensusContext(), + Substitute.For(), + specProvider, + epochSwitchManager, + Substitute.For()); + + Assert.That(quorumCertificateManager.VerifyCertificate(quorumCert, xdcBlockHeaderBuilder.TestObject, out _), Is.EqualTo(expected)); + } + + [Test] + public void CommitCertificate_HeaderDoesNotExists_ThrowInvalidOperationException() + { + IEpochSwitchManager epochSwitchManager = Substitute.For(); + ISpecProvider specProvider = Substitute.For(); + IXdcReleaseSpec xdcReleaseSpec = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(xdcReleaseSpec); + XdcConsensusContext context = new XdcConsensusContext(); + var quorumCertificateManager = new QuorumCertificateManager( + context, + Substitute.For(), + specProvider, + epochSwitchManager, + Substitute.For()); + QuorumCertificate qc = Build.A.QuorumCertificate().WithBlockInfo(new BlockRoundInfo(Hash256.Zero, 1, 0)).TestObject; + + Assert.That(() => quorumCertificateManager.CommitCertificate(qc), Throws.TypeOf()); + } + + [Test] + public void CommitCertificate_QcHasHigherRound_HighestQCIsSet() + { + IEpochSwitchManager epochSwitchManager = Substitute.For(); + ISpecProvider specProvider = Substitute.For(); + IXdcReleaseSpec xdcReleaseSpec = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(xdcReleaseSpec); + XdcConsensusContext context = new XdcConsensusContext(); + IBlockTree blockTree = Substitute.For(); + XdcBlockHeader targetHeader = Build.A.XdcBlockHeader().WithGeneratedExtraConsensusData().TestObject; + blockTree.FindHeader(Arg.Any()).Returns(targetHeader); + var quorumCertificateManager = new QuorumCertificateManager( + context, + blockTree, + specProvider, + epochSwitchManager, + Substitute.For()); + context.HighestQC = Build.A.QuorumCertificate().WithBlockInfo(new BlockRoundInfo(Hash256.Zero, 0, 1)).TestObject; + QuorumCertificate qc = Build.A.QuorumCertificate().WithBlockInfo(new BlockRoundInfo(targetHeader.Hash!, 1, 0)).TestObject; + quorumCertificateManager.CommitCertificate(qc); + + Assert.That(context.HighestQC, Is.EqualTo(qc)); + } + + [Test] + public void CommitCertificate_TargetHeaderDoesNotHaveQc_ThrowBlockchainException() + { + IEpochSwitchManager epochSwitchManager = Substitute.For(); + ISpecProvider specProvider = Substitute.For(); + IXdcReleaseSpec xdcReleaseSpec = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(xdcReleaseSpec); + XdcConsensusContext context = new XdcConsensusContext(); + IBlockTree blockTree = Substitute.For(); + XdcBlockHeader targetHeader = Build.A.XdcBlockHeader().TestObject; + blockTree.FindHeader(Arg.Any()).Returns(targetHeader); + var quorumCertificateManager = new QuorumCertificateManager( + context, + blockTree, + specProvider, + epochSwitchManager, + Substitute.For()); + QuorumCertificate qc = Build.A.QuorumCertificate().WithBlockInfo(new BlockRoundInfo(targetHeader.Hash!, 1, 0)).TestObject; + + Assert.That(() => quorumCertificateManager.CommitCertificate(qc), Throws.TypeOf()); + } + + [TestCase(true)] + [TestCase(false)] + public void CommitCertificate_ParentQcHasHigherRound_LockQCIsSetToParent(bool lockQcIsNull) + { + IEpochSwitchManager epochSwitchManager = Substitute.For(); + ISpecProvider specProvider = Substitute.For(); + IXdcReleaseSpec xdcReleaseSpec = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(xdcReleaseSpec); + XdcConsensusContext context = new XdcConsensusContext(); + IBlockTree blockTree = Substitute.For(); + XdcBlockHeader targetHeader = Build.A.XdcBlockHeader().WithGeneratedExtraConsensusData().TestObject; + blockTree.FindHeader(Arg.Any()).Returns(targetHeader); + var quorumCertificateManager = new QuorumCertificateManager( + context, + blockTree, + specProvider, + epochSwitchManager, + Substitute.For()); + + if (lockQcIsNull) + context.LockQC = null; + else + context.LockQC = Build.A.QuorumCertificate().WithBlockInfo(new BlockRoundInfo(Hash256.Zero, 0, 1)).TestObject; + QuorumCertificate qc = Build.A.QuorumCertificate().WithBlockInfo(new BlockRoundInfo(targetHeader.Hash!, 2, 0)).TestObject; + quorumCertificateManager.CommitCertificate(qc); + + Assert.That(context.LockQC, Is.EqualTo(targetHeader.ExtraConsensusData!.QuorumCert)); + } + + [Test] + public void CommitCertificate_QcHasHigherRoundThanCurrent_CurrentRoundIsAdvancedByQcRoundPlusOne() + { + IEpochSwitchManager epochSwitchManager = Substitute.For(); + ISpecProvider specProvider = Substitute.For(); + IXdcReleaseSpec xdcReleaseSpec = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(xdcReleaseSpec); + XdcConsensusContext context = new XdcConsensusContext(); + IBlockTree blockTree = Substitute.For(); + XdcBlockHeader targetHeader = Build.A.XdcBlockHeader().WithGeneratedExtraConsensusData().TestObject; + blockTree.FindHeader(Arg.Any()).Returns(targetHeader); + var quorumCertificateManager = new QuorumCertificateManager( + context, + blockTree, + specProvider, + epochSwitchManager, + Substitute.For()); + context.HighestQC = Build.A.QuorumCertificate().WithBlockInfo(new BlockRoundInfo(Hash256.Zero, 0, 1)).TestObject; + QuorumCertificate qc = Build.A.QuorumCertificate().WithBlockInfo(new BlockRoundInfo(targetHeader.Hash!, 1, 0)).TestObject; + quorumCertificateManager.CommitCertificate(qc); + + Assert.That(context.CurrentRound, Is.EqualTo(qc.ProposedBlockInfo.Round + 1)); + } + + + [Test] + public void CommitCertificate_RoundsAreContinuous_GrandParentHeaderIsFinalized() + { + IEpochSwitchManager epochSwitchManager = Substitute.For(); + ISpecProvider specProvider = Substitute.For(); + IXdcReleaseSpec xdcReleaseSpec = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(xdcReleaseSpec); + + XdcBlockHeader grandParentHeader = Build.A.XdcBlockHeader().WithExtraFieldsV2( + new ExtraFieldsV2(1, Build.A.QuorumCertificate().WithBlockInfo(new BlockRoundInfo(Hash256.Zero, 0, 3)).TestObject) + ).TestObject; + XdcBlockHeader parentHeader = Build.A.XdcBlockHeader() + .WithParentHash(grandParentHeader.Hash!) + .WithExtraFieldsV2( + new ExtraFieldsV2(2, Build.A.QuorumCertificate().WithBlockInfo(new BlockRoundInfo(grandParentHeader.Hash!, 1, 4)).TestObject) + ).TestObject; + XdcBlockHeader targetHeader = Build.A.XdcBlockHeader() + .WithParentHash(parentHeader.Hash!) + .WithExtraFieldsV2( + new ExtraFieldsV2(3, Build.A.QuorumCertificate().WithBlockInfo(new BlockRoundInfo(parentHeader.Hash!, 2, 5)).TestObject) + ).WithNumber(3).TestObject; + + IBlockTree blockTree = Substitute.For(); + blockTree.FindHeader(grandParentHeader.Hash!).Returns(grandParentHeader); + blockTree.FindHeader(parentHeader.Hash!).Returns(parentHeader); + blockTree.FindHeader(targetHeader.Hash!).Returns(targetHeader); + + XdcConsensusContext context = new XdcConsensusContext(); + context.HighestCommitBlock = new BlockRoundInfo(Hash256.Zero, 0, 1); + + var quorumCertificateManager = new QuorumCertificateManager( + context, + blockTree, + specProvider, + epochSwitchManager, + Substitute.For()); + QuorumCertificate qc = Build.A.QuorumCertificate().WithBlockInfo(new BlockRoundInfo(targetHeader.Hash!, 3, 6)).TestObject; + quorumCertificateManager.CommitCertificate(qc); + + Assert.That(context.HighestCommitBlock.Hash, Is.EqualTo(grandParentHeader.Hash)); + } + + [Test] + public void CommitCertificate_RoundsAreNotContinuous_GrandParentHeaderIsNotFinalized() + { + IEpochSwitchManager epochSwitchManager = Substitute.For(); + ISpecProvider specProvider = Substitute.For(); + IXdcReleaseSpec xdcReleaseSpec = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(xdcReleaseSpec); + + XdcBlockHeader grandParentHeader = Build.A.XdcBlockHeader().WithExtraFieldsV2( + new ExtraFieldsV2(1, Build.A.QuorumCertificate().WithBlockInfo(new BlockRoundInfo(Hash256.Zero, 0, 3)).TestObject) + ).TestObject; + XdcBlockHeader parentHeader = Build.A.XdcBlockHeader() + .WithParentHash(grandParentHeader.Hash!) + .WithExtraFieldsV2( + new ExtraFieldsV2(3, Build.A.QuorumCertificate().WithBlockInfo(new BlockRoundInfo(grandParentHeader.Hash!, 1, 4)).TestObject) + ).TestObject; + XdcBlockHeader targetHeader = Build.A.XdcBlockHeader() + .WithParentHash(parentHeader.Hash!) + .WithExtraFieldsV2( + new ExtraFieldsV2(4, Build.A.QuorumCertificate().WithBlockInfo(new BlockRoundInfo(parentHeader.Hash!, 3, 5)).TestObject) + ).WithNumber(3).TestObject; + + IBlockTree blockTree = Substitute.For(); + blockTree.FindHeader(grandParentHeader.Hash!).Returns(grandParentHeader); + blockTree.FindHeader(parentHeader.Hash!).Returns(parentHeader); + blockTree.FindHeader(targetHeader.Hash!).Returns(targetHeader); + + XdcConsensusContext context = new XdcConsensusContext(); + var startFinalizedBlock = new BlockRoundInfo(Hash256.Zero, 0, 1); + context.HighestCommitBlock = startFinalizedBlock; + + var quorumCertificateManager = new QuorumCertificateManager( + context, + blockTree, + specProvider, + epochSwitchManager, + Substitute.For()); + QuorumCertificate qc = Build.A.QuorumCertificate().WithBlockInfo(new BlockRoundInfo(targetHeader.Hash!, 4, 6)).TestObject; + quorumCertificateManager.CommitCertificate(qc); + + Assert.That(context.HighestCommitBlock, Is.EqualTo(startFinalizedBlock)); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/SnapshotDecoderTests.cs b/src/Nethermind/Nethermind.Xdc.Test/SnapshotDecoderTests.cs index 95467dedec7b..6c4702d670f4 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/SnapshotDecoderTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/SnapshotDecoderTests.cs @@ -1,17 +1,14 @@ +// 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.Eip2930; -using Nethermind.Core.Extensions; -using Nethermind.Core.Test.Builders; -using Nethermind.Int256; using Nethermind.Serialization.Rlp; using Nethermind.Xdc.RLP; using Nethermind.Xdc.Types; using NUnit.Framework; -using System.Collections; using System.Collections.Generic; -using System.Text; namespace Nethermind.Xdc.Test; @@ -24,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 45a482f10776..4a8516e2ddec 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/SnapshotManagerTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/SnapshotManagerTests.cs @@ -4,42 +4,43 @@ using FluentAssertions; using Nethermind.Blockchain; using Nethermind.Core; -using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Db; -using Nethermind.Int256; +using Nethermind.Xdc.Contracts; +using Nethermind.Xdc.Spec; using Nethermind.Xdc.Types; +using NSubstitute; using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Nethermind.Xdc.Test; + internal class SnapshotManagerTests { private ISnapshotManager _snapshotManager; - private readonly IDb _snapshotDb = new MemDb(); + private IBlockTree _blockTree; + private IXdcReleaseSpec _xdcReleaseSpec; + private IDb _snapshotDb; [SetUp] public void Setup() { - IPenaltyHandler penaltyHandler = NSubstitute.Substitute.For(); - _snapshotManager = new SnapshotManager(_snapshotDb, penaltyHandler); - } + _xdcReleaseSpec = Substitute.For(); + _xdcReleaseSpec.EpochLength.Returns(900); + _xdcReleaseSpec.Gap.Returns(450); - [TearDown] - public void TearDown() - { - _snapshotDb.Dispose(); + _snapshotDb = new MemDb(); + + IPenaltyHandler penaltyHandler = Substitute.For(); + _blockTree = Substitute.For(); + _snapshotManager = new SnapshotManager(_snapshotDb, _blockTree, penaltyHandler, Substitute.For(), Substitute.For()); } [Test] public void GetSnapshot_ShouldReturnNullForNonExistentSnapshot() { // Act - var result = _snapshotManager.GetSnapshot(TestItem.KeccakD); + Snapshot? result = _snapshotManager.GetSnapshotByBlockNumber(0, _xdcReleaseSpec); // Assert result.Should().BeNull(); @@ -49,11 +50,14 @@ public void GetSnapshot_ShouldReturnNullForNonExistentSnapshot() public void GetSnapshot_ShouldRetrieveFromIfFound() { // Arrange - var snapshot = new Snapshot(2, TestItem.KeccakE, [Address.FromNumber(1)]); + const int gapBlock = 0; + XdcBlockHeader header = Build.A.XdcBlockHeader().TestObject; + var snapshot = new Snapshot(gapBlock, header.Hash!, [Address.FromNumber(1)]); _snapshotManager.StoreSnapshot(snapshot); + _blockTree.FindHeader(gapBlock).Returns(header); // Act - var result = _snapshotManager.GetSnapshot(TestItem.KeccakE); + Snapshot? result = _snapshotManager.GetSnapshotByGapNumber(gapBlock); // assert that it was retrieved from cache result.Should().BeEquivalentTo(snapshot); @@ -62,10 +66,8 @@ public void GetSnapshot_ShouldRetrieveFromIfFound() [Test] public void GetSnapshot_ShouldReturnNullForEmptyDb() { - // Arrange - var hash = TestItem.KeccakF; // Act - var result = _snapshotManager.GetSnapshot(hash); + Snapshot? result = _snapshotManager.GetSnapshotByBlockNumber(0, _xdcReleaseSpec); // Assert result.Should().BeNull(); } @@ -74,11 +76,14 @@ public void GetSnapshot_ShouldReturnNullForEmptyDb() public void GetSnapshot_ShouldRetrieveFromDbIfNotInCache() { // Arrange - var snapshot = new Snapshot(3, TestItem.KeccakG, [Address.FromNumber(3)]); + const int gapBlock = 0; + XdcBlockHeader header = Build.A.XdcBlockHeader().TestObject; + var snapshot = new Snapshot(gapBlock, header.Hash!, [Address.FromNumber(1)]); _snapshotManager.StoreSnapshot(snapshot); + _blockTree.FindHeader(gapBlock).Returns(header); // Act - var saved = _snapshotManager.GetSnapshot(TestItem.KeccakG); + Snapshot? saved = _snapshotManager.GetSnapshotByGapNumber(gapBlock); // Assert saved.Should().BeEquivalentTo(snapshot); @@ -88,11 +93,14 @@ public void GetSnapshot_ShouldRetrieveFromDbIfNotInCache() public void StoreSnapshot_ShouldStoreSnapshotInDb() { // Arrange - var snapshot = new Snapshot(4, TestItem.KeccakH, [Address.FromNumber(4)]); + const int gapBlock = 0; + XdcBlockHeader header = Build.A.XdcBlockHeader().TestObject; + var snapshot = new Snapshot(gapBlock, header.Hash!, [Address.FromNumber(1)]); + _blockTree.FindHeader(gapBlock).Returns(header); // Act _snapshotManager.StoreSnapshot(snapshot); - var fromDb = _snapshotManager.GetSnapshot(TestItem.KeccakH); + Snapshot? fromDb = _snapshotManager.GetSnapshotByGapNumber(gapBlock); // Assert fromDb.Should().BeEquivalentTo(snapshot); @@ -102,57 +110,68 @@ public void StoreSnapshot_ShouldStoreSnapshotInDb() public void GetSnapshot_ShouldReturnSnapshotIfExists() { // setup a snapshot and store it - var snapshot1 = new Snapshot(5, TestItem.KeccakA, [Address.FromNumber(5)]); + const int gapBlock1 = 0; + XdcBlockHeader header = Build.A.XdcBlockHeader().TestObject; + var snapshot1 = new Snapshot(gapBlock1, header.Hash!, [Address.FromNumber(1)]); _snapshotManager.StoreSnapshot(snapshot1); - var result = _snapshotManager.GetSnapshot(TestItem.KeccakA); + _blockTree.FindHeader(gapBlock1).Returns(header); + Snapshot? result = _snapshotManager.GetSnapshotByGapNumber(gapBlock1); - // assert that it was retrieved from db + // assert that it was retrieved from db result.Should().BeEquivalentTo(snapshot1); // store another snapshot with the same hash but different data - var snapshot2 = new Snapshot(6, TestItem.KeccakA, [Address.FromNumber(5)]); + + const int gapBlock2 = 450; + XdcBlockHeader header2 = Build.A.XdcBlockHeader().WithGeneratedExtraConsensusData(1).TestObject; + var snapshot2 = new Snapshot(gapBlock2, header2.Hash!, [Address.FromNumber(2)]); _snapshotManager.StoreSnapshot(snapshot2); - result = _snapshotManager.GetSnapshot(TestItem.KeccakA); + _blockTree.FindHeader(gapBlock2).Returns(header2); + _snapshotManager.StoreSnapshot(snapshot2); + result = _snapshotManager.GetSnapshotByBlockNumber(900, _xdcReleaseSpec); // assert that the original snapshot is still returned - result.Should().BeEquivalentTo(snapshot1); + result.Should().BeEquivalentTo(snapshot2); } - [Test] - public void GetSnapshotByHeader_ShouldReturnNullIfNotExists() + [TestCase(1, 0)] + [TestCase(451, 0)] + [TestCase(899, 0)] + [TestCase(900, 450)] + [TestCase(1349, 450)] + [TestCase(1350, 450)] + [TestCase(1800, 1350)] + public void GetSnapshot_DifferentBlockNumbers_ReturnsSnapshotFromCorrectGapNumber(int blockNumber, int expectedGapNumber) { - XdcBlockHeaderBuilder builder = Build.A.XdcBlockHeader(); - XdcBlockHeader header = builder.WithBaseFee((UInt256)1_000_000_000).TestObject; - header.Hash = TestItem.KeccakH; - // Act - var result = _snapshotManager.GetSnapshotByHeader(header); - // Assert - result.Should().BeNull(); - } + // setup a snapshot and store it + XdcBlockHeader header = Build.A.XdcBlockHeader().TestObject; + var snapshot = new Snapshot(expectedGapNumber, header.Hash!, [Address.FromNumber(1)]); + _snapshotManager.StoreSnapshot(snapshot); + _blockTree.FindHeader(expectedGapNumber).Returns(header); + Snapshot? result = _snapshotManager.GetSnapshotByBlockNumber(blockNumber, _xdcReleaseSpec); - [Test] - public void GetSnapshotByHeader_ShouldReturnNullIfHeaderIsNull() - { - // Act - var result = _snapshotManager.GetSnapshotByHeader(null); - // Assert - result.Should().BeNull(); + // assert that it was retrieved from db + result.Should().BeEquivalentTo(snapshot); } - [Test] - public void GetSnapshotByHeader_ShouldReturnSnapshotIfExists() + [TestCase(450)] + [TestCase(1350)] + public void NewHeadBlock_(int gapNumber) { - // Arrange - var snapshot = new Snapshot(7, TestItem.KeccakB, [Address.FromNumber(6)]); - _snapshotManager.StoreSnapshot(snapshot); - XdcBlockHeaderBuilder builder = Build.A.XdcBlockHeader(); - XdcBlockHeader header = builder.WithBaseFee((UInt256)1_000_000_000).TestObject; - header.Hash = TestItem.KeccakB; - - // Act - var result = _snapshotManager.GetSnapshotByHeader(header); - - // Assert - result.Should().BeEquivalentTo(snapshot); + 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/SpecialTransactionsTests.cs b/src/Nethermind/Nethermind.Xdc.Test/SpecialTransactionsTests.cs new file mode 100644 index 000000000000..fcdf9bda6a54 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/SpecialTransactionsTests.cs @@ -0,0 +1,987 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Autofac; +using FluentAssertions; +using FluentAssertions.Extensions; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Headers; +using Nethermind.Blockchain.Tracing; +using Nethermind.Consensus; +using Nethermind.Consensus.Processing; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Blockchain; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Db; +using Nethermind.Evm; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.Tracing.State; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.TxPool; +using Nethermind.Xdc.Contracts; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Test.Helpers; +using NSubstitute; +using NSubstitute.ExceptionExtensions; +using NUnit.Framework; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nethermind.Xdc.Test; + +internal class SpecialTransactionsTests +{ + private bool IsTimeForOnchainSignature(IXdcReleaseSpec spec, long blockNumber) + { + return blockNumber % spec.MergeSignRange == 0; + } + + private Task ProposeBatchTransferTxFrom(PrivateKey source, PrivateKey destination, UInt256 amount, int count, XdcTestBlockchain chain) + { + return Task.Run(() => + { + (PrivateKey, PrivateKey) swap(PrivateKey a, PrivateKey b) => (b, a); + + for (int i = 0; i < count; i++) + { + (source, destination) = swap(source, destination); + CreateTransferTxFrom(source, destination, amount, chain); + } + }); + } + + private Transaction CreateTransferTxFrom(PrivateKey source, PrivateKey destination, UInt256 amount, XdcTestBlockchain chain) + { + Transaction tx = Build.A.Transaction + .WithSenderAddress(source.Address) + .WithTo(destination.Address) + .WithValue(amount) + .WithType(TxType.Legacy) + .TestObject; + + var signer = new Signer(chain.SpecProvider.ChainId, source, NullLogManager.Instance); + signer.Sign(tx); + + tx.Hash = tx.CalculateHash(); + + chain.TxPool.SubmitTx(tx, TxHandlingOptions.None); + + return tx; + } + + private PrivateKey[] FilledAccounts(XdcTestBlockchain chain) + { + var genesisSpec = chain.SpecProvider.GenesisSpec as XdcReleaseSpec; + var pks = chain.MasterNodeCandidates + .Where(k => genesisSpec!.GenesisMasterNodes.Contains(k.Address)); + return pks.ToArray(); + } + + + [TestCase(false)] + [TestCase(true)] + public async Task SignTx_Is_Dispatched_On_MergeSignRange_Block(bool enableEip1559) + { + var blockChain = await XdcTestBlockchain.Create(1, true); + + var mergeSignBlockRange = 5; + + blockChain.ChangeReleaseSpec((spec) => + { + spec.MergeSignRange = mergeSignBlockRange; + spec.IsEip1559Enabled = enableEip1559; + }); + + blockChain.StartHotStuffModule(); + + XdcBlockHeader? head = blockChain.BlockTree.Head!.Header as XdcBlockHeader; + do + { + await blockChain.TriggerAndSimulateBlockProposalAndVoting(); + head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header; + } + while (!IsTimeForOnchainSignature(blockChain.SpecProvider.GetXdcSpec(head), head.Number - 1)); + + Assert.That(blockChain.BlockTree.Head.Number, Is.EqualTo(mergeSignBlockRange + 1)); + + Transaction[] pendingTxs = blockChain.TxPool.GetPendingTransactions(); + + var spec = (XdcReleaseSpec)blockChain.SpecProvider.GetFinalSpec(); + var specialTxs = pendingTxs.Where(r => r.To == spec.BlockSignerContract); + + Assert.That(specialTxs, Is.Not.Empty); + + var specialTx = specialTxs.First(); + + var blockTarget = (long)(new UInt256(specialTx.Data.Span.Slice(4, 32), true)); + + Assert.That(blockTarget, Is.EqualTo(mergeSignBlockRange)); + } + + [TestCase(true)] + [TestCase(false)] + public async Task SignTx_Is_Not_Dispatched_Outside_MergeSignRange_Block(bool enableEip1559) + { + var blockChain = await XdcTestBlockchain.Create(1, true); + + var mergeSignBlockRange = 5; + + blockChain.ChangeReleaseSpec((spec) => + { + spec.MergeSignRange = mergeSignBlockRange; + spec.IsEip1559Enabled = enableEip1559; + }); + + blockChain.StartHotStuffModule(); + + XdcBlockHeader? head = blockChain.BlockTree.Head!.Header as XdcBlockHeader; + do + { + await blockChain.TriggerAndSimulateBlockProposalAndVoting(); + head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header; + } + while (!IsTimeForOnchainSignature(blockChain.SpecProvider.GetXdcSpec(head), head.Number + 1)); + + var receipts = blockChain.TxPool.GetPendingTransactions(); + + var spec = (XdcReleaseSpec)blockChain.SpecProvider.GetFinalSpec(); + receipts.Any(r => r.To == spec.BlockSignerContract + || r.To == spec.RandomizeSMCBinary).Should().BeFalse(); + } + + [TestCase(false)] + [TestCase(true)] + public async Task Special_Tx_Is_Executed_Before_Normal_Txs(bool enableEip1559) + { + var blockChain = await XdcTestBlockchain.Create(1, true); + + var mergeSignBlockRange = 5; + + blockChain.ChangeReleaseSpec((spec) => + { + spec.MergeSignRange = mergeSignBlockRange; + spec.IsEip1559Enabled = enableEip1559; + }); + + blockChain.StartHotStuffModule(); + + XdcBlockHeader? head = blockChain.BlockTree.Head!.Header as XdcBlockHeader; + var spec = blockChain.SpecProvider.GetXdcSpec(head!); + + var random = new Random(); + + var accounts = FilledAccounts(blockChain); + + for (int i = 1; i < spec.MergeSignRange + 2; i++) + { + if (head!.Number == mergeSignBlockRange + 1) + { + var source = accounts.ElementAt(random.Next() % accounts.Length); + var dest = accounts.Except([source]).ElementAt(random.Next() % (accounts.Length - 1)); + await ProposeBatchTransferTxFrom(source, dest, 1, 2, blockChain); + } + + await blockChain.TriggerAndSimulateBlockProposalAndVoting(); + head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header; + } + + var block = (XdcBlockHeader)blockChain.BlockTree.Head.Header; + spec = blockChain.SpecProvider.GetXdcSpec(block!); + + var receipts = blockChain.ReceiptStorage.Get(block.Hash!); + + Assert.That(receipts, Is.Not.Empty); + Assert.That(receipts.Length, Is.GreaterThan(1)); + + bool onlyEncounteredSpecialTx = true; + foreach (var transaction in receipts) + { + if (transaction.Recipient == spec.BlockSignerContract || transaction.Recipient == spec.RandomizeSMCBinary) + { + if (!onlyEncounteredSpecialTx) + { + // we encountered a normal transaction before so special txs are not lumped at the start + Assert.Fail(); + } + } + else + { + onlyEncounteredSpecialTx = false; + } + } + + Assert.Pass(); + } + + [TestCase(false, false)] + [TestCase(false, true)] + [TestCase(true, false)] + [TestCase(true, false)] + public async Task Tx_With_With_BlackListed_Sender_Fails_Validation(bool blackListingActivated, bool enableEip1559) + { + var blockChain = await XdcTestBlockchain.Create(5, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.BlackListedAddresses = [blockChain.Signer.Address]; + spec.IsEip1559Enabled = enableEip1559; + spec.IsBlackListingEnabled = blackListingActivated; + spec.IsTipTrc21FeeEnabled = false; + }); + + var moqVm = new VirtualMachine(new BlockhashProvider(new BlockhashCache(blockChain.Container.Resolve(), NullLogManager.Instance), blockChain.MainWorldState, NullLogManager.Instance), blockChain.SpecProvider, NullLogManager.Instance); + + var transactionProcessor = new XdcTransactionProcessor(BlobBaseFeeCalculator.Instance, blockChain.SpecProvider, blockChain.MainWorldState, moqVm, Substitute.For(), NullLogManager.Instance, Substitute.For()); + + + XdcBlockHeader head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec(head); + + blockChain.MainWorldState.BeginScope(head); + + moqVm.SetBlockExecutionContext(new BlockExecutionContext(head, spec)); + + var txSign = SignTransactionManager.CreateTxSign((UInt256)head.Number, head.Hash!, blockChain.TxPool.GetLatestPendingNonce(TestItem.AddressA), spec.BlockSignerContract, blockChain.Signer.Address); + await blockChain.Signer.Sign(txSign); + + TransactionResult? result = null; + + try + { + result = transactionProcessor.Execute(txSign, NullTxTracer.Instance); + } + catch + { + result = TransactionResult.Ok; + } + + if (blackListingActivated) + { + result.Value.Error.Should().Be(XdcTransactionResult.ContainsBlacklistedAddressError); + } + else + { + result.Value.Error.Should().NotBe(XdcTransactionResult.ContainsBlacklistedAddressError); + } + } + + + [TestCase(false, false)] + [TestCase(false, true)] + [TestCase(true, false)] + [TestCase(true, true)] + public async Task Tx_With_With_BlackListed_Receiver_Fails_Validation(bool blackListingActivated, bool enableEip1559) + { + + var blockChain = await XdcTestBlockchain.Create(5, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.BlackListedAddresses = [TestItem.AddressA]; + spec.IsEip1559Enabled = enableEip1559; + spec.IsBlackListingEnabled = blackListingActivated; + spec.IsTipTrc21FeeEnabled = false; + }); + var moqVm = new VirtualMachine(new BlockhashProvider(new BlockhashCache(blockChain.Container.Resolve(), NullLogManager.Instance), blockChain.MainWorldState, NullLogManager.Instance), blockChain.SpecProvider, NullLogManager.Instance); + + var transactionProcessor = new XdcTransactionProcessor( + BlobBaseFeeCalculator.Instance, + blockChain.SpecProvider, + blockChain.MainWorldState, + moqVm, + Substitute.For(), + NullLogManager.Instance, + Substitute.For()); + + + XdcBlockHeader head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec(head); + + blockChain.MainWorldState.BeginScope(head); + + moqVm.SetBlockExecutionContext(new BlockExecutionContext(head, spec)); + + var nonce = blockChain.ReadOnlyState.GetNonce(blockChain.Signer.Address); + + Transaction tx = Build.A.Transaction + .WithNonce(nonce) + .WithSenderAddress(blockChain.Signer.Address) + .WithTo(TestItem.AddressA).TestObject; + await blockChain.Signer.Sign(tx); + + TransactionResult? result = null; + + try + { + result = transactionProcessor.Execute(tx, NullTxTracer.Instance); + } + catch + { + result = TransactionResult.Ok; + } + + if (blackListingActivated) + { + result.Value.Error.Should().Be(XdcTransactionResult.ContainsBlacklistedAddressError); + } + else + { + result.Value.Error.Should().NotBe(XdcTransactionResult.ContainsBlacklistedAddressError); + } + } + + [TestCase(false)] + [TestCase(true)] + public async Task Malformed_WrongLength_SpecialTx_Fails_Validation(bool enableEip1559) + { + var blockChain = await XdcTestBlockchain.Create(5, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.IsEip1559Enabled = enableEip1559; + spec.IsTipTrc21FeeEnabled = false; + }); + + var moqVm = new VirtualMachine(new BlockhashProvider(new BlockhashCache(blockChain.Container.Resolve(), NullLogManager.Instance), blockChain.MainWorldState, NullLogManager.Instance), blockChain.SpecProvider, NullLogManager.Instance); + + XdcBlockHeader head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec(head); + + blockChain.MainWorldState.BeginScope(head); + + moqVm.SetBlockExecutionContext(new BlockExecutionContext(head, spec)); + + Transaction? tx = SignTransactionManager.CreateTxSign((UInt256)head.Number, head.Hash!, blockChain.TxPool.GetLatestPendingNonce(blockChain.Signer.Address), spec.BlockSignerContract, blockChain.Signer.Address); + + // damage the data field in the tx + tx.Data = Enumerable.Range(0, 48).Select(i => (byte)i).ToArray(); + + await blockChain.Signer.Sign(tx); + + var result = blockChain.TxPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); + + Assert.That(result, Is.EqualTo(AcceptTxResult.Invalid)); + } + + [TestCase(true)] + [TestCase(false)] + public async Task Malformed_SenderNonceLesserThanTxNonce_SignTx_Fails_Validation(bool enableEip1559) + { + var blockChain = await XdcTestBlockchain.Create(5, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.IsEip1559Enabled = enableEip1559; + spec.IsTipTrc21FeeEnabled = false; + }); + + var moqVm = new VirtualMachine(new BlockhashProvider(new BlockhashCache(blockChain.Container.Resolve(), NullLogManager.Instance), blockChain.MainWorldState, NullLogManager.Instance), blockChain.SpecProvider, NullLogManager.Instance); + + var transactionProcessor = new XdcTransactionProcessor( + BlobBaseFeeCalculator.Instance, + blockChain.SpecProvider, + blockChain.MainWorldState, + moqVm, + Substitute.For(), + NullLogManager.Instance, + Substitute.For()); + + + XdcBlockHeader head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec(head); + + blockChain.MainWorldState.BeginScope(head); + + moqVm.SetBlockExecutionContext(new BlockExecutionContext(head, spec)); + + + blockChain.MainWorldState.IncrementNonce(blockChain.Signer.Address); + var nonce = blockChain.MainWorldState.GetNonce(blockChain.Signer.Address); + + + Transaction txWithSmallerNonce = SignTransactionManager.CreateTxSign((UInt256)head.Number, head.Hash!, nonce - 1, spec.BlockSignerContract, blockChain.Signer.Address); + + // damage the data field in the tx + txWithSmallerNonce.Data = Enumerable.Range(0, 48).Select(i => (byte)i).ToArray(); + + await blockChain.Signer.Sign(txWithSmallerNonce); + + TransactionResult? result = null; + + try + { + result = transactionProcessor.Execute(txWithSmallerNonce, NullTxTracer.Instance); + } + catch + { + result = TransactionResult.Ok; + } + + result.Value.Error.Should().Be(XdcTransactionResult.NonceTooLowError); + } + + [TestCase(true)] + [TestCase(false)] + public async Task Malformed_SenderNonceBiggerLesserThanTxNonce_SignTx_Fails_Validation(bool enableEip1559) + { + var blockChain = await XdcTestBlockchain.Create(5, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.IsEip1559Enabled = enableEip1559; + spec.IsTipTrc21FeeEnabled = false; + }); + + var moqVm = new VirtualMachine(new BlockhashProvider(new BlockhashCache(blockChain.Container.Resolve(), NullLogManager.Instance), blockChain.MainWorldState, NullLogManager.Instance), blockChain.SpecProvider, NullLogManager.Instance); + + var transactionProcessor = new XdcTransactionProcessor( + BlobBaseFeeCalculator.Instance, + blockChain.SpecProvider, + blockChain.MainWorldState, + moqVm, + Substitute.For(), + NullLogManager.Instance, + Substitute.For()); + + + XdcBlockHeader head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec(head); + + blockChain.MainWorldState.BeginScope(head); + + moqVm.SetBlockExecutionContext(new BlockExecutionContext(head, spec)); + + + blockChain.MainWorldState.IncrementNonce(blockChain.Signer.Address); + var nonce = blockChain.MainWorldState.GetNonce(blockChain.Signer.Address); + + + Transaction txWithBiggerNonce = SignTransactionManager.CreateTxSign((UInt256)head.Number, head.Hash!, nonce + 1, spec.BlockSignerContract, blockChain.Signer.Address); + + // damage the data field in the tx + txWithBiggerNonce.Data = Enumerable.Range(0, 48).Select(i => (byte)i).ToArray(); + + await blockChain.Signer.Sign(txWithBiggerNonce); + + TransactionResult? result = null; + + try + { + result = transactionProcessor.Execute(txWithBiggerNonce, NullTxTracer.Instance); + } + catch + { + result = TransactionResult.Ok; + } + + result.Value.Error.Should().Be(XdcTransactionResult.NonceTooHighError); + } + + + [TestCase(true)] + [TestCase(false)] + public async Task Malformed_SenderNonceEqualLesserThanTxNonce_SignTx_Fails_Validation(bool enableEip1559) + { + var blockChain = await XdcTestBlockchain.Create(5, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.IsEip1559Enabled = false; + spec.IsTipTrc21FeeEnabled = false; + }); + + var moqVm = new VirtualMachine(new BlockhashProvider(new BlockhashCache(blockChain.Container.Resolve(), NullLogManager.Instance), blockChain.MainWorldState, NullLogManager.Instance), blockChain.SpecProvider, NullLogManager.Instance); + + var transactionProcessor = new XdcTransactionProcessor( + BlobBaseFeeCalculator.Instance, + blockChain.SpecProvider, + blockChain.MainWorldState, + moqVm, + Substitute.For(), + NullLogManager.Instance, + Substitute.For()); + + + XdcBlockHeader head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec(head); + + blockChain.MainWorldState.BeginScope(head); + + moqVm.SetBlockExecutionContext(new BlockExecutionContext(head, spec)); + + + blockChain.MainWorldState.IncrementNonce(blockChain.Signer.Address); + var nonce = blockChain.MainWorldState.GetNonce(blockChain.Signer.Address); + + Transaction validNonceTx = SignTransactionManager.CreateTxSign((UInt256)head.Number, head.Hash!, nonce, spec.BlockSignerContract, blockChain.Signer.Address); + + // damage the data field in the tx + validNonceTx.Data = Enumerable.Range(0, 48).Select(i => (byte)i).ToArray(); + + await blockChain.Signer.Sign(validNonceTx); + + TransactionResult? result = null; + + try + { + result = transactionProcessor.Execute(validNonceTx, NullTxTracer.Instance); + } + catch + { + result = TransactionResult.Ok; + } + + result.Value.Error.Should().NotBe(XdcTransactionResult.NonceTooHighError); + result.Value.Error.Should().NotBe(XdcTransactionResult.NonceTooLowError); + } + + [TestCase(true)] + [TestCase(false)] + public async Task Malformed_WrongBlockNumber_BlockTooHigh_SignTx_Fails_Validation(bool enableEip1559) + { + var epochLength = 10; + var blockChain = await XdcTestBlockchain.Create(epochLength * 3, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.IsEip1559Enabled = enableEip1559; + spec.EpochLength = epochLength; + }); + + XdcBlockHeader head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec(head); + + blockChain.MainWorldState.BeginScope(head); + + UInt256 tooHighBlockNumber = (UInt256)head.Number + 1; + Transaction txTooHigh = SignTransactionManager.CreateTxSign( + tooHighBlockNumber, + head.Hash!, + blockChain.TxPool.GetLatestPendingNonce(blockChain.Signer.Address), + spec.BlockSignerContract, + blockChain.Signer.Address); + + await blockChain.Signer.Sign(txTooHigh); + + var result = blockChain.TxPool.SubmitTx(txTooHigh, TxHandlingOptions.PersistentBroadcast); + + Assert.That(result, Is.EqualTo(AcceptTxResult.Invalid)); + } + + [TestCase(true)] + [TestCase(false)] + public async Task Malformed_WrongBlockNumber_BlockTooLow_SignTx_Fails_Validation(bool enableEip1559) + { + var epochLength = 10; + var blockChain = await XdcTestBlockchain.Create(epochLength * 3, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.IsEip1559Enabled = enableEip1559; + spec.EpochLength = epochLength; + }); + + XdcBlockHeader head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec(head); + + blockChain.MainWorldState.BeginScope(head); + + long lowerBound = head.Number - (spec.EpochLength * 2); + UInt256 tooLowBlockNumber = (UInt256)lowerBound; + Transaction txTooLow = SignTransactionManager.CreateTxSign( + tooLowBlockNumber, + head.Hash!, + blockChain.TxPool.GetLatestPendingNonce(blockChain.Signer.Address), + spec.BlockSignerContract, + blockChain.Signer.Address); + + await blockChain.Signer.Sign(txTooLow); + + var result = blockChain.TxPool.SubmitTx(txTooLow, TxHandlingOptions.PersistentBroadcast); + + Assert.That(result, Is.EqualTo(AcceptTxResult.Invalid)); + } + + [TestCase(false)] + [TestCase(true)] + public async Task Malformed_WrongBlockNumber_BlockWithinRange_SignTx_Fails_Validation(bool enableEip1559) + { + var epochLength = 10; + var blockChain = await XdcTestBlockchain.Create(epochLength * 3, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.IsEip1559Enabled = enableEip1559; + spec.EpochLength = epochLength; + }); + + XdcBlockHeader head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec(head); + + // Header.Number is current block; we must pass: + // blkNumber < header.Number + // blkNumber > header.Number - (EpochLength * 2) + // + // Pick something comfortably in the middle of that interval. + long upper = head.Number - 1; + long lower = head.Number - (spec.EpochLength * 2) + 1; + long validBlockNumber = lower + (upper - lower) / 2; + + Transaction tx = + SignTransactionManager.CreateTxSign( + (UInt256)validBlockNumber, + head.Hash!, + blockChain.TxPool.GetLatestPendingNonce(blockChain.Signer.Address), + spec.BlockSignerContract, + blockChain.Signer.Address); + + await blockChain.Signer.Sign(tx); + + var result = blockChain.TxPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); + + Assert.That(result, Is.EqualTo(AcceptTxResult.Accepted)); + } + + [TestCase(true)] + [TestCase(false)] + public async Task SignTx_Increments_Nonce_And_Emits_Log_And_Consume_NoGas(bool enableEip1559) + { + var blockChain = await XdcTestBlockchain.Create(5, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.IsEip1559Enabled = enableEip1559; + spec.IsTipTrc21FeeEnabled = false; + }); + + var moqVm = new VirtualMachine(new BlockhashProvider(new BlockhashCache(blockChain.Container.Resolve(), NullLogManager.Instance), blockChain.MainWorldState, NullLogManager.Instance), blockChain.SpecProvider, NullLogManager.Instance); + + var transactionProcessor = new XdcTransactionProcessor( + BlobBaseFeeCalculator.Instance, + blockChain.SpecProvider, + blockChain.MainWorldState, + moqVm, + Substitute.For(), + NullLogManager.Instance, + Substitute.For()); + + + Block head = blockChain.BlockTree.Head!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec((XdcBlockHeader)head.Header); + + blockChain.MainWorldState.BeginScope(head.Header); + + moqVm.SetBlockExecutionContext(new BlockExecutionContext(head.Header, spec)); + + UInt256 initialNonce = blockChain.MainWorldState.GetNonce(blockChain.Signer.Address); + UInt256 initialBalance = blockChain.MainWorldState.GetBalance(blockChain.Signer.Address); + + Transaction? tx = SignTransactionManager.CreateTxSign((UInt256)head.Number - 1, head.ParentHash!, initialNonce, spec.BlockSignerContract, blockChain.Signer.Address); + + await blockChain.Signer.Sign(tx); + + var receiptsTracer = new BlockReceiptsTracer(); + + var initialCountOfReceipts = receiptsTracer.TxReceipts.Length; + + receiptsTracer.StartNewBlockTrace(head); + receiptsTracer.StartNewTxTrace(tx); + + TransactionResult? result = transactionProcessor.Execute(tx, receiptsTracer); + + Assert.That(result.Value.EvmExceptionType, Is.EqualTo(EvmExceptionType.None), $"specialTx to {spec.BlockSignerContract} should succeed"); + + + receiptsTracer.EndTxTrace(); + receiptsTracer.EndBlockTrace(); + + UInt256 finalNonce = blockChain.MainWorldState.GetNonce(blockChain.Signer.Address); + UInt256 finalBalance = blockChain.MainWorldState.GetBalance(blockChain.Signer.Address); + + Assert.That(finalNonce, Is.EqualTo(initialNonce + 1)); + Assert.That(finalBalance, Is.EqualTo(initialBalance)); + + var finalCountOfReceipts = receiptsTracer.TxReceipts.Length; + + Assert.That(finalCountOfReceipts, Is.EqualTo(initialCountOfReceipts + 1)); + + var finalReceipt = receiptsTracer.TxReceipts[^1]; + + Assert.That(finalReceipt?.Logs?.Length, Is.EqualTo(1)); + + Assert.That(finalReceipt?.Logs?[0].Address, Is.EqualTo(spec.BlockSignerContract)); + } + + [TestCase(true)] + [TestCase(false)] + public async Task Valid_SpecialTx_NotSign_Call_EmptyTx_Handler(bool enableEip1559) + { + var blockChain = await XdcTestBlockchain.Create(5, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.IsEip1559Enabled = enableEip1559; + spec.IsTipTrc21FeeEnabled = false; + + spec.IsTIPXDCXMiner = true; + + spec.TradingStateAddressBinary = new Address("0x00000000000000000000000000000000b000091"); + spec.XDCXAddressBinary = new Address("0x00000000000000000000000000000000b000092"); + spec.XDCXLendingAddressBinary = new Address("0x00000000000000000000000000000000b000093"); + spec.XDCXLendingFinalizedTradeAddressBinary = new Address("0x00000000000000000000000000000000b000094"); + }); + + var moqVm = new VirtualMachine(new BlockhashProvider(new BlockhashCache(blockChain.Container.Resolve(), NullLogManager.Instance), blockChain.MainWorldState, NullLogManager.Instance), blockChain.SpecProvider, NullLogManager.Instance); + + var transactionProcessor = new XdcTransactionProcessor( + BlobBaseFeeCalculator.Instance, + blockChain.SpecProvider, + blockChain.MainWorldState, + moqVm, + Substitute.For(), + NullLogManager.Instance, + Substitute.For()); + + + Block head = blockChain.BlockTree.Head!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec((XdcBlockHeader)head.Header); + + blockChain.MainWorldState.BeginScope(head.Header); + + moqVm.SetBlockExecutionContext(new BlockExecutionContext(head.Header, spec)); + + Address[] addresses = [ + spec.TradingStateAddressBinary, + spec.XDCXLendingAddressBinary, + spec.XDCXAddressBinary, + spec.XDCXLendingFinalizedTradeAddressBinary, + ]; + + var receiptsTracer = new BlockReceiptsTracer(); + + receiptsTracer.StartNewBlockTrace(head); + foreach (var address in addresses) + { + UInt256 initialNonce = blockChain.MainWorldState.GetNonce(blockChain.Signer.Address); + UInt256 initialBalance = blockChain.MainWorldState.GetBalance(blockChain.Signer.Address); + + Transaction? tx = Build.A.Transaction + .WithType(TxType.Legacy) + .WithSenderAddress(blockChain.Signer.Address) + .WithTo(address).TestObject; + + await blockChain.Signer.Sign(tx); + + var initialCountOfReceipts = receiptsTracer.TxReceipts.Length; + + receiptsTracer.StartNewTxTrace(tx); + + TransactionResult? result = transactionProcessor.Execute(tx, receiptsTracer); + + Assert.That(result.Value.EvmExceptionType, Is.EqualTo(EvmExceptionType.None), $"specialTx to {address} should succeed"); + + receiptsTracer.EndTxTrace(); + + UInt256 finalNonce = blockChain.MainWorldState.GetNonce(blockChain.Signer.Address); + UInt256 finalBalance = blockChain.MainWorldState.GetBalance(blockChain.Signer.Address); + + Assert.That(finalNonce, Is.EqualTo(initialNonce), $"specialTx to {address} does not increment nonce, initialNonce: {initialNonce}, finalNonce: {finalNonce}"); + + Assert.That(initialBalance, Is.EqualTo(finalBalance), $"specialTx to {address} does not increment nonce, initialBalance: {initialNonce}, finalBalance: {finalNonce}"); + + var finalCountOfReceipts = receiptsTracer.TxReceipts.Length; + + Assert.That(finalCountOfReceipts, Is.EqualTo(initialCountOfReceipts + 1)); + + var finalReceipt = receiptsTracer.TxReceipts[^1]; + + Assert.That(finalReceipt?.Logs?.Length, Is.EqualTo(1)); + + Assert.That(finalReceipt?.Logs?[0].Address, Is.EqualTo(address)); + } + receiptsTracer.EndBlockTrace(); + + Assert.That(receiptsTracer.TxReceipts.Length, Is.EqualTo(addresses.Length)); + } + + [TestCase(false)] + [TestCase(true)] + public async Task SignTx_With_ZeroBalance_CanBeIncludedInBlock_And_ReceiptIsEmitted(bool enableEip1559) + { + var chain = await XdcTestBlockchain.Create(); + + chain.ChangeReleaseSpec((spec) => + { + spec.IsEip1559Enabled = enableEip1559; + }); + + 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 = chain.Signer.Key!; + Address owner = signer915.Address; + + // Ensure signer has 0 balance BEFORE the block that includes the SignTx is committed + using (chain.MainWorldState.BeginScope(header915!)) + { + chain.MainWorldState.CreateAccountIfNotExists(chain.Signer.Address, UInt256.Zero); + chain.MainWorldState.Commit((IReleaseSpec)spec, NullStateTracer.Instance); + } + + var signTxManager = chain.Container.Resolve(); + await signTxManager.SubmitTransactionSign(head, spec); + + await chain.AddBlockMayHaveExtraTx(); + + var block = (XdcBlockHeader)chain.BlockTree.Head!.Header; + + // Get receipts of the block after (i.e., the block that included the SignTx) + var receipts = chain.ReceiptStorage.Get(block.Hash!); + Assert.That(receipts, Is.Not.Null); + Assert.That(receipts, Is.Not.Empty); + + receipts.Any(r => r.Recipient == spec.BlockSignerContract).Should().BeTrue(); + } + + [TestCase(false)] + [TestCase(true)] + public async Task RandomizeTx_IncrementNonce_And_Is_Treated_As_Free(bool enableEip1559) + { + var blockChain = await XdcTestBlockchain.Create(5, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.IsEip1559Enabled = enableEip1559; + }); + + var moqVm = new VirtualMachine(new BlockhashProvider(new BlockhashCache(blockChain.Container.Resolve(), NullLogManager.Instance), blockChain.MainWorldState, NullLogManager.Instance), blockChain.SpecProvider, NullLogManager.Instance); + + var transactionProcessor = blockChain.TxProcessor as XdcTransactionProcessor; + + Block head = blockChain.BlockTree.Head!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec((XdcBlockHeader)head.Header); + + // Ensure signer has 0 balance BEFORE the block that includes the SignTx is committed + using (blockChain.MainWorldState.BeginScope(head.Header!)) + { + byte[] dummyRuntimeCode = [0x60, 0x00, 0x60, 0x00, 0x01, 0x50, 0x00]; // PUSH1 PUSH1 ADD POP STOP + blockChain.MainWorldState.CreateAccountIfNotExists(spec.RandomizeSMCBinary, UInt256.Zero); + blockChain.MainWorldState.InsertCode( + spec.RandomizeSMCBinary, + Keccak.Compute(dummyRuntimeCode), + dummyRuntimeCode, + spec); + } + + blockChain.MainWorldState.BeginScope(head.Header); + moqVm.SetBlockExecutionContext(new BlockExecutionContext(head.Header, spec)); + + UInt256 initialNonce = blockChain.MainWorldState.GetNonce(blockChain.Signer.Address); + UInt256 initialBalance = blockChain.MainWorldState.GetBalance(blockChain.Signer.Address); + + var tx = Build.A.Transaction + .WithType(TxType.Legacy) + .WithSenderAddress(blockChain.Signer.Address) + .WithTo(spec.RandomizeSMCBinary) + .WithGasPrice(UInt256.Zero) + .WithValue(UInt256.Zero) + .TestObject; + + await blockChain.Signer.Sign(tx); + + var receiptsTracer = new BlockReceiptsTracer(); + receiptsTracer.StartNewBlockTrace(head); + receiptsTracer.StartNewTxTrace(tx); + + TransactionResult result = transactionProcessor!.Execute(tx, receiptsTracer); + + receiptsTracer.EndTxTrace(); + receiptsTracer.EndBlockTrace(); + + result.Error.Should().Be(TransactionResult.ErrorType.None); + + UInt256 finalNonce = blockChain.MainWorldState.GetNonce(blockChain.Signer.Address); + UInt256 finalBalance = blockChain.MainWorldState.GetBalance(blockChain.Signer.Address); + + finalNonce.Should().Be(initialNonce + 1); + finalBalance.Should().Be(initialBalance); // zero gas price => no balance change + + receiptsTracer.TxReceipts.Length.Should().NotBe(0); + var receipt = receiptsTracer.TxReceipts[^1]; + receipt.GasUsed.Should().BeGreaterThan(0); + } + + + [TestCase(true)] + [TestCase(false)] + public async Task RandomizeTx_From_ZeroBalance_Account(bool enableEip1559) + { + var blockChain = await XdcTestBlockchain.Create(5, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.IsEip1559Enabled = enableEip1559; + }); + + var moqVm = new VirtualMachine(new BlockhashProvider(new BlockhashCache(blockChain.Container.Resolve(), NullLogManager.Instance), blockChain.MainWorldState, NullLogManager.Instance), blockChain.SpecProvider, NullLogManager.Instance); + + var transactionProcessor = blockChain.TxProcessor as XdcTransactionProcessor; + + Block head = (Block)blockChain.BlockTree.Head!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec((XdcBlockHeader)head.Header); + + moqVm.SetBlockExecutionContext(new BlockExecutionContext(head.Header, spec)); + + using (blockChain.MainWorldState.BeginScope(head.Header!)) + { + blockChain.MainWorldState.CreateAccountIfNotExists(blockChain.Signer.Address, UInt256.Zero); + blockChain.MainWorldState.Commit((IReleaseSpec)spec, NullStateTracer.Instance); + + byte[] dummyRuntimeCode = [0x60, 0x00, 0x60, 0x00, 0x01, 0x50, 0x00]; // PUSH1 PUSH1 ADD POP STOP + blockChain.MainWorldState.CreateAccountIfNotExists(spec.RandomizeSMCBinary, UInt256.Zero); + blockChain.MainWorldState.InsertCode( + spec.RandomizeSMCBinary, + Keccak.Compute(dummyRuntimeCode), + dummyRuntimeCode, + spec); + } + + blockChain.MainWorldState.BeginScope(head.Header); + + UInt256 initialNonce = blockChain.MainWorldState.GetNonce(blockChain.Signer.Address); + UInt256 initialBalance = blockChain.MainWorldState.GetBalance(blockChain.Signer.Address); + + var tx = Build.A.Transaction + .WithType(TxType.Legacy) + .WithSenderAddress(blockChain.Signer.Address) + .WithTo(spec.RandomizeSMCBinary) + .WithGasPrice(UInt256.Zero) + .WithValue(UInt256.Zero) + .TestObject; + + await blockChain.Signer.Sign(tx); + + var receiptsTracer = new BlockReceiptsTracer(); + receiptsTracer.StartNewBlockTrace(head); + receiptsTracer.StartNewTxTrace(tx); + + TransactionResult result = transactionProcessor!.Execute(tx, receiptsTracer); + + receiptsTracer.EndTxTrace(); + receiptsTracer.EndBlockTrace(); + + result.Error.Should().Be(TransactionResult.ErrorType.None); + + UInt256 finalNonce = blockChain.MainWorldState.GetNonce(blockChain.Signer.Address); + UInt256 finalBalance = blockChain.MainWorldState.GetBalance(blockChain.Signer.Address); + + finalNonce.Should().Be(initialNonce + 1); + finalBalance.Should().Be(initialBalance); // zero gas price => no balance change + + receiptsTracer.TxReceipts.Length.Should().NotBe(0); + var receipt = receiptsTracer.TxReceipts[^1]; + receipt.GasUsed.Should().BeGreaterThan(0); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/SubnetSnapshotDecoderTests.cs b/src/Nethermind/Nethermind.Xdc.Test/SubnetSnapshotDecoderTests.cs new file mode 100644 index 000000000000..4c72f05d7724 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/SubnetSnapshotDecoderTests.cs @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.RLP; +using Nethermind.Xdc.Types; +using NUnit.Framework; +using System.Collections.Generic; + +namespace Nethermind.Xdc.Test; + +[TestFixture] +public class SubnetSnapshotDecoderTests +{ + private static IEnumerable Snapshots => [ + new SubnetSnapshot(1, Keccak.EmptyTreeHash, [], []), + new SubnetSnapshot(3, Keccak.EmptyTreeHash, [Address.FromNumber(1), Address.FromNumber(2)], [Address.FromNumber(3), Address.FromNumber(4)]), + ]; + + [Test, TestCaseSource(nameof(Snapshots))] + public void RoundTrip_ValueDecoder(SubnetSnapshot original) + { + SubnetSnapshotDecoder encoder = new(); + RlpStream rlpStream = new(encoder.GetLength(original, RlpBehaviors.None)); + encoder.Encode(rlpStream, original); + rlpStream.Position = 0; + + Rlp.ValueDecoderContext ctx = rlpStream.Data.AsSpan().AsRlpValueContext(); + SubnetSnapshot decoded = encoder.Decode(ref ctx)!; + if (original is null) + { + decoded.Should().BeNull(); + } + else + { + decoded.Should().BeEquivalentTo(original); + } + } + + [Test, TestCaseSource(nameof(Snapshots))] + public void RoundTrip_stream(SubnetSnapshot original) + { + SubnetSnapshotDecoder encoder = new(); + RlpStream stream = new(encoder.GetLength(original, RlpBehaviors.None)); + encoder.Encode(stream, original); + + stream.Reset(); + + SubnetSnapshotDecoder decoder = new(); + SubnetSnapshot decoded = decoder.Decode(stream); + decoded.Should().BeEquivalentTo(original); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/TimeoutCertificateDecoderTests.cs b/src/Nethermind/Nethermind.Xdc.Test/TimeoutCertificateDecoderTests.cs new file mode 100644 index 000000000000..6f408edf6d6c --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/TimeoutCertificateDecoderTests.cs @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.RLP; +using Nethermind.Xdc.Types; +using NUnit.Framework; +using System.Collections; + +namespace Nethermind.Xdc.Test; + +[TestFixture, Parallelizable(ParallelScope.All)] +public class TimeoutCertificateDecoderTests +{ + public static IEnumerable TcCases + { + get + { + yield return new TestCaseData(new TimeoutCertificate(1, [new Signature(new byte[64], 0), new Signature(new byte[64], 0), new Signature(new byte[64], 0)], 0), true); + yield return new TestCaseData(new TimeoutCertificate(1, [new Signature(new byte[64], 0), new Signature(new byte[64], 0), new Signature(new byte[64], 0)], 0), false); + yield return new TestCaseData(new TimeoutCertificate(1, [], 0), true); + yield return new TestCaseData(new TimeoutCertificate(1, [], 0), false); + } + } + [TestCaseSource(nameof(TcCases))] + public void EncodeDecode_RoundTrip_Matches_AllFields(TimeoutCertificate tc, bool useRlpStream) + { + var decoder = new TimeoutCertificateDecoder(); + + Rlp encoded = decoder.Encode(tc); + var stream = new RlpStream(encoded.Bytes); + TimeoutCertificate decoded; + if (useRlpStream) + { + decoded = decoder.Decode(stream); + } + else + { + Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(stream.Data.AsSpan()); + decoded = decoder.Decode(ref decoderContext); + } + + decoded.Should().BeEquivalentTo(tc); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/TimeoutCertificateManagerTests.cs b/src/Nethermind/Nethermind.Xdc.Test/TimeoutCertificateManagerTests.cs new file mode 100644 index 000000000000..8901c83aca30 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/TimeoutCertificateManagerTests.cs @@ -0,0 +1,222 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Consensus; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Types; +using NSubstitute; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Nethermind.Xdc.Test; + +[Parallelizable(ParallelScope.All)] +public class TimeoutCertificateManagerTests +{ + + [Test] + public void VerifyTC_NullCert_Throws() + { + TimeoutCertificateManager tcManager = BuildTimeoutCertificateManager(); + Assert.That(() => tcManager.VerifyTimeoutCertificate(null!, out _), Throws.ArgumentNullException); + } + + [Test] + public void VerifyTC_NullSignatures_Throws() + { + TimeoutCertificateManager tcManager = BuildTimeoutCertificateManager(); + var tc = new TimeoutCertificate(1, null!, 0); + Assert.That(() => tcManager.VerifyTimeoutCertificate(tc, out _), Throws.ArgumentNullException); + } + + [Test] + public void VerifyTC_SnapshotMissing_ReturnsFalse() + { + var tc = new TimeoutCertificate(1, Array.Empty(), 0); + ISnapshotManager snapshotManager = Substitute.For(); + snapshotManager.GetSnapshotByGapNumber(Arg.Any()) + .Returns((Snapshot?)null); + IBlockTree blockTree = Substitute.For(); + XdcBlockHeader header = Build.A.XdcBlockHeader().TestObject; + blockTree.Head.Returns(new Block(header)); + ISpecProvider specProvider = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(new XdcReleaseSpec() { V2Configs = [new V2ConfigParams()] }); + var tcManager = new TimeoutCertificateManager( + new XdcConsensusContext(), + snapshotManager, + Substitute.For(), + specProvider, + blockTree, + Substitute.For(), + Substitute.For()); + + var ok = tcManager.VerifyTimeoutCertificate(tc, out var err); + Assert.That(ok, Is.False); + Assert.That(err, Does.Contain("Failed to get snapshot")); + } + + [Test] + public void VerifyTC_EmptyCandidates_ReturnsFalse() + { + var tc = new TimeoutCertificate(1, Array.Empty(), 0); + ISnapshotManager snapshotManager = Substitute.For(); + snapshotManager.GetSnapshotByGapNumber(Arg.Any()) + .Returns(new Snapshot(0, Hash256.Zero, Array.Empty
())); + IBlockTree blockTree = Substitute.For(); + XdcBlockHeader header = Build.A.XdcBlockHeader().TestObject; + blockTree + .Head + .Returns(new Block(header)); + ISpecProvider specProvider = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(new XdcReleaseSpec() { V2Configs = [new V2ConfigParams()] }); + var tcManager = new TimeoutCertificateManager( + new XdcConsensusContext(), + snapshotManager, + Substitute.For(), + specProvider, + blockTree, + Substitute.For(), + Substitute.For()); + + var ok = tcManager.VerifyTimeoutCertificate(tc, out var err); + Assert.That(ok, Is.False); + Assert.That(err, Does.Contain("Empty master node")); + } + + public static IEnumerable TcCases() + { + var keyBuilder = new PrivateKeyGenerator(); + PrivateKey[] keys = keyBuilder.Generate(20).ToArray(); + IEnumerable
masterNodes = keys.Select(k => k.Address); + + // Base case + yield return new TestCaseData(BuildTimeoutCertificate(keys), masterNodes, true); + + // Insufficient signature count + PrivateKey[] notEnoughKeys = keys.Take(13).ToArray(); + yield return new TestCaseData(BuildTimeoutCertificate(notEnoughKeys), masterNodes, false); + + // Duplicated signatures still should fail if not enough + yield return new TestCaseData(BuildTimeoutCertificate(notEnoughKeys.Concat(notEnoughKeys).ToArray()), masterNodes, false); + + // Sufficient signature count + yield return new TestCaseData(BuildTimeoutCertificate(keys.Take(14).ToArray()), masterNodes, true); + + // Signer not in master nodes + yield return new TestCaseData(BuildTimeoutCertificate(keys), keys.Skip(1).Select(k => k.Address), false); + } + + [TestCaseSource(nameof(TcCases))] + public void VerifyTCWithDifferentParameters_ReturnsExpected(TimeoutCertificate timeoutCertificate, IEnumerable
masternodesList, bool expected) + { + Address[] masternodes = masternodesList.ToArray(); + ISnapshotManager snapshotManager = Substitute.For(); + snapshotManager.GetSnapshotByGapNumber(Arg.Any()) + .Returns(new Snapshot(0, Hash256.Zero, masternodes)); + + IEpochSwitchManager epochSwitchManager = Substitute.For(); + var epochSwitchInfo = new EpochSwitchInfo(masternodes, [], [], new BlockRoundInfo(Hash256.Zero, 1, 10)); + epochSwitchManager + .GetEpochSwitchInfo(Arg.Any()) + .Returns(epochSwitchInfo); + epochSwitchManager + .GetEpochSwitchInfo(Arg.Any()) + .Returns(epochSwitchInfo); + epochSwitchManager.GetTimeoutCertificateEpochInfo(Arg.Any()).Returns(epochSwitchInfo); + + ISpecProvider specProvider = Substitute.For(); + IXdcReleaseSpec xdcReleaseSpec = Substitute.For(); + xdcReleaseSpec.EpochLength.Returns(900); + xdcReleaseSpec.SwitchEpoch.Returns(89300); + xdcReleaseSpec.CertThreshold.Returns(0.667); + specProvider.GetSpec(Arg.Any()).Returns(xdcReleaseSpec); + + IBlockTree blockTree = Substitute.For(); + XdcBlockHeader header = Build.A.XdcBlockHeader().TestObject; + blockTree.Head.Returns(new Block(header, new BlockBody())); + blockTree.FindHeader(Arg.Any()).Returns(header); + + var context = new XdcConsensusContext(); + ISyncInfoManager syncInfoManager = Substitute.For(); + ISigner signer = Substitute.For(); + + var tcManager = new TimeoutCertificateManager(context, snapshotManager, epochSwitchManager, specProvider, + blockTree, syncInfoManager, signer); + + Assert.That(tcManager.VerifyTimeoutCertificate(timeoutCertificate, out _), Is.EqualTo(expected)); + } + + [TestCase(4UL)] + [TestCase(6UL)] + public async Task HandleTimeoutVote_RoundDoesNotMatchCurrentRound_ShouldReturnEarly(ulong round) + { + var ctx = new XdcConsensusContext() { CurrentRound = 5 }; + var tcManager = BuildTimeoutCertificateManager(ctx); + // dummy timeout message, only care about the round + var timeout = new Timeout(round, null, 0); + await tcManager.HandleTimeoutVote(timeout); + Assert.That(tcManager.GetTimeoutsCount(timeout), Is.EqualTo(0)); + } + + [TestCase(99UL, 0UL, true, false)] // Round smaller than current round + [TestCase(100UL, 1000UL, true, false)] // Incorrect gap number, snapshot is null + [TestCase(100UL, 0UL, false, false)] // Signer not in masternodes candidates + [TestCase(500UL, 0UL, true, true)] // Far away round but should get filtered in + public void FilterTimeout_DifferentCases_ReturnsExpected(ulong round, ulong gap, bool correctSigner, bool expected) + { + var keyBuilder = new PrivateKeyGenerator(); + PrivateKey[] keys = keyBuilder.Generate(21).ToArray(); + var masternodes = keys.Take(20).Select(k => k.Address).ToArray(); + ISnapshotManager snapshotManager = Substitute.For(); + snapshotManager.GetSnapshotByGapNumber(0) + .Returns(new Snapshot(0, Hash256.Zero, masternodes)); + + IEpochSwitchManager epochSwitchManager = Substitute.For(); + ISpecProvider specProvider = Substitute.For(); + IXdcReleaseSpec xdcReleaseSpec = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(xdcReleaseSpec); + IBlockTree blockTree = Substitute.For(); + XdcBlockHeader header = Build.A.XdcBlockHeader().TestObject; + blockTree.Head.Returns(new Block(header, new BlockBody())); + + var context = new XdcConsensusContext() { CurrentRound = 100 }; + ISyncInfoManager syncInfoManager = Substitute.For(); + ISigner signer = Substitute.For(); + + var tcManager = new TimeoutCertificateManager(context, snapshotManager, epochSwitchManager, specProvider, + blockTree, syncInfoManager, signer); + + var key = correctSigner ? keys.First() : keys.Last(); + var timeout = XdcTestHelper.BuildSignedTimeout(key, round, gap); + Assert.That(tcManager.FilterTimeout(timeout), Is.EqualTo(expected)); + } + + private TimeoutCertificateManager BuildTimeoutCertificateManager(XdcConsensusContext? ctx = null) + { + return new TimeoutCertificateManager( + ctx ?? new XdcConsensusContext(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For()); + } + + private static TimeoutCertificate BuildTimeoutCertificate(PrivateKey[] keys, ulong round = 1, ulong gap = 0) + { + var ecdsa = new EthereumEcdsa(0); + ValueHash256 msgHash = TimeoutCertificateManager.ComputeTimeoutMsgHash(round, gap); + Signature[] signatures = keys.Select(k => ecdsa.Sign(k, msgHash)).ToArray(); + return new TimeoutCertificate(round, signatures, gap); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/VoteDecoderTests.cs b/src/Nethermind/Nethermind.Xdc.Test/VoteDecoderTests.cs new file mode 100644 index 000000000000..e72f8b383fce --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/VoteDecoderTests.cs @@ -0,0 +1,204 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.Types; +using NUnit.Framework; +using System.Collections; + +namespace Nethermind.Xdc.Test; + +[TestFixture, Parallelizable(ParallelScope.All)] +public class VoteDecoderTests +{ + public static IEnumerable VoteCases + { + get + { + yield return new TestCaseData( + new Vote( + new BlockRoundInfo(Hash256.Zero, 1, 1), + 0, + new Signature(new byte[64], 0) + ), + true + ); + + yield return new TestCaseData( + new Vote( + new BlockRoundInfo(Hash256.Zero, ulong.MaxValue, long.MaxValue), + ulong.MaxValue, + new Signature(new byte[64], 0) + ), + true + ); + + yield return new TestCaseData( + new Vote( + new BlockRoundInfo(Hash256.Zero, 1, 1), + 0, + new Signature(new byte[64], 0) + ), + false + ); + } + } + + [TestCaseSource(nameof(VoteCases))] + public void EncodeDecode_RoundTrip_Matches_AllFields(Vote vote, bool useRlpStream) + { + var decoder = new VoteDecoder(); + + Rlp encoded = decoder.Encode(vote); + var stream = new RlpStream(encoded.Bytes); + Vote decoded; + + if (useRlpStream) + { + decoded = decoder.Decode(stream); + } + else + { + Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(stream.Data.AsSpan()); + decoded = decoder.Decode(ref decoderContext); + } + + decoded.Should().BeEquivalentTo(vote, options => options.Excluding(v => v.Signer)); + } + + [Test] + public void Encode_UseBothRlpStreamAndValueDecoderContext_IsEquivalentAfterReencoding() + { + Vote vote = new( + new BlockRoundInfo(Hash256.Zero, 1, 1), + 0, + new Signature(new byte[64], 0) + ); + + VoteDecoder decoder = new(); + RlpStream stream = new RlpStream(decoder.GetLength(vote)); + decoder.Encode(stream, vote); + stream.Position = 0; + + Vote decodedStream = decoder.Decode(stream); + stream.Position = 0; + + Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(stream.Data.AsSpan()); + Vote decodedContext = decoder.Decode(ref decoderContext); + + decodedStream.Should().BeEquivalentTo(vote, options => options.Excluding(v => v.Signer)); + decodedContext.Should().BeEquivalentTo(vote, options => options.Excluding(v => v.Signer)); + decodedStream.Should().BeEquivalentTo(decodedContext); + } + + [Test] + public void TotalLength_Equals_GetLength() + { + Vote vote = new( + new BlockRoundInfo(Hash256.Zero, 42, 42), + 10, + new Signature(new byte[64], 0) + ); + + var decoder = new VoteDecoder(); + Rlp encoded = decoder.Encode(vote); + + int expectedTotal = decoder.GetLength(vote, RlpBehaviors.None); + Assert.That(encoded.Bytes.Length, Is.EqualTo(expectedTotal), + "Encoded total length should match GetLength()."); + } + + [Test] + public void Encode_ForSealing_Omits_Signature() + { + Vote vote = new( + new BlockRoundInfo(Hash256.Zero, 1, 1), + 0, + new Signature(new byte[64], 0) + ); + + var decoder = new VoteDecoder(); + + Rlp normalEncoded = decoder.Encode(vote, RlpBehaviors.None); + + Rlp sealingEncoded = decoder.Encode(vote, RlpBehaviors.ForSealing); + + Assert.That(sealingEncoded.Bytes.Length, Is.LessThan(normalEncoded.Bytes.Length), + "ForSealing encoding should be shorter as it omits the signature."); + + var stream = new RlpStream(sealingEncoded.Bytes); + Vote decoded = decoder.Decode(stream, RlpBehaviors.ForSealing); + + Assert.That(decoded.Signature, Is.Null, + "ForSealing decoding should not contain Signature field."); + Assert.That(decoded.ProposedBlockInfo.Round, Is.EqualTo(vote.ProposedBlockInfo.Round)); + Assert.That(decoded.GapNumber, Is.EqualTo(vote.GapNumber)); + } + + [Test] + public void Encode_Null_ReturnsEmptySequence() + { + var decoder = new VoteDecoder(); + + Rlp encoded = decoder.Encode(null!); + + Assert.That(encoded, Is.EqualTo(Rlp.OfEmptySequence)); + } + + [Test] + public void Decode_Null_ReturnsNull() + { + var decoder = new VoteDecoder(); + var stream = new RlpStream(Rlp.OfEmptySequence.Bytes); + + Vote decoded = decoder.Decode(stream); + + Assert.That(decoded, Is.Null); + } + + [Test] + public void Decode_EmptyByteArray_ValueDecoderContext_ReturnsNull() + { + var decoder = new VoteDecoder(); + Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(Rlp.OfEmptySequence.Bytes); + + Vote decoded = decoder.Decode(ref decoderContext); + + Assert.That(decoded, Is.Null); + } + + [Test] + public void GetLength_ForSealing_IsShorter() + { + Vote vote = new( + new BlockRoundInfo(Hash256.Zero, 1, 1), + 0, + new Signature(new byte[64], 0) + ); + + var decoder = new VoteDecoder(); + + int normalLength = decoder.GetLength(vote, RlpBehaviors.None); + int sealingLength = decoder.GetLength(vote, RlpBehaviors.ForSealing); + + Assert.That(sealingLength, Is.LessThan(normalLength), + "ForSealing length should be shorter as signature is omitted."); + } + + [Test] + public void Vote_PoolKey_ReturnsRoundAndHash() + { + Vote vote = new( + new BlockRoundInfo(Hash256.Zero, 1, 100), + 10, + new Signature(new byte[64], 0) + ); + + var (round, hash) = vote.PoolKey(); + + Assert.That(round, Is.EqualTo(1UL)); + Assert.That(hash, Is.Not.EqualTo(Hash256.Zero)); // Should be computed hash + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs new file mode 100644 index 000000000000..53c64111c232 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcBlockProducerTest.cs @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Config; +using Nethermind.Consensus; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Transactions; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; +using Nethermind.Logging; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Types; +using NSubstitute; +using NUnit.Framework; +using System.Linq; +using System.Threading.Tasks; + +namespace Nethermind.Xdc.Test; + +internal class XdcBlockProducerTest +{ + [Test] + public async Task BuildBlock_HasCorrectQC_ProducesValidHeader() + { + ISpecProvider specProvider = Substitute.For(); + IXdcReleaseSpec xdcReleaseSpec = Substitute.For(); + xdcReleaseSpec.MinePeriod.Returns(2); + xdcReleaseSpec.EpochLength.Returns(900); + xdcReleaseSpec.GasLimitBoundDivisor.Returns(1); + specProvider.GetSpec(Arg.Any()).Returns(xdcReleaseSpec); + var epochManager = Substitute.For(); + IWorldState stateProvider = Substitute.For(); + stateProvider.HasStateForBlock(Arg.Any()).Returns(true); + + PrivateKey[] masterNodes = XdcTestHelper.GeneratePrivateKeys(108); + epochManager + .GetEpochSwitchInfo(Arg.Any()) + .Returns(new Types.EpochSwitchInfo(masterNodes.Select(m => m.Address).ToArray(), [], [], new Types.BlockRoundInfo(Hash256.Zero, 0, 0))); + + ISealer sealer = new XdcSealer(new Signer(0, new ProtectedPrivateKey(masterNodes[1], ""), NullLogManager.Instance)); + + XdcBlockHeader parent = Build.A.XdcBlockHeader().TestObject; + + var xdcContext = new XdcConsensusContext(); + xdcContext.SetNewRound(1); + xdcContext.HighestQC = XdcTestHelper.CreateQc(new Types.BlockRoundInfo(parent.Hash!, 0, parent.Number), 0, masterNodes); + + var quorumCertificateManager = Substitute.For(); + quorumCertificateManager.VerifyCertificate(Arg.Any(), Arg.Any(), out _).Returns(true); + + IBlockchainProcessor processor = Substitute.For(); + processor.Process(Arg.Any(), Arg.Any(), Arg.Any()).Returns(args => args.ArgAt(0)); + + XdcBlockProducer producer = new XdcBlockProducer( + epochManager, + Substitute.For(), + xdcContext, + Substitute.For(), + processor, + sealer, + Substitute.For(), + stateProvider, + new XdcGasLimitCalculator(specProvider, Substitute.For()), + Substitute.For(), + specProvider, + Substitute.For(), + Substitute.For(), + Substitute.For()); + XdcHeaderValidator headerValidator = new XdcHeaderValidator(Substitute.For(), quorumCertificateManager, new XdcSealValidator(Substitute.For(), epochManager, specProvider), specProvider, NullLogManager.Instance); + + Block? block = await producer.BuildBlock(parent); + + Assert.That(block, Is.Not.Null); + bool actual = headerValidator.Validate(block.Header, parent, false, out string? error); + Assert.That(actual, Is.True); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcGasLimitCalculatorTests.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcGasLimitCalculatorTests.cs new file mode 100644 index 000000000000..6fa3521624fe --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcGasLimitCalculatorTests.cs @@ -0,0 +1,214 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Config; +using Nethermind.Consensus; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Int256; +using Nethermind.Specs; +using Nethermind.Xdc.Spec; +using NSubstitute; +using NUnit.Framework; +using System; + +namespace Nethermind.Xdc.Test; + +[TestFixture, Parallelizable(ParallelScope.All)] +public class XdcGasLimitCalculatorTests +{ + [Test] + public void GetGasLimit_WhenDynamicGasLimitNotEnabled_ReturnsTargetBlockGasLimit() + { + // Arrange + const long targetGasLimit = 10_000_000L; + + ISpecProvider specProvider = Substitute.For(); + IBlocksConfig blocksConfig = Substitute.For(); + blocksConfig.TargetBlockGasLimit.Returns(targetGasLimit); + + var calculator = new XdcGasLimitCalculator(specProvider, blocksConfig); + var parentHeader = CreateParentHeader(1000); + var xdcSpec = CreateXdcSpec(isDynamicGasLimitBlock: false); + + specProvider.GetSpec(Arg.Any()).Returns(xdcSpec); + + // Act + long result = calculator.GetGasLimit(parentHeader); + + // Assert + result.Should().Be(targetGasLimit); + } + + [Test] + public void GetGasLimit_WhenDynamicGasLimitNotEnabledAndTargetBlockGasLimitIsNull_ReturnsDefaultXdcGasLimit() + { + // Arrange + ISpecProvider specProvider = Substitute.For(); + IBlocksConfig blocksConfig = Substitute.For(); + blocksConfig.TargetBlockGasLimit.Returns((long?)null); + + var calculator = new XdcGasLimitCalculator(specProvider, blocksConfig); + var parentHeader = CreateParentHeader(1000); + var xdcSpec = CreateXdcSpec(isDynamicGasLimitBlock: false); + + specProvider.GetSpec(Arg.Any()).Returns(xdcSpec); + + // Act + long result = calculator.GetGasLimit(parentHeader); + + // Assert + result.Should().Be(XdcConstants.DefaultTargetGasLimit); + } + + [Test] + public void GetGasLimit_WhenDynamicGasLimitEnabled_UsesTargetAdjustedCalculator() + { + // Arrange + const long parentGasLimit = 84_000_000L; + const long targetGasLimit = 100_000_000L; + + ISpecProvider specProvider = Substitute.For(); + IBlocksConfig blocksConfig = Substitute.For(); + blocksConfig.TargetBlockGasLimit.Returns(targetGasLimit); + + var calculator = new XdcGasLimitCalculator(specProvider, blocksConfig); + var parentHeader = CreateParentHeader(1000, parentGasLimit); + var xdcSpec = CreateXdcSpec(isDynamicGasLimitBlock: true); + + specProvider.GetSpec(Arg.Any()).Returns(xdcSpec); + + // Act + long result = calculator.GetGasLimit(parentHeader); + + // Assert + // The TargetAdjustedGasLimitCalculator will adjust the gas limit toward the target + result.Should().NotBe(parentGasLimit); + // It should move toward the target (100M) from parent (84M) + result.Should().BeGreaterThan(parentGasLimit); + result.Should().BeLessThanOrEqualTo(targetGasLimit); + } + + [Test] + public void GetGasLimit_WhenDynamicGasLimitEnabled_GasLimitAdjustsTowardTarget() + { + // Arrange + const long parentGasLimit = 50_000_000L; + const long targetGasLimit = 84_000_000L; + + ISpecProvider specProvider = Substitute.For(); + IBlocksConfig blocksConfig = Substitute.For(); + blocksConfig.TargetBlockGasLimit.Returns(targetGasLimit); + + var calculator = new XdcGasLimitCalculator(specProvider, blocksConfig); + var parentHeader = CreateParentHeader(1000, parentGasLimit); + var xdcSpec = CreateXdcSpec(isDynamicGasLimitBlock: true); + + specProvider.GetSpec(Arg.Any()).Returns(xdcSpec); + + // Act + long result = calculator.GetGasLimit(parentHeader); + + // Assert + // Should increase toward target + result.Should().BeGreaterThan(parentGasLimit); + result.Should().BeLessThanOrEqualTo(targetGasLimit); + } + + [TestCase(true)] + [TestCase(false)] + public void GetGasLimit_AtDynamicGasLimitBlockBoundary_TransitionsToTargetAdjusted(bool dynamicBlockActive) + { + // Arrange + const long targetGasLimit = 100_000_000L; + ISpecProvider specProvider = Substitute.For(); + IBlocksConfig blocksConfig = Substitute.For(); + blocksConfig.TargetBlockGasLimit.Returns(targetGasLimit); + + var calculator = new XdcGasLimitCalculator(specProvider, blocksConfig); + var parentHeader = CreateParentHeader(1); + var spec = CreateXdcSpec(isDynamicGasLimitBlock: dynamicBlockActive); + specProvider.GetSpec(Arg.Any()).Returns(spec); + + long result = calculator.GetGasLimit(parentHeader); + + if (dynamicBlockActive) + { + result.Should().BeInRange(parentHeader.GasLimit - 100000, parentHeader.GasLimit + 100000); + } + else + { + result.Should().Be(targetGasLimit); + } + } + + [Test] + public void GetGasLimit_WhenParentGasLimitEqualsTarget_DynamicModeReturnsNearTarget() + { + // Arrange + const long targetGasLimit = 84_000_000L; + + ISpecProvider specProvider = Substitute.For(); + IBlocksConfig blocksConfig = Substitute.For(); + blocksConfig.TargetBlockGasLimit.Returns(targetGasLimit); + + var calculator = new XdcGasLimitCalculator(specProvider, blocksConfig); + var parentHeader = CreateParentHeader(1000, targetGasLimit); + var xdcSpec = CreateXdcSpec(isDynamicGasLimitBlock: true); + + specProvider.GetSpec(Arg.Any()).Returns(xdcSpec); + + // Act + long result = calculator.GetGasLimit(parentHeader); + + // Assert + // When at target, should return close to target + result.Should().BeGreaterOrEqualTo(targetGasLimit - 100_000); + result.Should().BeLessThanOrEqualTo(targetGasLimit + 100_000); + } + + [Test] + public void GetGasLimit_WithGenesisBlock_HandlesCorrectly() + { + // Arrange + const long targetGasLimit = 84_000_000L; + + ISpecProvider specProvider = Substitute.For(); + IBlocksConfig blocksConfig = Substitute.For(); + blocksConfig.TargetBlockGasLimit.Returns(targetGasLimit); + + var calculator = new XdcGasLimitCalculator(specProvider, blocksConfig); + var genesisHeader = CreateParentHeader(0, 5_000_000L); + var xdcSpec = CreateXdcSpec(isDynamicGasLimitBlock: false); + + specProvider.GetSpec(Arg.Any()).Returns(xdcSpec); + + // Act + long result = calculator.GetGasLimit(genesisHeader); + + // Assert + result.Should().Be(targetGasLimit); + } + + private static BlockHeader CreateParentHeader(long number, long gasLimit = 84_000_000L) + { + return Build.A.BlockHeader + .WithNumber(number) + .WithGasLimit(gasLimit) + .WithHash(TestItem.KeccakA) + .TestObject; + } + + private static IXdcReleaseSpec CreateXdcSpec(bool isDynamicGasLimitBlock) + { + var spec = Substitute.For(); + spec.IsDynamicGasLimitBlock.Returns(isDynamicGasLimitBlock); + spec.Eip1559TransitionBlock.Returns(long.MaxValue); // Not relevant for these tests + spec.IsEip1559Enabled.Returns(false); + spec.GasLimitBoundDivisor.Returns(1024); + return spec; + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcHeaderDecoderTests.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcHeaderDecoderTests.cs index acca0cc73403..6ff7ddf65254 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/XdcHeaderDecoderTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcHeaderDecoderTests.cs @@ -1,15 +1,17 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using FluentAssertions; -using NUnit.Framework; using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; using Nethermind.Int256; using Nethermind.Serialization.Rlp; - -using Nethermind.Core.Test.Builders; -using Nethermind.Core.Extensions; +using NUnit.Framework; namespace Nethermind.Xdc.Test { - [TestFixture] + [TestFixture, Parallelizable(ParallelScope.All)] public class XdcHeaderDecoderTests { private static (XdcBlockHeader Header, byte[] Bytes) BuildHeaderAndDefaultEncode(XdcHeaderDecoder codec, bool includeBaseFee = true) diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcHeaderStoreTests.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcHeaderStoreTests.cs new file mode 100644 index 000000000000..4e9b37e1c5ec --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcHeaderStoreTests.cs @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Blockchain.Blocks; +using Nethermind.Blockchain.Headers; +using Nethermind.Core; +using Nethermind.Core.Test.Builders; +using Nethermind.Db; +using NUnit.Framework; + +namespace Nethermind.Xdc.Test; + +internal class XdcBlockAndHeaderStoreTests +{ + IXdcHeaderStore _headerStore; + IBlockStore _blockStore; + + IDb headerDb; + IDb blockNumDb; + IDb blockDb; + + + [SetUp] + public void Setup() + { + headerDb = new MemDb(); + blockNumDb = new MemDb(); + blockDb = new MemDb(); + + _headerStore = new XdcHeaderStore(headerDb, blockNumDb); + _blockStore = new XdcBlockStore(blockDb); + } + + [Test] + public void XdcHeaderStore_ShouldInheritFromHeaderStore() + { + Assert.That(_headerStore, Is.InstanceOf()); + } + + [Test] + public void XdcHeaderStore_InsertAndGetHeader_ShouldWorkCorrectly() + { + // Arrange + XdcBlockHeaderBuilder headerBuilder = Build.A.XdcBlockHeader().WithGeneratedExtraConsensusData(); + var header = headerBuilder.TestObject; + + // Act + _headerStore.Insert(header); + XdcBlockHeader? retrievedHeader = _headerStore.Get(header.Hash!, false); + // Assert + retrievedHeader.Should().BeEquivalentTo(header); + } + + [Test] + public void XdcBlockStore_InsertAndGetBlock_ShouldWorkCorrectly() + { + // Arrange + XdcBlockHeaderBuilder headerBuilder = Build.A.XdcBlockHeader().WithGeneratedExtraConsensusData(); + var header = headerBuilder.TestObject; + BlockBuilder blockBuilder = Build.A.Block.WithHeader(header); + var block = blockBuilder.TestObject; + // Act + _blockStore.Insert(block); + Block? retrievedBlock = _blockStore.Get(block.Number, block.Hash!); + // Assert + retrievedBlock.Should().BeEquivalentTo(block, options => options.Excluding(h => h.EncodedSize)); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcHeaderValidatorTests.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcHeaderValidatorTests.cs index a5b527164068..19d64d89d975 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/XdcHeaderValidatorTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcHeaderValidatorTests.cs @@ -9,6 +9,7 @@ using Nethermind.Core.Test.Builders; using Nethermind.Logging; using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Types; using NSubstitute; using NUnit.Framework; using System; @@ -16,6 +17,7 @@ namespace Nethermind.Xdc.Test; +[Parallelizable(ParallelScope.All)] public class Tests { [Test] @@ -23,7 +25,7 @@ public void Validate_NotAnXdcHeader_ThrowsArgumentException() { BlockHeader parent = Build.A.BlockHeader.TestObject; BlockHeader header = Build.A.BlockHeader.WithParent(parent).TestObject; - XdcHeaderValidator validator = new(Substitute.For(), Substitute.For(), Substitute.For(), Substitute.For()); + XdcHeaderValidator validator = new(Substitute.For(), Substitute.For(), Substitute.For(), Substitute.For(), Substitute.For()); Assert.That(() => validator.Validate(header, parent, false, out _), Throws.TypeOf()); } @@ -64,6 +66,17 @@ public static IEnumerable HeaderTestCases() blockHeaderBuilder.WithUnclesHash(Hash256.FromBytesWithPadding([0x01])); yield return [blockHeaderBuilder, false]; + //Invalid difficulty + blockHeaderBuilder = CreateValidHeader(); + blockHeaderBuilder.WithDifficulty(2); + yield return [blockHeaderBuilder, false]; + + //Invalid total difficulty + blockHeaderBuilder = CreateValidHeader(); + blockHeaderBuilder.WithDifficulty(2); + blockHeaderBuilder.WithTotalDifficulty(1); + yield return [blockHeaderBuilder, false]; + static XdcBlockHeaderBuilder CreateValidHeader() { XdcBlockHeaderBuilder blockHeaderBuilder = (XdcBlockHeaderBuilder)Build.A @@ -90,8 +103,10 @@ public void Validate_HeaderWithDifferentValues_ReturnsExpected(XdcBlockHeaderBui IXdcReleaseSpec releaseSpec = Substitute.For(); releaseSpec.GasLimitBoundDivisor.Returns(1); specProvider.GetSpec(Arg.Any()).Returns(releaseSpec); - XdcHeaderValidator validator = new(Substitute.For(), sealValidator, specProvider, Substitute.For()); + IQuorumCertificateManager quorumCertificateManager = Substitute.For(); + quorumCertificateManager.VerifyCertificate(Arg.Any(), Arg.Any(), out _).Returns(true); + XdcHeaderValidator validator = new(Substitute.For(), quorumCertificateManager, sealValidator, specProvider, Substitute.For()); - Assert.That(validator.Validate(headerBuilder.TestObject, headerParent, false, out _), Is.EqualTo(expected)); + Assert.That(validator.Validate(headerBuilder.TestObject, headerParent, false, out string? error), Is.EqualTo(expected), "Error was: " + error); } } diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcSealValidatorTests.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcSealValidatorTests.cs index 91706c8d3877..0cc13b7f80ce 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/XdcSealValidatorTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcSealValidatorTests.cs @@ -1,35 +1,42 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using DotNetty.Codecs; -using Google.Protobuf.WellKnownTypes; -using Microsoft.Testing.Platform.Extensions.Messages; -using Nethermind.Consensus; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; -using Nethermind.Serialization.Rlp; -using Nethermind.Xdc.RLP; using Nethermind.Xdc.Spec; using Nethermind.Xdc.Types; using NSubstitute; -using NSubstitute.Core; using NUnit.Framework; -using Org.BouncyCastle.Utilities.Encoders; using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; -using System.Numerics; -using System.Reflection.PortableExecutable; -using System.Text; -using System.Threading.Tasks; namespace Nethermind.Xdc.Test; + +[Parallelizable(ParallelScope.All)] internal class XdcSealValidatorTests { + private static bool IsEpochSwitch(XdcBlockHeader header, IXdcReleaseSpec spec) + { + if (spec.SwitchBlock == header.Number) + { + return true; + } + ExtraFieldsV2? extraFields = header.ExtraConsensusData; + if (extraFields is null) + { + //Should this throw instead? + return false; + } + ulong parentRound = extraFields.QuorumCert.ProposedBlockInfo.Round; + ulong epochStart = extraFields.BlockRound - extraFields.BlockRound % (ulong)spec.EpochLength; + + return parentRound < epochStart; + } + [Test] public void ValidateSeal_NotXdcHeader_ThrowArgumentException() { @@ -61,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); } @@ -193,19 +200,21 @@ public void ValidateParams_HeaderHasDifferentSealParameters_ReturnsExpected(XdcB ISnapshotManager snapshotManager = Substitute.For(); snapshotManager - .CalculateNextEpochMasternodes(Arg.Any(), Arg.Any()) + .CalculateNextEpochMasternodes(Arg.Any(), Arg.Any(), Arg.Any()) .Returns((epochCandidates.ToArray(), penalties.ToArray())); IEpochSwitchManager epochSwitchManager = Substitute.For(); - epochSwitchManager.GetEpochSwitchInfo(Arg.Any(), Arg.Any()).Returns(new EpochSwitchInfo() - { - Masternodes = epochCandidates.ToArray() - }); + epochSwitchManager.GetEpochSwitchInfo(Arg.Any()).Returns(new EpochSwitchInfo(epochCandidates.ToArray(), [], [], new BlockRoundInfo(Hash256.Zero, 0, 0))); + epochSwitchManager.GetEpochSwitchInfo(Arg.Any()).Returns(new EpochSwitchInfo(epochCandidates.ToArray(), [], [], new BlockRoundInfo(Hash256.Zero, 0, 0))); + + bool isEpochSwitch = IsEpochSwitch(header, releaseSpec); + epochSwitchManager.IsEpochSwitchAtBlock(Arg.Any()).Returns(isEpochSwitch); + XdcSealValidator validator = new XdcSealValidator(snapshotManager, epochSwitchManager, specProvider); Assert.That(validator.ValidateParams(parent, header), Is.EqualTo(expected)); } - private static QuorumCert CreateQc(BlockRoundInfo roundInfo, PrivateKey[] keys, ulong gapNumber) + private static QuorumCertificate CreateQc(BlockRoundInfo roundInfo, PrivateKey[] keys, ulong gapNumber) { EthereumEcdsa ecdsa = new EthereumEcdsa(0); QuorumCertificateDecoder qcEncoder = new QuorumCertificateDecoder(); @@ -213,6 +222,6 @@ private static QuorumCert CreateQc(BlockRoundInfo roundInfo, PrivateKey[] keys, //Fake the sigs by signing empty hash IEnumerable signatures = keys.Select(k => ecdsa.Sign(k, Keccak.Compute(Hash256.Zero.Bytes))); - return new QuorumCert(roundInfo, signatures.ToArray(), gapNumber); + return new QuorumCertificate(roundInfo, signatures.ToArray(), gapNumber); } } diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcSealerTests.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcSealerTests.cs new file mode 100644 index 000000000000..be9720f5e6db --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcSealerTests.cs @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Consensus; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Logging; +using NUnit.Framework; +using System.Threading; +using System.Threading.Tasks; + +namespace Nethermind.Xdc.Test; + +[Parallelizable(ParallelScope.All)] +internal class XdcSealerTests +{ + [Test] + public async Task SealBlock_ShouldSignXdcBlockHeader() + { + // Arrange + var sealer = new XdcSealer(new Signer(0, Build.A.PrivateKey.TestObject, NullLogManager.Instance)); + var block = Build.A.Block.WithHeader(Build.A.XdcBlockHeader().TestObject).TestObject; + + // Act + var sealedBlock = await sealer.SealBlock(block, CancellationToken.None); + XdcBlockHeader sealedHeader = (XdcBlockHeader)sealedBlock.Header; + + // Assert + Assert.That(sealedHeader.Validator, Is.Not.Null); + Assert.That(sealedHeader.Validator!.Length, Is.EqualTo(Signature.Size)); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcSortTests.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcSortTests.cs new file mode 100644 index 000000000000..3f25ca598e32 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcSortTests.cs @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Core; +using Nethermind.Core.Test.Builders; +using Nethermind.Xdc.Contracts; +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; + +namespace Nethermind.Xdc.Test; + +internal class XdcSortTests +{ + public static IEnumerable CandidatesWithStake() + { + CandidateStake[] candidatesAndStake = + [ + new CandidateStake() { Address = TestItem.AddressA, Stake = 10_000_000}, + new CandidateStake() { Address = TestItem.AddressB, Stake = 10_000_000}, + new CandidateStake() { Address = TestItem.AddressC, Stake = 10_000_000} + ]; + Address[] expectedOrder = [TestItem.AddressC, TestItem.AddressB, TestItem.AddressA]; + + yield return new TestCaseData(candidatesAndStake, expectedOrder); + + candidatesAndStake = + [ + new CandidateStake() { Address = TestItem.AddressA, Stake = 10_000_000}, + new CandidateStake() { Address = TestItem.AddressB, Stake = 10_000_001}, + new CandidateStake() { Address = TestItem.AddressC, Stake = 10_000_000} + ]; + expectedOrder = [TestItem.AddressB, TestItem.AddressC, TestItem.AddressA]; + + yield return new TestCaseData(candidatesAndStake, expectedOrder); + + candidatesAndStake = + [ + new CandidateStake() { Address = TestItem.AddressA, Stake = 10_000_001}, + new CandidateStake() { Address = TestItem.AddressB, Stake = 10_000_000}, + new CandidateStake() { Address = TestItem.AddressC, Stake = 10_000_001} + ]; + expectedOrder = [TestItem.AddressC, TestItem.AddressA, TestItem.AddressB]; + + yield return new TestCaseData(candidatesAndStake, expectedOrder); + + candidatesAndStake = + [ + new CandidateStake() { Address = TestItem.AddressB, Stake = 10_000_000}, + new CandidateStake() { Address = TestItem.AddressC, Stake = 10_000_000}, + new CandidateStake() { Address = TestItem.AddressA, Stake = 10_000_000} + ]; + expectedOrder = [TestItem.AddressA, TestItem.AddressC, TestItem.AddressB]; + + yield return new TestCaseData(candidatesAndStake, expectedOrder); + // Test with 20 items with same stake to verify unstable sort behavior + candidatesAndStake = Enumerable.Range(0, 20) + .Select(i => new CandidateStake() + { + Address = new Address($"0x{i:D40}"), + Stake = 10_000_000 + }) + .ToArray(); + + // Sort is deterministic but not stable: equal elements are reordered from original positions + expectedOrder = new[] { 5, 4, 3, 2, 1, 12, 11, 19, 17, 15, 13, 6, 14, 7, 16, 8, 18, 9, 0, 10 } + .Select(i => new Address($"0x{i:D40}")) + .ToArray(); + + yield return new TestCaseData(candidatesAndStake, expectedOrder); + } + + [TestCaseSource(nameof(CandidatesWithStake))] + public void Slice_DifferentOrderAndStake_SortItemsAsExpected(CandidateStake[] candidatesAndStake, Address[] expectedOrder) + { + XdcSort.Slice(candidatesAndStake, (x, y) => x.Stake.CompareTo(y.Stake) >= 0); + + candidatesAndStake.Select(x => x.Address).Should().Equal(expectedOrder); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcSpecProviderTests.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcSpecProviderTests.cs index 8a17c4f80360..bd17d4aad799 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/XdcSpecProviderTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcSpecProviderTests.cs @@ -1,15 +1,15 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections.Generic; using FluentAssertions; using Nethermind.Xdc.Spec; using NUnit.Framework; +using System; +using System.Collections.Generic; namespace Nethermind.Xdc.Test; -[TestFixture] +[TestFixture, Parallelizable(ParallelScope.All)] public class XdcSpecProviderTests { [Test] diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcSubnetHeaderDecoderTests.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcSubnetHeaderDecoderTests.cs new file mode 100644 index 000000000000..8f9d55fcb3ba --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcSubnetHeaderDecoderTests.cs @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Core; +using Nethermind.Core.Test.Builders; +using Nethermind.Serialization.Rlp; +using NUnit.Framework; + +namespace Nethermind.Xdc.Test +{ + [TestFixture, Parallelizable(ParallelScope.All)] + public class XdcSubnetHeaderDecoderTests + { + private static (XdcSubnetBlockHeader Header, byte[] Bytes) BuildHeaderAndDefaultEncode(XdcSubnetHeaderDecoder codec, bool forSealing = false) + { + XdcSubnetBlockHeaderBuilder builder = Build.A.XdcSubnetBlockHeader(); + XdcSubnetBlockHeader header = builder.TestObject; + + Rlp encoded = codec.Encode(header, forSealing ? RlpBehaviors.ForSealing : RlpBehaviors.None); + return (header, encoded.Bytes); + } + + [Test] + public void EncodeDecode_RoundTrip_Matches_AllFields() + { + var codec = new XdcSubnetHeaderDecoder(); + var (original, encodedBytes) = BuildHeaderAndDefaultEncode(codec); + + // Decode + var stream = new RlpStream(encodedBytes); + BlockHeader? decodedBase = codec.Decode(stream); + Assert.That(decodedBase, Is.Not.Null, "The decoded header should not be null."); + Assert.That(decodedBase, Is.InstanceOf(), "The decoded header should be an instance of XdcSubnetBlockHeader."); + + var decoded = (XdcSubnetBlockHeader)decodedBase!; + + // Hash is excluded since decoder sets it from RLP, but original is often not set + decoded.Should().BeEquivalentTo(original, options => options.Excluding(h => h.Hash)); + } + + [Test] + public void TotalLength_Equals_GetLength() + { + var codec = new XdcSubnetHeaderDecoder(); + var (original, encodedBytes) = BuildHeaderAndDefaultEncode(codec); + + // compare to GetLength + int expectedTotal = codec.GetLength(original, RlpBehaviors.None); + Assert.That(encodedBytes.Length, Is.EqualTo(expectedTotal), "Encoded total length should match GetLength()."); + } + + [Test] + public void TotalLength_Equals_GetLength_ForSealing() + { + var codec = new XdcSubnetHeaderDecoder(); + var (original, encodedBytes) = BuildHeaderAndDefaultEncode(codec, true); + + // compare to GetLength + int expectedTotal = codec.GetLength(original, RlpBehaviors.ForSealing); + Assert.That(encodedBytes.Length, Is.EqualTo(expectedTotal), "Encoded total length should match GetLength()."); + } + + + [Test] + public void Encode_ForSealing_Omits_Validator_And_NextValidators() + { + var decoder = new XdcSubnetHeaderDecoder(); + var (original, encodedBytes) = BuildHeaderAndDefaultEncode(decoder, true); + + // ForSealing encoding + XdcSubnetBlockHeader unencoded = (XdcSubnetBlockHeader)decoder.Decode(new RlpStream(encodedBytes), RlpBehaviors.ForSealing)!; + + Assert.That(unencoded.Validator, Is.Null, "ForSealing encoding should not contain Validator field."); + Assert.That(unencoded.NextValidators, Is.Null, "ForSealing encoding should not contain NextValidators field."); + } + + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcTestExtentions.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcTestExtentions.cs new file mode 100644 index 000000000000..7f24ff71090a --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcTestExtentions.cs @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Xdc.Test.Helpers; + +namespace Nethermind.Core.Test.Builders; + +public static class BuildExtensions +{ + public static XdcBlockHeaderBuilder XdcBlockHeader(this Build build) + { + return new XdcBlockHeaderBuilder(); + } + + public static XdcSubnetBlockHeaderBuilder XdcSubnetBlockHeader(this Build build) + { + return new XdcSubnetBlockHeaderBuilder(); + } + + public static QuorumCertificateBuilder QuorumCertificate(this Build build) + { + return new QuorumCertificateBuilder(); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcTransactionProcessorTests.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcTransactionProcessorTests.cs new file mode 100644 index 000000000000..f21cbfc61889 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcTransactionProcessorTests.cs @@ -0,0 +1,156 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; +using Nethermind.Core.Test; +using Nethermind.Core.Test.Builders; +using Nethermind.Evm; +using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; +using Nethermind.Blockchain; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Xdc.Contracts; +using Nethermind.Xdc.Spec; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Xdc.Test; + +internal class XdcTransactionProcessorTests +{ + private IXdcReleaseSpec _spec; + private ISpecProvider _specProvider; + private IWorldState? _stateProvider; + private IDisposable _worldStateCloser; + private IMasternodeVotingContract _masternodeVotingContract; + private TestXdcTransactionProcessor? _transactionProcessor; + + private static readonly UInt256 AccountBalance = 1.Ether(); + + [SetUp] + public void Setup() + { + _spec = Substitute.For(); + _specProvider = Substitute.For(); + _specProvider.GetSpec(Arg.Any()).Returns(_spec); + _specProvider.GenesisSpec.Returns(_spec); + + _stateProvider = TestWorldStateFactory.CreateForTest(); + _worldStateCloser = _stateProvider.BeginScope(IWorldState.PreGenesis); + _stateProvider.CreateAccount(TestItem.AddressA, AccountBalance); + _stateProvider.Commit(_spec); + _stateProvider.CommitTree(0); + + _masternodeVotingContract = Substitute.For(); + + EthereumCodeInfoRepository codeInfoRepository = new(_stateProvider); + EthereumVirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); + _transactionProcessor = new TestXdcTransactionProcessor( + BlobBaseFeeCalculator.Instance, + _specProvider, + _stateProvider, + virtualMachine, + codeInfoRepository, + LimboLogs.Instance, + _masternodeVotingContract); + } + + [TearDown] + public void TearDown() + { + _worldStateCloser.Dispose(); + } + + [TestCase(false)] + [TestCase(true)] + public void PayFees_IsTipTrc21FeeEnabled_ShouldPayFeesToTheCorrectAddress(bool tipTrc21FeeEnabled) + { + _spec.IsTipTrc21FeeEnabled.Returns(tipTrc21FeeEnabled); + + long spentGas = 21000; + UInt256 premiumPerGas = 9; + UInt256 blobBaseFee = 0; + Address beneficiaryAddress = TestItem.AddressB; + Address ownerAddress = TestItem.AddressD; + + _stateProvider!.CreateAccount(beneficiaryAddress, AccountBalance); + _stateProvider.CreateAccount(ownerAddress, UInt256.Zero); + + _masternodeVotingContract.GetCandidateOwnerDuringProcessing(Arg.Any(), Arg.Any(), beneficiaryAddress) + .Returns(ownerAddress); + + Transaction tx = Build.A.Transaction + .WithGasPrice(10) + .WithMaxFeePerGas(10) + .WithMaxPriorityFeePerGas(9) + .WithGasLimit(21000) + .TestObject; + + XdcBlockHeader header = Build.A.XdcBlockHeader() + .WithNumber(1) + .WithBaseFee(1) + .TestObject; + header.Beneficiary = beneficiaryAddress; + + TransactionSubstate substate = default; + + FeesTracer tracer = new(); + UInt256 initialBeneficiaryBalance = _stateProvider.GetBalance(beneficiaryAddress); + UInt256 initialOwnerBalance = _stateProvider.GetBalance(ownerAddress); + + _transactionProcessor!.TestPayFees(tx, header, _spec, tracer, substate, spentGas, premiumPerGas, blobBaseFee, StatusCode.Success); + + UInt256 finalBeneficiaryBalance = _stateProvider.GetBalance(beneficiaryAddress); + UInt256 finalOwnerBalance = _stateProvider.GetBalance(ownerAddress); + UInt256 beneficiaryReceivedFees = finalBeneficiaryBalance - initialBeneficiaryBalance; + UInt256 ownerReceivedFees = finalOwnerBalance - initialOwnerBalance; + + if (tipTrc21FeeEnabled) + { + UInt256 expectedFees = tx.GasPrice * (ulong)spentGas; + Assert.That(ownerReceivedFees, Is.EqualTo(expectedFees)); + Assert.That(beneficiaryReceivedFees, Is.EqualTo(UInt256.Zero)); + } + else + { + UInt256 expectedFees = premiumPerGas * (ulong)spentGas; + Assert.That(beneficiaryReceivedFees, Is.EqualTo(expectedFees)); + Assert.That(ownerReceivedFees, Is.EqualTo(UInt256.Zero)); + } + } + + private class TestXdcTransactionProcessor : XdcTransactionProcessor + { + public TestXdcTransactionProcessor( + ITransactionProcessor.IBlobBaseFeeCalculator blobBaseFeeCalculator, + ISpecProvider? specProvider, + IWorldState? worldState, + IVirtualMachine? virtualMachine, + ICodeInfoRepository? codeInfoRepository, + ILogManager? logManager, + IMasternodeVotingContract masternodeVotingContract) + : base(blobBaseFeeCalculator, specProvider, worldState, virtualMachine, codeInfoRepository, logManager, masternodeVotingContract) + { + } + + public void TestPayFees( + Transaction tx, + XdcBlockHeader header, + IReleaseSpec spec, + ITxTracer tracer, + in TransactionSubstate substate, + long spentGas, + in UInt256 premiumPerGas, + in UInt256 blobBaseFee, + int statusCode) + { + PayFees(tx, header, spec, tracer, substate, spentGas, premiumPerGas, blobBaseFee, statusCode); + } + } +} + diff --git a/src/Nethermind/Nethermind.Xdc/Contracts/CandidateStake.cs b/src/Nethermind/Nethermind.Xdc/Contracts/CandidateStake.cs new file mode 100644 index 000000000000..1b0dacd4f064 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/Contracts/CandidateStake.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Int256; +using System.Diagnostics; + +namespace Nethermind.Xdc.Contracts; + +[DebuggerDisplay("{Address} {Stake}")] +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 new file mode 100644 index 000000000000..cc49310cfa71 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/Contracts/IMasternodeVotingContract.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Int256; + +namespace Nethermind.Xdc.Contracts; + +public interface IMasternodeVotingContract +{ + Address[] GetCandidatesByStake(BlockHeader blockHeader); + Address[] GetCandidates(BlockHeader blockHeader); + UInt256 GetCandidateStake(BlockHeader blockHeader, Address candidate); + Address GetCandidateOwner(BlockHeader blockHeader, Address candidate); + Address GetCandidateOwnerDuringProcessing(ITransactionProcessor transactionProcessor, BlockHeader blockHeader, Address candidate); +} diff --git a/src/Nethermind/Nethermind.Xdc/Contracts/MasternodeVotingContract.cs b/src/Nethermind/Nethermind.Xdc/Contracts/MasternodeVotingContract.cs new file mode 100644 index 000000000000..68e3e7b6e01c --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/Contracts/MasternodeVotingContract.cs @@ -0,0 +1,156 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Abi; +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.Evm.TransactionProcessing; +using Nethermind.Core.Crypto; +using Nethermind.Evm.State; +using Nethermind.Int256; +using System; + +namespace Nethermind.Xdc.Contracts; + +internal class MasternodeVotingContract : Contract, IMasternodeVotingContract +{ + private readonly IReadOnlyTxProcessingEnvFactory readOnlyTxProcessingEnvFactory; + + public MasternodeVotingContract( + IAbiEncoder abiEncoder, + Address contractAddress, + IReadOnlyTxProcessingEnvFactory readOnlyTxProcessingEnvFactory) + : base(abiEncoder, contractAddress ?? throw new ArgumentNullException(nameof(contractAddress)), CreateAbiDefinition()) + { + this.readOnlyTxProcessingEnvFactory = readOnlyTxProcessingEnvFactory; + } + + private static AbiDefinition CreateAbiDefinition() + { + AbiDefinitionParser abiDefinitionParser = new AbiDefinitionParser(); + return abiDefinitionParser.Parse(typeof(MasternodeVotingContract)); + } + + public UInt256 GetCandidateStake(BlockHeader blockHeader, Address candidate) + { + CallInfo callInfo = new CallInfo(blockHeader, "getCandidateCap", Address.SystemUser, candidate); + 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 GetCandidateOwnerDuringProcessing(ITransactionProcessor transactionProcessor, BlockHeader blockHeader, Address candidate) + { + byte[] result = base.CallCore(transactionProcessor, blockHeader, "getCandidateOwner", GenerateTransaction(ContractAddress, "getCandidateOwner", Address.SystemUser, candidate), true); + if (result.Length != 32) + throw new InvalidOperationException("Expected 'getCandidateOwner' to return exactly one result."); + return new Address(result.AsSpan().Slice(32 - Address.Size)); + } + + + public Address[] GetCandidates(BlockHeader blockHeader) + { + CallInfo callInfo = new CallInfo(blockHeader, "getCandidates", Address.SystemUser); + IConstantContract constant = GetConstant(readOnlyTxProcessingEnvFactory.Create()); + object[] result = constant.Call(callInfo); + return (Address[])result[0]!; + } + + /// + /// Optimization to get candidates directly from storage without going through EVM call + /// + /// + /// + public Address[] GetCandidatesFromState(BlockHeader header) + { + CandidateContractSlots variableSlot = CandidateContractSlots.Candidates; + Span input = [(byte)variableSlot]; + 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))); + } + return candidates; + } + + 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) + }); + } + XdcSort.Slice(candidatesAndStake, (x, y) => x.Stake.CompareTo(y.Stake) >= 0); + + 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, + ValidatorsState, + Voters, + KYCString, + InvalidKYCCount, + HasVotedInvalid, + OwnerToCandidate, + Owners, + Candidates, + CandidateCount, + OwnerCount, + MinCandidateCap, + MinVoterCap, + MaxValidatorNumber, + CandidateWithdrawDelay, + VoterWithdrawDelay + } +} diff --git a/src/Nethermind/Nethermind.Xdc/Contracts/MasternodeVotingContract.json b/src/Nethermind/Nethermind.Xdc/Contracts/MasternodeVotingContract.json new file mode 100644 index 000000000000..4a20ad6ee498 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/Contracts/MasternodeVotingContract.json @@ -0,0 +1,727 @@ +[ + { + "constant": false, + "inputs": [ + { + "name": "_candidate", + "type": "address" + } + ], + "name": "propose", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "owners", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_candidate", + "type": "address" + }, + { + "name": "_cap", + "type": "uint256" + } + ], + "name": "unvote", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getCandidates", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "ownerCount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "address" + } + ], + "name": "hasVotedInvalid", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_blockNumber", + "type": "uint256" + } + ], + "name": "getWithdrawCap", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "uint256" + } + ], + "name": "ownerToCandidate", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_candidate", + "type": "address" + } + ], + "name": "getVoters", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getWithdrawBlockNumbers", + "outputs": [ + { + "name": "", + "type": "uint256[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_candidate", + "type": "address" + }, + { + "name": "_voter", + "type": "address" + } + ], + "name": "getVoterCap", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_address", + "type": "address" + } + ], + "name": "getLatestKYC", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "candidates", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_blockNumber", + "type": "uint256" + }, + { + "name": "_index", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_candidate", + "type": "address" + } + ], + "name": "getCandidateCap", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_invalidCandidate", + "type": "address" + } + ], + "name": "invalidPercent", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "uint256" + } + ], + "name": "KYCString", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_candidate", + "type": "address" + } + ], + "name": "vote", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "invalidKYCCount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "candidateCount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "voterWithdrawDelay", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_candidate", + "type": "address" + } + ], + "name": "resign", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_candidate", + "type": "address" + } + ], + "name": "getCandidateOwner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_address", + "type": "address" + } + ], + "name": "getHashCount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "maxValidatorNumber", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "candidateWithdrawDelay", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_candidate", + "type": "address" + } + ], + "name": "isCandidate", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "minCandidateCap", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getOwnerCount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_invalidCandidate", + "type": "address" + } + ], + "name": "voteInvalidKYC", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "kychash", + "type": "string" + } + ], + "name": "uploadKYC", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "minVoterCap", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "_candidates", + "type": "address[]" + }, + { + "name": "_caps", + "type": "uint256[]" + }, + { + "name": "_firstOwner", + "type": "address" + }, + { + "name": "_minCandidateCap", + "type": "uint256" + }, + { + "name": "_minVoterCap", + "type": "uint256" + }, + { + "name": "_maxValidatorNumber", + "type": "uint256" + }, + { + "name": "_candidateWithdrawDelay", + "type": "uint256" + }, + { + "name": "_voterWithdrawDelay", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "_voter", + "type": "address" + }, + { + "indexed": false, + "name": "_candidate", + "type": "address" + }, + { + "indexed": false, + "name": "_cap", + "type": "uint256" + } + ], + "name": "Vote", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "_voter", + "type": "address" + }, + { + "indexed": false, + "name": "_candidate", + "type": "address" + }, + { + "indexed": false, + "name": "_cap", + "type": "uint256" + } + ], + "name": "Unvote", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "_owner", + "type": "address" + }, + { + "indexed": false, + "name": "_candidate", + "type": "address" + }, + { + "indexed": false, + "name": "_cap", + "type": "uint256" + } + ], + "name": "Propose", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "_owner", + "type": "address" + }, + { + "indexed": false, + "name": "_candidate", + "type": "address" + } + ], + "name": "Resign", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "_owner", + "type": "address" + }, + { + "indexed": false, + "name": "_blockNumber", + "type": "uint256" + }, + { + "indexed": false, + "name": "_cap", + "type": "uint256" + } + ], + "name": "Withdraw", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "_owner", + "type": "address" + }, + { + "indexed": false, + "name": "kycHash", + "type": "string" + } + ], + "name": "UploadedKYC", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "_masternodeOwner", + "type": "address" + }, + { + "indexed": false, + "name": "_masternodes", + "type": "address[]" + } + ], + "name": "InvalidatedNode", + "type": "event" + } +] diff --git a/src/Nethermind/Nethermind.Xdc/Contracts/XdcContractData.cs b/src/Nethermind/Nethermind.Xdc/Contracts/XdcContractData.cs new file mode 100644 index 000000000000..2482386cdf00 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/Contracts/XdcContractData.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Extensions; + +namespace Nethermind.Xdc.Contracts; + +internal static class XdcContractData +{ + // XDCValidatorBin is the compiled bytecode used for deploying new contracts. + public static byte[] XDCValidatorBin() => Bytes.FromHexString("606060405260043610610196576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063012679511461019b578063025e7c27146101c957806302aa9be21461022c57806306a49fce1461026e5780630db02622146102d85780630e3e4fb81461030157806315febd68146103715780632a3640b1146103a85780632d15cc041461042a5780632f9c4bba146104b8578063302b687214610522578063326586521461058e5780633477ee2e14610640578063441a3e70146106a357806358e7525f146106cf5780635b860d271461071c5780635b9cd8cc146107695780636dd7d8ea1461082457806372e44a3814610852578063a9a981a31461089f578063a9ff959e146108c8578063ae6e43f5146108f1578063b642facd1461092a578063c45607df146109a3578063d09f1ab4146109f0578063d161c76714610a19578063d51b9e9314610a42578063d55b7dff14610a93578063ef18374a14610abc578063f2ee3c7d14610ae5578063f5c9512514610b1e578063f8ac9dd514610b4c575b600080fd5b6101c7600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610b75565b005b34156101d457600080fd5b6101ea60048080359060200190919050506111fc565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561023757600080fd5b61026c600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061123b565b005b341561027957600080fd5b610281611796565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b838110156102c45780820151818401526020810190506102a9565b505050509050019250505060405180910390f35b34156102e357600080fd5b6102eb61182a565b6040518082815260200191505060405180910390f35b341561030c57600080fd5b610357600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611830565b604051808215151515815260200191505060405180910390f35b341561037c57600080fd5b610392600480803590602001909190505061185f565b6040518082815260200191505060405180910390f35b34156103b357600080fd5b6103e8600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506118bb565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561043557600080fd5b610461600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611909565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b838110156104a4578082015181840152602081019050610489565b505050509050019250505060405180910390f35b34156104c357600080fd5b6104cb6119dc565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b8381101561050e5780820151818401526020810190506104f3565b505050509050019250505060405180910390f35b341561052d57600080fd5b610578600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611a79565b6040518082815260200191505060405180910390f35b341561059957600080fd5b6105c5600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611b03565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156106055780820151818401526020810190506105ea565b50505050905090810190601f1680156106325780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561064b57600080fd5b6106616004808035906020019091905050611da2565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156106ae57600080fd5b6106cd6004808035906020019091908035906020019091905050611de1565b005b34156106da57600080fd5b610706600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061208d565b6040518082815260200191505060405180910390f35b341561072757600080fd5b610753600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506120d9565b6040518082815260200191505060405180910390f35b341561077457600080fd5b6107a9600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506121a1565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156107e95780820151818401526020810190506107ce565b50505050905090810190601f1680156108165780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610850600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061226a565b005b341561085d57600080fd5b610889600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612653565b6040518082815260200191505060405180910390f35b34156108aa57600080fd5b6108b261266b565b6040518082815260200191505060405180910390f35b34156108d357600080fd5b6108db612671565b6040518082815260200191505060405180910390f35b34156108fc57600080fd5b610928600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612677565b005b341561093557600080fd5b610961600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612c36565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156109ae57600080fd5b6109da600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612ca2565b6040518082815260200191505060405180910390f35b34156109fb57600080fd5b610a03612cee565b6040518082815260200191505060405180910390f35b3415610a2457600080fd5b610a2c612cf4565b6040518082815260200191505060405180910390f35b3415610a4d57600080fd5b610a79600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612cfa565b604051808215151515815260200191505060405180910390f35b3415610a9e57600080fd5b610aa6612d53565b6040518082815260200191505060405180910390f35b3415610ac757600080fd5b610acf612d59565b6040518082815260200191505060405180910390f35b3415610af057600080fd5b610b1c600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612d63565b005b3415610b2957600080fd5b610b4a600480803590602001908201803590602001919091929050506134f1565b005b3415610b5757600080fd5b610b5f6135f0565b6040518082815260200191505060405180910390f35b6000600b543410151515610b8857600080fd5b6000600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002080549050141580610c1c57506000600660003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002080549050115b1515610c2757600080fd5b81600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160149054906101000a900460ff16151515610c8457600080fd5b610cd934600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101546135f690919063ffffffff16565b915060088054806001018281610cef919061362d565b9160005260206000209001600085909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550506060604051908101604052803373ffffffffffffffffffffffffffffffffffffffff16815260200160011515815260200183815250600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060208201518160000160146101000a81548160ff02191690831515021790555060408201518160010155905050610eb834600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546135f690919063ffffffff16565b600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610f5160016009546135f690919063ffffffff16565b6009819055506000600660003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208054905014156110185760078054806001018281610fb6919061362d565b9160005260206000209001600033909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050600a600081548092919060010191905055505b600660003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208054806001018281611069919061362d565b9160005260206000209001600085909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208054806001018281611109919061362d565b9160005260206000209001600033909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550507f7635f1d87b47fba9f2b09e56eb4be75cca030e0cb179c1602ac9261d39a8f5c1338434604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001828152602001935050505060405180910390a1505050565b60078181548110151561120b57fe5b90600052602060002090016000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000828280600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515156112cd57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561140657600b546113f882600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461361490919063ffffffff16565b1015151561140557600080fd5b5b61145b84600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001015461361490919063ffffffff16565b600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001018190555061153384600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461361490919063ffffffff16565b600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506115cb43600f546135f690919063ffffffff16565b9250611632846000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000016000868152602001908152602001600020546135f690919063ffffffff16565b6000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000016000858152602001908152602001600020819055506000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010180548060010182816116db9190613659565b9160005260206000209001600085909190915055507faa0e554f781c3c3b2be110a0557f260f11af9a8aa2c64bc1e7a31dbb21e32fa2338686604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001828152602001935050505060405180910390a15050505050565b61179e613685565b600880548060200260200160405190810160405280929190818152602001828054801561182057602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190600101908083116117d6575b5050505050905090565b600a5481565b60056020528160005260406000206020528060005260406000206000915091509054906101000a900460ff1681565b60008060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000016000838152602001908152602001600020549050919050565b6006602052816000526040600020818154811015156118d657fe5b90600052602060002090016000915091509054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b611911613685565b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208054806020026020016040519081016040528092919081815260200182805480156119d057602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311611986575b50505050509050919050565b6119e4613699565b6000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101805480602002602001604051908101604052809291908181526020018280548015611a6f57602002820191906000526020600020905b815481526020019060010190808311611a5b575b5050505050905090565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b611b0b6136ad565b611b1482612cfa565b15611c655760036000611b2684612c36565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600160036000611b6f86612c36565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208054905003815481101515611bba57fe5b90600052602060002090018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015611c595780601f10611c2e57610100808354040283529160200191611c59565b820191906000526020600020905b815481529060010190602001808311611c3c57829003601f168201915b50505050509050611d9d565b600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208054905003815481101515611cf657fe5b90600052602060002090018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015611d955780601f10611d6a57610100808354040283529160200191611d95565b820191906000526020600020905b815481529060010190602001808311611d7857829003601f168201915b505050505090505b919050565b600881815481101515611db157fe5b90600052602060002090016000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008282600082111515611df457600080fd5b814310151515611e0357600080fd5b60008060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600001600084815260200190815260200160002054111515611e6457600080fd5b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010182815481101515611eb357fe5b906000526020600020900154141515611ecb57600080fd5b6000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160008681526020019081526020016000205492506000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000016000868152602001908152602001600020600090556000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010184815481101515611fc457fe5b9060005260206000209001600090553373ffffffffffffffffffffffffffffffffffffffff166108fc849081150290604051600060405180830381858888f19350505050151561201357600080fd5b7ff279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568338685604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001838152602001828152602001935050505060405180910390a15050505050565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101549050919050565b60008082600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160149054906101000a900460ff16151561213857600080fd5b61214184612c36565b915061214b612d59565b6064600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020540281151561219757fe5b0492505050919050565b6003602052816000526040600020818154811015156121bc57fe5b9060005260206000209001600091509150508054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156122625780601f1061223757610100808354040283529160200191612262565b820191906000526020600020905b81548152906001019060200180831161224557829003601f168201915b505050505081565b600c54341015151561227b57600080fd5b80600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160149054906101000a900460ff1615156122d757600080fd5b61232c34600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101546135f690919063ffffffff16565b600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101819055506000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054141561249b57600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805480600101828161244b919061362d565b9160005260206000209001600033909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505b61252d34600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546135f690919063ffffffff16565b600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055507f66a9138482c99e9baf08860110ef332cc0c23b4a199a53593d8db0fc8f96fbfc338334604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001828152602001935050505060405180910390a15050565b60046020528060005260406000206000915090505481565b60095481565b600f5481565b6000806000833373ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614151561271957600080fd5b84600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160149054906101000a900460ff16151561277557600080fd5b6000600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160146101000a81548160ff0219169083151502179055506127e6600160095461361490919063ffffffff16565b600981905550600094505b6008805490508510156128bb578573ffffffffffffffffffffffffffffffffffffffff1660088681548110151561282457fe5b906000526020600020900160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614156128ae5760088581548110151561287b57fe5b906000526020600020900160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556128bb565b84806001019550506127f1565b600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054935061299284600160008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001015461361490919063ffffffff16565b600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101819055506000600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550612a7243600e546135f690919063ffffffff16565b9250612ad9846000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000016000868152602001908152602001600020546135f690919063ffffffff16565b6000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000016000858152602001908152602001600020819055506000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001018054806001018281612b829190613659565b9160005260206000209001600085909190915055507f4edf3e325d0063213a39f9085522994a1c44bea5f39e7d63ef61260a1e58c6d33387604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a1505050505050565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b6000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805490509050919050565b600d5481565b600e5481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160149054906101000a900460ff169050919050565b600b5481565b6000600a54905090565b600080612d6e613685565b600080600033600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160149054906101000a900460ff161515612dcf57600080fd5b87600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160149054906101000a900460ff161515612e2b57600080fd5b612e3433612c36565b9750612e3f89612c36565b9650600560008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16151515612ed757600080fd5b6001600560008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055506001600460008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550604b612fc4612d59565b6064600460008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020540281151561301057fe5b041015156134e65760016008805490500360405180591061302e5750595b9080825280602002602001820160405250955060009450600093505b600880549050841015613357578673ffffffffffffffffffffffffffffffffffffffff166130b160088681548110151561308057fe5b906000526020600020900160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16612c36565b73ffffffffffffffffffffffffffffffffffffffff16141561334a576130e3600160095461361490919063ffffffff16565b6009819055506008848154811015156130f857fe5b906000526020600020900160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16868680600101975081518110151561313857fe5b9060200190602002019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff168152505060088481548110151561318357fe5b906000526020600020900160006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055600160006008868154811015156131c457fe5b906000526020600020900160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600080820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556000820160146101000a81549060ff021916905560018201600090555050600360008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006132bb91906136c1565b600660008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600061330691906136e2565b600460008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600090555b838060010194505061304a565b600092505b600780549050831015613439578673ffffffffffffffffffffffffffffffffffffffff1660078481548110151561338f57fe5b906000526020600020900160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561342c576007838154811015156133e657fe5b906000526020600020900160006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055600a6000815480929190600190039190505550613439565b828060010193505061335c565b7fe18d61a5bf4aa2ab40afc88aa9039d27ae17ff4ec1c65f5f414df6f02ce8b35e8787604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001828103825283818151815260200191508051906020019060200280838360005b838110156134d15780820151818401526020810190506134b6565b50505050905001935050505060405180910390a15b505050505050505050565b600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002080548060010182816135429190613703565b91600052602060002090016000848490919290919250919061356592919061372f565b50507f949360d814b28a3b393a68909efe1fee120ee09cac30f360a0f80ab5415a611a338383604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001806020018281038252848482818152602001925080828437820191505094505050505060405180910390a15050565b600c5481565b600080828401905083811015151561360a57fe5b8091505092915050565b600082821115151561362257fe5b818303905092915050565b8154818355818115116136545781836000526020600020918201910161365391906137af565b5b505050565b8154818355818115116136805781836000526020600020918201910161367f91906137af565b5b505050565b602060405190810160405280600081525090565b602060405190810160405280600081525090565b602060405190810160405280600081525090565b50805460008255906000526020600020908101906136df91906137d4565b50565b508054600082559060005260206000209081019061370091906137af565b50565b81548183558181151161372a5781836000526020600020918201910161372991906137d4565b5b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061377057803560ff191683800117855561379e565b8280016001018555821561379e579182015b8281111561379d578235825591602001919060010190613782565b5b5090506137ab91906137af565b5090565b6137d191905b808211156137cd5760008160009055506001016137b5565b5090565b90565b6137fd91905b808211156137f957600081816137f09190613800565b506001016137da565b5090565b90565b50805460018160011615610100020316600290046000825580601f106138265750613845565b601f01602090049060005260206000209081019061384491906137af565b5b505600a165627a7a72305820f5bbb127b52ce86c873faef85cff176563476a5e49a3d88eaa9a06a8f432c9080029"); +} diff --git a/src/Nethermind/Nethermind.Xdc/DifficultyCalculator.cs b/src/Nethermind/Nethermind.Xdc/DifficultyCalculator.cs index fee5e491aaf5..dd029f693aa9 100644 --- a/src/Nethermind/Nethermind.Xdc/DifficultyCalculator.cs +++ b/src/Nethermind/Nethermind.Xdc/DifficultyCalculator.cs @@ -4,13 +4,9 @@ using Nethermind.Consensus; using Nethermind.Core; using Nethermind.Int256; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Nethermind.Xdc; + internal class DifficultyCalculator : IDifficultyCalculator { public UInt256 Calculate(BlockHeader header, BlockHeader parent) diff --git a/src/Nethermind/Nethermind.Xdc/EpochSwitchInfo.cs b/src/Nethermind/Nethermind.Xdc/EpochSwitchInfo.cs deleted file mode 100644 index 7eabe3b74319..000000000000 --- a/src/Nethermind/Nethermind.Xdc/EpochSwitchInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Core; -using Nethermind.Xdc.Types; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Nethermind.Xdc; -public class EpochSwitchInfo -{ - public Address[] Penalties { get; set; } - public Address[] Masternodes { get; set; } - public BlockRoundInfo EpochSwitchBlockInfo { get; set; } - public BlockRoundInfo EpochSwitchParentBlockInfo { get; set; } -} diff --git a/src/Nethermind/Nethermind.Xdc/EpochSwitchManager.cs b/src/Nethermind/Nethermind.Xdc/EpochSwitchManager.cs new file mode 100644 index 000000000000..fe1f440411cb --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/EpochSwitchManager.cs @@ -0,0 +1,412 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Core; +using Nethermind.Core.Caching; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Types; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Nethermind.Xdc; + +internal class EpochSwitchManager : IEpochSwitchManager +{ + public EpochSwitchManager(ISpecProvider xdcSpecProvider, IBlockTree tree, ISnapshotManager snapshotManager) + { + _xdcSpecProvider = xdcSpecProvider; + _tree = tree; + _snapshotManager = snapshotManager; + } + + private ISpecProvider _xdcSpecProvider { get; } + private IBlockTree _tree { get; } + private ISnapshotManager _snapshotManager { get; } + private LruCache _round2EpochBlockInfo { get; set; } = new(XdcConstants.InMemoryRound2Epochs, nameof(_round2EpochBlockInfo)); + private LruCache _epochSwitches { get; set; } = new(XdcConstants.InMemoryEpochs, nameof(_epochSwitches)); + + /** + * Determine if the given block is an epoch switch block. + **/ + public bool IsEpochSwitchAtBlock(XdcBlockHeader header) + { + var xdcSpec = _xdcSpecProvider.GetXdcSpec(header); + + if (header.Number == xdcSpec.SwitchBlock) + { + return true; + } + + if (header.ExtraConsensusData is null) + { + return false; + } + + var round = header.ExtraConsensusData.BlockRound; + var qc = header.ExtraConsensusData.QuorumCert; + + ulong parentRound = qc.ProposedBlockInfo.Round; + ulong epochStartRound = round - (round % (ulong)xdcSpec.EpochLength); + ulong epochNumber = (ulong)xdcSpec.SwitchEpoch + round / (ulong)xdcSpec.EpochLength; + + if (qc.ProposedBlockInfo.BlockNumber == xdcSpec.SwitchBlock) + { + return true; + } + + if (parentRound < epochStartRound) + { + _round2EpochBlockInfo.Set(round, new BlockRoundInfo(header.Hash, round, header.Number)); + return true; + } + + return false; + } + + /** + * Determine if an epoch switch occurs at the given round, based on the parent block. + **/ + public bool IsEpochSwitchAtRound(ulong currentRound, XdcBlockHeader parent) + { + var xdcSpec = _xdcSpecProvider.GetXdcSpec(parent); + + ulong epochNumber = (ulong)xdcSpec.SwitchEpoch + currentRound / (ulong)xdcSpec.EpochLength; + + if (parent.Number == xdcSpec.SwitchBlock) + { + return true; + } + + if (parent.ExtraConsensusData is null) + { + return false; + } + + var parentRound = parent.ExtraConsensusData.BlockRound; + if (currentRound <= parentRound) + { + return false; + } + + ulong epochStartRound = currentRound - (currentRound % (ulong)xdcSpec.EpochLength); + return parentRound < epochStartRound; + } + + public EpochSwitchInfo? GetEpochSwitchInfo(XdcBlockHeader header) + { + if (_epochSwitches.TryGet(header.Hash, out var epochSwitchInfo)) + { + return epochSwitchInfo; + } + + var xdcSpec = _xdcSpecProvider.GetXdcSpec(header); + + while (!IsEpochSwitchAtBlock(header)) + { + header = (XdcBlockHeader)_tree.FindHeader(header.ParentHash); + } + + Address[] masterNodes; + + if (header.Number == xdcSpec.SwitchBlock) + { + masterNodes = xdcSpec.GenesisMasterNodes; + } + else + { + if (header.ExtraConsensusData is null) + { + return null; + } + + masterNodes = header.ValidatorsAddress.Value.ToArray(); + } + + var snap = _snapshotManager.GetSnapshotByBlockNumber(header.Number, xdcSpec); + if (snap is null) + { + return null; + } + + Address[] penalties = header.PenaltiesAddress.Value.ToArray(); + Address[] candidates = snap.NextEpochCandidates; + + var standbyNodes = Array.Empty
(); + + if (masterNodes.Length != candidates.Length) + { + standbyNodes = candidates + .Except(masterNodes) + .Except(penalties) + .ToArray(); + } + + epochSwitchInfo = new EpochSwitchInfo(masterNodes, standbyNodes, penalties, new BlockRoundInfo(header.Hash, header.ExtraConsensusData?.BlockRound ?? 0, header.Number)); + + if (header.ExtraConsensusData?.QuorumCert is not null) + { + epochSwitchInfo.EpochSwitchParentBlockInfo = header.ExtraConsensusData.QuorumCert.ProposedBlockInfo; + } + + _epochSwitches.Set(header.Hash, epochSwitchInfo); + return epochSwitchInfo; + } + + public EpochSwitchInfo? GetEpochSwitchInfo(Hash256 hash) + { + if (_epochSwitches.TryGet(hash, out var epochSwitchInfo)) + { + return epochSwitchInfo; + } + + XdcBlockHeader h = (XdcBlockHeader)_tree.FindHeader(hash); + if (h is null) + { + return null; + } + + return GetEpochSwitchInfo(h); + } + + private EpochSwitchInfo[] GetEpochSwitchBetween(XdcBlockHeader start, XdcBlockHeader end) + { + var epochSwitchInfos = new List(); + + Hash256 iteratorHash = end.Hash; + long iteratorBlockNumber = end.Number; + + while (iteratorBlockNumber > start.Number) + { + EpochSwitchInfo epochSwitchInfo; + + if ((epochSwitchInfo = GetEpochSwitchInfo(iteratorHash)) is null) + { + return null; + } + + if (epochSwitchInfo.EpochSwitchParentBlockInfo is null) + { + break; + } + + iteratorHash = epochSwitchInfo.EpochSwitchParentBlockInfo.Hash; + iteratorBlockNumber = epochSwitchInfo.EpochSwitchBlockInfo.BlockNumber; + + if (iteratorBlockNumber >= start.Number) + { + epochSwitchInfos.Add(epochSwitchInfo); + } + } + + epochSwitchInfos.Reverse(); + return epochSwitchInfos.ToArray(); + } + + private BlockRoundInfo? GetBlockInfoInCache(ulong estRound, ulong epoch) + { + var epochSwitchInCache = new List(); + + for (ulong r = estRound; r < estRound + (ulong)epoch; r++) + { + if (_round2EpochBlockInfo.TryGet(r, out BlockRoundInfo blockInfo)) + { + epochSwitchInCache.Add(blockInfo); + } + } + + if (epochSwitchInCache.Count == 1) + { + return epochSwitchInCache[0]; + } + else if (epochSwitchInCache.Count == 0) + { + return null; + } + + foreach (var blockInfo in epochSwitchInCache) + { + var header = _tree.FindHeader(blockInfo.BlockNumber); + if (header is null) + { + continue; + } + if (header.Hash == blockInfo.Hash) + { + return blockInfo; + } + } + + return null; + } + + private bool TryBinarySearchBlockByEpochNumber(ulong targetEpochNumber, long start, long end, ulong switchBlock, ulong epoch, IXdcReleaseSpec xdcSpec, out BlockRoundInfo epochBlockInfo) + { + while (start < end) + { + var header = (XdcBlockHeader)_tree.FindHeader((start + end) / 2); + if (header is null) + { + epochBlockInfo = null; + return false; + } + + if (header.ExtraConsensusData is null) + { + epochBlockInfo = null; + return false; + } + + bool isEpochSwitch = IsEpochSwitchAtBlock(header); + ulong epochNum = (ulong)xdcSpec.SwitchEpoch + (header.ExtraConsensusData?.BlockRound ?? 0) / (ulong)xdcSpec.EpochLength; + + if (epochNum == targetEpochNumber) + { + if (header.ExtraConsensusData is null) + { + epochBlockInfo = null; + return false; + } + + ulong round = header.ExtraConsensusData.BlockRound; + + if (isEpochSwitch) + { + epochBlockInfo = new BlockRoundInfo(header.Hash, round, header.Number); + return true; + } + else + { + end = header.Number; + // trick to shorten the search + start = Math.Max(start, end - (int)(round % epoch)); + } + } + else if (epochNum > targetEpochNumber) + { + end = header.Number; + } + else + { + long nextStart = header.Number; + if (nextStart == start) + { + break; + } + start = nextStart; + } + } + + epochBlockInfo = null; + return false; + } + + public EpochSwitchInfo? GetTimeoutCertificateEpochInfo(TimeoutCertificate timeoutCert) + { + var headOfChainHeader = (XdcBlockHeader)_tree.Head.Header; + + EpochSwitchInfo epochSwitchInfo = GetEpochSwitchInfo(headOfChainHeader); + if (epochSwitchInfo is null) + { + return null; + } + + var xdcSpec = _xdcSpecProvider.GetXdcSpec(headOfChainHeader); + + ulong epochRound = epochSwitchInfo.EpochSwitchBlockInfo.Round; + ulong tempTCEpoch = (ulong)xdcSpec.SwitchEpoch + epochRound / (ulong)xdcSpec.EpochLength; + + var epochBlockInfo = new BlockRoundInfo(epochSwitchInfo.EpochSwitchBlockInfo.Hash, epochRound, epochSwitchInfo.EpochSwitchBlockInfo.BlockNumber); + + while (epochBlockInfo.Round > timeoutCert.Round) + { + tempTCEpoch--; + epochBlockInfo = GetBlockByEpochNumber(tempTCEpoch); + if (epochBlockInfo is null) + { + return null; + } + } + + return GetEpochSwitchInfo(epochBlockInfo.Hash); + } + + public BlockRoundInfo? GetBlockByEpochNumber(ulong targetEpoch) + { + var headHeader = _tree.Head?.Header as XdcBlockHeader; + if (headHeader is null) + { + return null; + } + var xdcSpec = _xdcSpecProvider.GetXdcSpec(headHeader); + + EpochSwitchInfo epochSwitchInfo = GetEpochSwitchInfo(headHeader); + if (epochSwitchInfo is null) + { + return null; + } + + ulong epochNumber = (ulong)xdcSpec.SwitchEpoch + epochSwitchInfo.EpochSwitchBlockInfo.Round / (ulong)xdcSpec.EpochLength; + + if (targetEpoch == epochNumber) + { + return epochSwitchInfo.EpochSwitchBlockInfo; + } + + if (targetEpoch > epochNumber) + { + return null; + } + + if (targetEpoch < (ulong)xdcSpec.SwitchEpoch) + { + return null; + } + + ulong estRound = (targetEpoch - (ulong)xdcSpec.SwitchEpoch) * (ulong)xdcSpec.EpochLength; + + var epochBlockInfo = GetBlockInfoInCache(estRound, (ulong)xdcSpec.EpochLength); + if (epochBlockInfo is not null) + { + return epochBlockInfo; + } + + var epoch = (ulong)xdcSpec.EpochLength; + ulong estBlockNumDiff = epoch * (epochNumber - targetEpoch); + long estBlockNum = Math.Max((long)xdcSpec.SwitchBlock, epochSwitchInfo.EpochSwitchBlockInfo.BlockNumber - (long)estBlockNumDiff); + + ulong closeEpochNum = 2ul; + + if (closeEpochNum >= epochNumber - targetEpoch) + { + var estBlockHeader = (XdcBlockHeader)_tree.FindHeader(estBlockNum); + if (estBlockHeader is null) + { + return null; + } + var epochSwitchInfos = GetEpochSwitchBetween(estBlockHeader, headHeader); + if (epochSwitchInfos is null) + { + return null; + } + foreach (var info in epochSwitchInfos) + { + ulong epochNum = (ulong)xdcSpec.SwitchEpoch + info.EpochSwitchBlockInfo.Round / (ulong)xdcSpec.EpochLength; + if (epochNum == targetEpoch) + { + return info.EpochSwitchBlockInfo; + } + } + } + + if (!TryBinarySearchBlockByEpochNumber(targetEpoch, estBlockNum, epochSwitchInfo.EpochSwitchBlockInfo.BlockNumber, (ulong)xdcSpec.SwitchBlock, (ulong)xdcSpec.EpochLength, xdcSpec, out epochBlockInfo)) + { + return null; + } + + return epochBlockInfo; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/Errors/CertificateValidationException.cs b/src/Nethermind/Nethermind.Xdc/Errors/CertificateValidationException.cs deleted file mode 100644 index 4c877ab73773..000000000000 --- a/src/Nethermind/Nethermind.Xdc/Errors/CertificateValidationException.cs +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Nethermind.Xdc.Errors; - -public enum CertificateType -{ - QuorumCertificate, - TimeoutCertificate -} - -public enum CertificateValidationFailure -{ - InvalidContent, - InvalidSignatures, - InvalidRound, - InvalidGapNumber - -} - -[Serializable] -public class CertificateValidationException(CertificateType certificateType, CertificateValidationFailure failure, Exception? innerException = null) - : Exception($"Invalid {certificateType}: {failure}", innerException) -{ - public CertificateType CertificateType { get; } = certificateType; - public CertificateValidationFailure Failure { get; } = failure; -} diff --git a/src/Nethermind/Nethermind.Xdc/Errors/ConsensusHeaderDataExtractionException.cs b/src/Nethermind/Nethermind.Xdc/Errors/DataExtractionException.cs similarity index 72% rename from src/Nethermind/Nethermind.Xdc/Errors/ConsensusHeaderDataExtractionException.cs rename to src/Nethermind/Nethermind.Xdc/Errors/DataExtractionException.cs index 96524245ffc6..2458c6531f67 100644 --- a/src/Nethermind/Nethermind.Xdc/Errors/ConsensusHeaderDataExtractionException.cs +++ b/src/Nethermind/Nethermind.Xdc/Errors/DataExtractionException.cs @@ -5,7 +5,7 @@ namespace Nethermind.Xdc.Errors; -public class ConsensusHeaderDataExtractionException(string fieldType, Exception? innerException = null) +public class DataExtractionException(string fieldType, Exception? innerException = null) : Exception($"Failed to get {fieldType} from header", innerException) { public string FieldType { get; } = fieldType; diff --git a/src/Nethermind/Nethermind.Xdc/Errors/IncomingMessageRoundNotEqualCurrentRoundException.cs b/src/Nethermind/Nethermind.Xdc/Errors/IncomingMessageRoundNotEqualCurrentRoundException.cs index 9af25294097d..cd0a2095bf35 100644 --- a/src/Nethermind/Nethermind.Xdc/Errors/IncomingMessageRoundNotEqualCurrentRoundException.cs +++ b/src/Nethermind/Nethermind.Xdc/Errors/IncomingMessageRoundNotEqualCurrentRoundException.cs @@ -4,6 +4,7 @@ using System; namespace Nethermind.Xdc.Errors; + public class IncomingMessageRoundNotEqualCurrentRoundException(ulong incomingRound, ulong currentRound, Exception? innerException = null) : Exception($"message round number: {incomingRound} does not match currentRound: {currentRound}", innerException) { diff --git a/src/Nethermind/Nethermind.Xdc/IEpochSwitchManager.cs b/src/Nethermind/Nethermind.Xdc/IEpochSwitchManager.cs index afcf4a97c4b1..68347e987511 100644 --- a/src/Nethermind/Nethermind.Xdc/IEpochSwitchManager.cs +++ b/src/Nethermind/Nethermind.Xdc/IEpochSwitchManager.cs @@ -1,24 +1,17 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Nethermind.Blockchain; -using Nethermind.Xdc.Types; using Nethermind.Core.Crypto; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Nethermind.Xdc.Types; namespace Nethermind.Xdc; + public interface IEpochSwitchManager { - EpochSwitchInfo? GetPreviousEpochSwitchInfoByHash(Hash256 parentHash, int limit); - bool IsEpochSwitchAtRound(ulong currentRound, XdcBlockHeader parent, out ulong epochNumber); - bool IsEpochSwitchAtBlock(XdcBlockHeader header, out ulong epochNumber); - EpochSwitchInfo? GetEpochSwitchInfo(XdcBlockHeader header, Hash256 parentHash); - bool IsEpochSwitch(XdcBlockHeader header, out ulong epochNumber); - EpochSwitchInfo[] GetEpochSwitchBetween(XdcBlockHeader start, XdcBlockHeader end); - (ulong currentCheckpointNumber, ulong epochNumber) GetCurrentEpochNumbers(ulong blockNumber); - EpochSwitchInfo? GetTimeoutCertificateEpochInfo(TimeoutCert timeoutCert); + bool IsEpochSwitchAtRound(ulong currentRound, XdcBlockHeader parent); + bool IsEpochSwitchAtBlock(XdcBlockHeader header); + EpochSwitchInfo? GetEpochSwitchInfo(XdcBlockHeader? header); + EpochSwitchInfo? GetEpochSwitchInfo(Hash256 blockHash); + EpochSwitchInfo? GetTimeoutCertificateEpochInfo(TimeoutCertificate timeoutCertificate); + BlockRoundInfo? GetBlockByEpochNumber(ulong epochNumber); } diff --git a/src/Nethermind/Nethermind.Xdc/IForensicsProcessor.cs b/src/Nethermind/Nethermind.Xdc/IForensicsProcessor.cs index 6760e280ba9d..7fc1b9d29392 100644 --- a/src/Nethermind/Nethermind.Xdc/IForensicsProcessor.cs +++ b/src/Nethermind/Nethermind.Xdc/IForensicsProcessor.cs @@ -4,30 +4,27 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Xdc.Types; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace Nethermind.Xdc; public interface IForensicsProcessor { - Task ForensicsMonitoring(IEnumerable headerQcToBeCommitted, QuorumCert incomingQC); + Task ForensicsMonitoring(IEnumerable headerQcToBeCommitted, QuorumCertificate incomingQC); - Task SetCommittedQCs(IEnumerable headers, QuorumCert incomingQC); + Task SetCommittedQCs(IEnumerable headers, QuorumCertificate incomingQC); - Task ProcessForensics(QuorumCert incomingQC); + Task ProcessForensics(QuorumCertificate incomingQC); - Task SendForensicProof(QuorumCert firstQc, QuorumCert secondQc); + Task SendForensicProof(QuorumCertificate firstQc, QuorumCertificate secondQc); (Hash256 AncestorHash, IList FirstPath, IList SecondPath) FindAncestorBlockHash(BlockRoundInfo firstBlockInfo, BlockRoundInfo secondBlockInfo); Task ProcessVoteEquivocation(Vote incomingVote); - Task DetectEquivocationInVotePool(Vote vote, List votePool); + Task DetectEquivocationInVotePool(Vote vote, IEnumerable votePool); Task SendVoteEquivocationProof(Vote vote1, Vote vote2, Address signer); } diff --git a/src/Nethermind/Nethermind.Xdc/IPenaltyHandler.cs b/src/Nethermind/Nethermind.Xdc/IPenaltyHandler.cs index 242441f9791d..f9906aec6281 100644 --- a/src/Nethermind/Nethermind.Xdc/IPenaltyHandler.cs +++ b/src/Nethermind/Nethermind.Xdc/IPenaltyHandler.cs @@ -3,15 +3,9 @@ using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Evm.State; -using Nethermind.Xdc.Types; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Nethermind.Xdc; + public interface IPenaltyHandler { Address[] HandlePenalties(long number, Hash256 currentHash, Address[] candidates); diff --git a/src/Nethermind/Nethermind.Xdc/IQuorumCertificateManager.cs b/src/Nethermind/Nethermind.Xdc/IQuorumCertificateManager.cs index 2b484df97243..45b8a89458b9 100644 --- a/src/Nethermind/Nethermind.Xdc/IQuorumCertificateManager.cs +++ b/src/Nethermind/Nethermind.Xdc/IQuorumCertificateManager.cs @@ -2,15 +2,15 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Xdc.Types; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Nethermind.Xdc; + public interface IQuorumCertificateManager { - void CommitCertificate(QuorumCert qc); - void VerifyCertificate(QuorumCert qc, XdcBlockHeader parentHeader); + QuorumCertificate HighestKnownCertificate { get; } + QuorumCertificate LockCertificate { get; } + + void CommitCertificate(QuorumCertificate qc); + bool VerifyCertificate(QuorumCertificate qc, XdcBlockHeader certificateTarget, out string error); + void Initialize(XdcBlockHeader current); } diff --git a/src/Nethermind/Nethermind.Xdc/ISignTransactionManager.cs b/src/Nethermind/Nethermind.Xdc/ISignTransactionManager.cs new file mode 100644 index 000000000000..513d649a432d --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/ISignTransactionManager.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Xdc.Spec; +using System.Threading.Tasks; + +namespace Nethermind.Xdc; + +internal interface ISignTransactionManager +{ + Task SubmitTransactionSign(XdcBlockHeader header, IXdcReleaseSpec spec); +} diff --git a/src/Nethermind/Nethermind.Xdc/ISignatureManager.cs b/src/Nethermind/Nethermind.Xdc/ISignatureManager.cs new file mode 100644 index 000000000000..05a9056b3d17 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/ISignatureManager.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Consensus; +using Nethermind.Core; +using Nethermind.Core.Crypto; + +namespace Nethermind.Xdc; + +public interface ISignatureManager +{ + public ISigner CurrentSigner { get; } + public ISignerStore CurrentSignerStore { get; } + bool VerifyMessageSignature(Hash256 hash256, Signature signature, Address[] masternodes, out Address? address); +} diff --git a/src/Nethermind/Nethermind.Xdc/ISnapshotManager.cs b/src/Nethermind/Nethermind.Xdc/ISnapshotManager.cs index 67ea7385393e..cd41816d6a4d 100644 --- a/src/Nethermind/Nethermind.Xdc/ISnapshotManager.cs +++ b/src/Nethermind/Nethermind.Xdc/ISnapshotManager.cs @@ -5,16 +5,19 @@ using Nethermind.Core.Crypto; using Nethermind.Xdc.Spec; using Nethermind.Xdc.Types; -using System; -using System.Collections.Frozen; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Collections.ObjectModel; namespace Nethermind.Xdc; + public interface ISnapshotManager { - Snapshot? GetSnapshot(Hash256 hash); + static bool IsTimeForSnapshot(long blockNumber, IXdcReleaseSpec spec) + { + if (blockNumber == spec.SwitchBlock) + return true; + return blockNumber % spec.EpochLength == spec.EpochLength - spec.Gap; + } + Snapshot? GetSnapshotByGapNumber(long gapNumber); + Snapshot? GetSnapshotByBlockNumber(long blockNumber, IXdcReleaseSpec spec); void StoreSnapshot(Snapshot snapshot); - (Address[] Masternodes, Address[] PenalizedNodes) CalculateNextEpochMasternodes(XdcBlockHeader header, IXdcReleaseSpec spec); + (Address[] Masternodes, Address[] PenalizedNodes) CalculateNextEpochMasternodes(long blockNumber, Hash256 parentHash, IXdcReleaseSpec spec); } diff --git a/src/Nethermind/Nethermind.Xdc/ISyncInfoManager.cs b/src/Nethermind/Nethermind.Xdc/ISyncInfoManager.cs index 4ca0423ac7eb..c9a98ff58358 100644 --- a/src/Nethermind/Nethermind.Xdc/ISyncInfoManager.cs +++ b/src/Nethermind/Nethermind.Xdc/ISyncInfoManager.cs @@ -2,14 +2,10 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Xdc.Types; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Nethermind.Xdc; -internal interface ISyncInfoManager + +public interface ISyncInfoManager { void ProcessSyncInfo(SyncInfo syncInfo); bool VerifySyncInfo(SyncInfo syncInfo); diff --git a/src/Nethermind/Nethermind.Xdc/ITimeoutCertificateManager.cs b/src/Nethermind/Nethermind.Xdc/ITimeoutCertificateManager.cs index 2bf340c6abc9..87204820daef 100644 --- a/src/Nethermind/Nethermind.Xdc/ITimeoutCertificateManager.cs +++ b/src/Nethermind/Nethermind.Xdc/ITimeoutCertificateManager.cs @@ -3,17 +3,16 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Xdc.Types; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace Nethermind.Xdc; -internal interface ITimeoutCertificateManager + +public interface ITimeoutCertificateManager { - void HandleTimeout(Timeout timeout); - void OnCountdownTimer(DateTime time); - void ProcessTimeoutCertificate(TimeoutCert timeoutCert); - void VerifyTimeoutCertificate(TimeoutCert timeoutCert); + Task OnReceiveTimeout(Timeout timeout); + Task HandleTimeoutVote(Timeout timeout); + void OnCountdownTimer(); + void ProcessTimeoutCertificate(TimeoutCertificate timeoutCertificate); + bool VerifyTimeoutCertificate(TimeoutCertificate timeoutCertificate, out string errorMessage); + long GetTimeoutsCount(Timeout timeout); } diff --git a/src/Nethermind/Nethermind.Xdc/ITimeoutTimer.cs b/src/Nethermind/Nethermind.Xdc/ITimeoutTimer.cs new file mode 100644 index 000000000000..56bdb3682f1f --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/ITimeoutTimer.cs @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Xdc; + +public interface ITimeoutTimer +{ + void Reset(TimeSpan period); + void Start(TimeSpan period); + void TriggerTimeout(); +} diff --git a/src/Nethermind/Nethermind.Xdc/IVotesManager.cs b/src/Nethermind/Nethermind.Xdc/IVotesManager.cs index 89d64ade1320..6337ec0d51e5 100644 --- a/src/Nethermind/Nethermind.Xdc/IVotesManager.cs +++ b/src/Nethermind/Nethermind.Xdc/IVotesManager.cs @@ -1,19 +1,18 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Core.Crypto; using Nethermind.Xdc.Types; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace Nethermind.Xdc; -internal interface IVotesManager + +public interface IVotesManager { Task CastVote(BlockRoundInfo blockInfo); Task HandleVote(Vote vote); - Task VerifyVotes(List votes, XdcBlockHeader header); - bool VerifyVotingRules(BlockRoundInfo blockInfo, QuorumCert qc); - List GetVotes(); + Task OnReceiveVote(Vote vote); + bool VerifyVotingRules(BlockRoundInfo roundInfo, QuorumCertificate certificate); + bool VerifyVotingRules(XdcBlockHeader header); + bool VerifyVotingRules(Hash256 blockHash, long blockNumber, ulong roundNumber, QuorumCertificate qc); } diff --git a/src/Nethermind/Nethermind.Xdc/IXdcConsensusContext.cs b/src/Nethermind/Nethermind.Xdc/IXdcConsensusContext.cs new file mode 100644 index 000000000000..08f90a1c4c40 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/IXdcConsensusContext.cs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Xdc.Types; +using System; + +namespace Nethermind.Xdc; + +public interface IXdcConsensusContext +{ + ulong CurrentRound { get; } + BlockRoundInfo HighestCommitBlock { get; set; } + QuorumCertificate? HighestQC { get; set; } + TimeoutCertificate? HighestTC { get; set; } + QuorumCertificate? LockQC { get; set; } + int TimeoutCounter { get; set; } + DateTime RoundStarted { get; } + + event EventHandler NewRoundSetEvent; + + void SetNewRound(); + void SetNewRound(ulong round); +} diff --git a/src/Nethermind/Nethermind.Xdc/IXdcHeaderStore.cs b/src/Nethermind/Nethermind.Xdc/IXdcHeaderStore.cs new file mode 100644 index 000000000000..f2f42c82cc3b --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/IXdcHeaderStore.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain.Headers; +using Nethermind.Core.Crypto; +using System.Collections.Generic; + +namespace Nethermind.Xdc; + +internal interface IXdcHeaderStore : IHeaderStore +{ + void Insert(XdcBlockHeader header) => ((IHeaderStore)this).Insert(header); + 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/IXdcSealer.cs b/src/Nethermind/Nethermind.Xdc/IXdcSealer.cs deleted file mode 100644 index ab47b32c82be..000000000000 --- a/src/Nethermind/Nethermind.Xdc/IXdcSealer.cs +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Consensus; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Nethermind.Xdc; -internal interface IXdcSealer : ISealer -{ - bool CanSeal(ulong round, XdcBlockHeader parentHash); -} diff --git a/src/Nethermind/Nethermind.Xdc/InitializeBlockchainXdc.cs b/src/Nethermind/Nethermind.Xdc/InitializeBlockchainXdc.cs new file mode 100644 index 000000000000..1b317a027c43 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/InitializeBlockchainXdc.cs @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Api; +using Nethermind.Core; +using Nethermind.Init.Steps; +using Nethermind.TxPool; +using Nethermind.TxPool.Filters; +using Nethermind.Xdc.TxPool; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Nethermind.Xdc; + +internal class InitializeBlockchainXdc(INethermindApi api, IChainHeadInfoProvider chainHeadInfoProvider) + : InitializeBlockchain(api, chainHeadInfoProvider) +{ + private readonly INethermindApi _api = api; + protected override ITxPool CreateTxPool(IChainHeadInfoProvider chainHeadInfoProvider) + { + _api.TxGossipPolicy.Policies.Add(new XdcTxGossipPolicy(_api.SpecProvider, chainHeadInfoProvider)); + + Nethermind.TxPool.TxPool txPool = new(_api.EthereumEcdsa!, + _api.BlobTxStorage ?? NullBlobTxStorage.Instance, + chainHeadInfoProvider, + _api.Config(), + _api.TxValidator!, + _api.LogManager, + CreateTxPoolTxComparer(), + _api.TxGossipPolicy, + new SignTransactionFilter(_api.EngineSigner, _api.BlockTree, _api.SpecProvider), + _api.HeadTxValidator + ); + + _api.DisposeStack.Push(txPool); + return txPool; + } + + protected new IComparer CreateTxPoolTxComparer() + { + return new XdcTransactionComparerProvider(_api.SpecProvider!, _api.BlockTree!).GetDefaultComparer(); + } +} diff --git a/src/Nethermind/Nethermind.Xdc/MustBeEmptyUnclesValidator.cs b/src/Nethermind/Nethermind.Xdc/MustBeEmptyUnclesValidator.cs new file mode 100644 index 000000000000..7ac9efbe67e6 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/MustBeEmptyUnclesValidator.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Consensus.Validators; +using Nethermind.Core; + +namespace Nethermind.Xdc; + +public class MustBeEmptyUnclesValidator : IUnclesValidator +{ + public bool Validate(BlockHeader header, BlockHeader[] uncles) + { + return uncles.Length == 0; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/Nethermind.Xdc.csproj b/src/Nethermind/Nethermind.Xdc/Nethermind.Xdc.csproj index b5a0f49d24bc..7f576e9d23f4 100644 --- a/src/Nethermind/Nethermind.Xdc/Nethermind.Xdc.csproj +++ b/src/Nethermind/Nethermind.Xdc/Nethermind.Xdc.csproj @@ -4,10 +4,19 @@ annotations + + + + + + + + + diff --git a/src/Nethermind/Nethermind.Xdc/NewRoundEventArgs.cs b/src/Nethermind/Nethermind.Xdc/NewRoundEventArgs.cs new file mode 100644 index 000000000000..787260cc0773 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/NewRoundEventArgs.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Xdc; + +public class NewRoundEventArgs(ulong round, int previousRoundTimeouts) : EventArgs +{ + public ulong NewRound { get; } = round; + public int PreviousRoundTimeouts { get; } = previousRoundTimeouts; +} diff --git a/src/Nethermind/Nethermind.Xdc/PenaltyHandler.cs b/src/Nethermind/Nethermind.Xdc/PenaltyHandler.cs new file mode 100644 index 000000000000..b8f2c2f4f64b --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/PenaltyHandler.cs @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Crypto; + +namespace Nethermind.Xdc; + +internal class PenaltyHandler : IPenaltyHandler +{ + public Address[] HandlePenalties(long number, Hash256 currentHash, Address[] candidates) + { + //Mock implementation for now + return []; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/QuorumCertificateManager.cs b/src/Nethermind/Nethermind.Xdc/QuorumCertificateManager.cs new file mode 100644 index 000000000000..16ce9cdf913c --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/QuorumCertificateManager.cs @@ -0,0 +1,242 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Crypto; +using Nethermind.Logging; +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Types; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; + +namespace Nethermind.Xdc; + +internal class QuorumCertificateManager : IQuorumCertificateManager +{ + public QuorumCertificateManager( + IXdcConsensusContext context, + IBlockTree blockTree, + ISpecProvider xdcConfig, + IEpochSwitchManager epochSwitchManager, + ILogManager logManager) + { + _context = context; + _blockTree = blockTree; + _specProvider = xdcConfig; + _epochSwitchManager = epochSwitchManager; + _logger = logManager.GetClassLogger(); + } + + private IXdcConsensusContext _context { get; } + private readonly IBlockTree _blockTree; + private IEpochSwitchManager _epochSwitchManager { get; } + + private ILogger _logger; + + private ISpecProvider _specProvider { get; } + private readonly EthereumEcdsa _ethereumEcdsa = new EthereumEcdsa(0); + private readonly static VoteDecoder _voteDecoder = new(); + + public QuorumCertificate HighestKnownCertificate => _context.HighestQC; + public QuorumCertificate LockCertificate => _context.LockQC; + + public void CommitCertificate(QuorumCertificate qc) + { + if (qc.ProposedBlockInfo.Round > _context.HighestQC.ProposedBlockInfo.Round) + { + _context.HighestQC = qc; + } + + var proposedBlockHeader = (XdcBlockHeader)_blockTree.FindHeader(qc.ProposedBlockInfo.Hash); + if (proposedBlockHeader is null) + throw new InvalidBlockException(proposedBlockHeader, "Proposed block header not found in chain"); + + IXdcReleaseSpec spec = _specProvider.GetXdcSpec(proposedBlockHeader, _context.CurrentRound); + + //Can only look for a QC in proposed block after the switch block + if (proposedBlockHeader.Number > spec.SwitchBlock) + { + QuorumCertificate? parentQc = proposedBlockHeader.ExtraConsensusData?.QuorumCert; + if (parentQc is null) + throw new BlockchainException("QC is targeting a block without required consensus data."); + + if (_context.LockQC is null || parentQc.ProposedBlockInfo.Round > _context.LockQC.ProposedBlockInfo.Round) + { + //Parent QC is now our lock + _context.LockQC = parentQc; + } + + if (!CommitBlock(_blockTree, proposedBlockHeader, proposedBlockHeader.ExtraConsensusData.BlockRound, qc, out string error)) + { + if (_logger.IsWarn) _logger.Warn($"Could not commit block ({proposedBlockHeader.Hash}). {error}"); + } + + } + + if (qc.ProposedBlockInfo.Round >= _context.CurrentRound) + { + _context.SetNewRound(qc.ProposedBlockInfo.Round + 1); + } + } + + private bool CommitBlock(IBlockTree chain, XdcBlockHeader proposedBlockHeader, ulong proposedRound, QuorumCertificate proposedQuorumCert, [NotNullWhen(false)] out string? error) + { + IXdcReleaseSpec spec = _specProvider.GetXdcSpec(proposedBlockHeader); + //Can only commit a QC if the proposed block is at least 2 blocks after the switch block, since we want to check grandparent of proposed QC + + if ((proposedBlockHeader.Number - 2) <= spec.SwitchBlock) + { + error = $"Proposed block ({proposedBlockHeader.Number}) is too close or before genesis block ({spec.SwitchBlock})"; + return false; + } + + XdcBlockHeader parentHeader = (XdcBlockHeader)_blockTree.FindHeader(proposedBlockHeader.ParentHash); + + if (parentHeader.ExtraConsensusData is null) + { + error = $"Block {parentHeader.ToString(BlockHeader.Format.FullHashAndNumber)} does not have required consensus data! Chain might be corrupt!"; + return false; + } + + if (proposedRound - 1 != parentHeader.ExtraConsensusData.BlockRound) + { + error = $"QC round is not continuous from parent QC round."; + return false; + } + + XdcBlockHeader grandParentHeader = (XdcBlockHeader)_blockTree.FindHeader(parentHeader.ParentHash); + + if (grandParentHeader.ExtraConsensusData is null) + { + + error = $"QC grand parent ({grandParentHeader.ToString(BlockHeader.Format.FullHashAndNumber)}) does not have a QC."; + return false; + } + + if (proposedRound - 2 != grandParentHeader.ExtraConsensusData.BlockRound) + { + error = $"QC round is not continuous from grand parent QC round."; + return false; + } + + if (_context.HighestCommitBlock is not null && (_context.HighestCommitBlock.Round >= parentHeader.ExtraConsensusData.BlockRound || _context.HighestCommitBlock.BlockNumber > grandParentHeader.Number)) + { + error = $"Committed block ({_context.HighestCommitBlock.Hash}) has higher round or block number."; + return false; + } + + _context.HighestCommitBlock = new BlockRoundInfo(grandParentHeader.Hash, parentHeader.ExtraConsensusData.BlockRound, grandParentHeader.Number); + + //Mark grand parent as finalized + _blockTree.ForkChoiceUpdated(grandParentHeader.Hash, grandParentHeader.Hash); + error = null; + return true; + } + + public bool VerifyCertificate(QuorumCertificate qc, XdcBlockHeader certificateTarget, out string error) + { + if (qc is null) + throw new ArgumentNullException(nameof(qc)); + if (certificateTarget is null) + throw new ArgumentNullException(nameof(certificateTarget)); + if (qc.Signatures is null) + throw new ArgumentException("QC must contain vote signatures.", nameof(qc)); + + EpochSwitchInfo epochSwitchInfo = _epochSwitchManager.GetEpochSwitchInfo(certificateTarget) ?? _epochSwitchManager.GetEpochSwitchInfo(qc.ProposedBlockInfo.Hash); + if (epochSwitchInfo is null) + { + error = $"Epoch switch info not found for header {certificateTarget?.ToString(BlockHeader.Format.FullHashAndNumber)}"; + return false; + } + + //Possible optimize here + Signature[] uniqueSignatures = qc.Signatures.Distinct().ToArray(); + + ulong qcRound = qc.ProposedBlockInfo.Round; + IXdcReleaseSpec spec = _specProvider.GetXdcSpec(certificateTarget, qcRound); + double certThreshold = spec.CertThreshold; + double required = Math.Ceiling(epochSwitchInfo.Masternodes.Length * certThreshold); + if ((qcRound > 0) && (uniqueSignatures.Length < required)) + { + error = $"Number of votes ({uniqueSignatures.Length}/{epochSwitchInfo.Masternodes.Length}) does not meet threshold of {certThreshold}"; + return false; + } + + ValueHash256 voteHash = VoteHash(qc.ProposedBlockInfo, qc.GapNumber); + bool allValid = true; + Parallel.ForEach(uniqueSignatures, (s, state) => + { + Address signer = _ethereumEcdsa.RecoverAddress(s, voteHash); + if (!epochSwitchInfo.Masternodes.Contains(signer)) + { + allValid = false; + state.Stop(); + } + }); + + if (!allValid) + { + error = $"Quorum certificate contains one or more invalid vote signatures"; + return false; + } + + long epochSwitchNumber = epochSwitchInfo.EpochSwitchBlockInfo.BlockNumber; + long gapNumber = epochSwitchNumber - (epochSwitchNumber % (long)spec.EpochLength) - (long)spec.Gap; + + if (epochSwitchNumber - (epochSwitchNumber % (long)spec.EpochLength) < (long)spec.Gap) + gapNumber = 0; + + if (gapNumber != (long)qc.GapNumber) + { + error = $"Gap number mismatch between QC Gap {qc.GapNumber} and {gapNumber}"; + return false; + } + + if (certificateTarget.Number == spec.SwitchBlock) + { + //Do not check round info on genesis block + if (qc.ProposedBlockInfo.BlockNumber != certificateTarget.Number || qc.ProposedBlockInfo.Hash != certificateTarget.Hash) + { + error = "QC genesis block data does not match header data."; + return false; + } + } + else if (!qc.ProposedBlockInfo.ValidateBlockInfo(certificateTarget)) + { + error = "QC block data does not match header data."; + return false; + } + + error = null; + return true; + } + private ValueHash256 VoteHash(BlockRoundInfo proposedBlockInfo, ulong gapNumber) + { + KeccakRlpStream stream = new(); + _voteDecoder.Encode(stream, new Vote(proposedBlockInfo, gapNumber), RlpBehaviors.ForSealing); + return stream.GetValueHash(); + } + + public void Initialize(XdcBlockHeader current) + { + IXdcReleaseSpec spec = _specProvider.GetXdcSpec(current); + QuorumCertificate latestQc; + if (current.Number == spec.SwitchBlock) + { + latestQc = new QuorumCertificate(new BlockRoundInfo(current.Hash, 0, current.Number), Array.Empty(), + (ulong)Math.Max(0, current.Number - spec.Gap)); + _context.HighestQC = latestQc; + _context.SetNewRound(1); + } + else + { + CommitCertificate(current.ExtraConsensusData.QuorumCert); + } + } +} diff --git a/src/Nethermind/Nethermind.Xdc/README.md b/src/Nethermind/Nethermind.Xdc/README.md new file mode 100644 index 000000000000..11f468eb76ec --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/README.md @@ -0,0 +1,1213 @@ +# XDC Module Architecture Overview + +## Table of Contents +1. [Introduction](#introduction) +2. [High-Level Architecture](#high-level-architecture) +3. [Core Components](#core-components) +4. [Consensus Flow](#consensus-flow) +5. [Component Interactions](#component-interactions) +6. [Data Structures](#data-structures) +7. [Key Algorithms](#key-algorithms) + +--- + +## Introduction + +The Nethermind XDC module implements a Byzantine Fault Tolerant (BFT) consensus mechanism based on the HotStuff protocol. It extends Ethereum's blockchain architecture with additional consensus features including: + +- **HotStuff-based consensus** with 3-chain finalization +- **Quorum Certificates (QC)** for block validation +- **Timeout Certificates (TC)** for liveness +- **Epoch-based validator rotation** +- **Round-robin leader selection** + +--- + +## High-Level Architecture + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ XDC CONSENSUS LAYER │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌───────────────┐ ┌──────────────────┐ │ +│ │ XdcHotStuff │────────▶│ XdcBlockProducer │ │ +│ │ (Orchestrator)│ └──────────────────┘ │ +│ └───────┬───────┘ │ │ +│ │ │ │ +│ │ ▼ │ +│ │ ┌──────────────────┐ │ +│ │ │ XdcSealer │ │ +│ │ └──────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌───────────────────────────────────────────┐ │ +│ │ XdcConsensusContext (State) │ │ +│ ├───────────────────────────────────────────┤ │ +│ │ - CurrentRound │ │ +│ │ - HighestQC / LockQC │ │ +│ │ - HighestTC │ │ +│ │ - HighestCommitBlock │ │ +│ └───────────────┬───────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────────────────────────┐ │ +│ │ Consensus Managers │ │ +│ ├──────────────────────────────────────────┤ │ +│ │ • QuorumCertificateManager │ │ +│ │ • VotesManager │ │ +│ │ • TimeoutCertificateManager │ │ +│ │ • EpochSwitchManager │ │ +│ │ • SnapshotManager │ │ +│ └──────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ BLOCKCHAIN LAYER │ +├─────────────────────────────────────────────────────────────────────┤ +│ XdcBlockTree │ XdcHeaderStore │ XdcBlockStore │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Core Components + +### 1. XdcHotStuff (Consensus Orchestrator) + +**Purpose**: Main consensus loop coordinator + +**Key Responsibilities**: +- Round management +- Leader election +- Block proposal triggering +- Vote coordination +- Timeout handling + +**Key Methods**: +```csharp +Task RunRoundChecks(CancellationToken ct) +Task BuildAndProposeBlock(...) +Task CommitCertificateAndVote(...) +Address GetLeaderAddress(...) +``` + +**State Machine**: +``` + ┌──────────────────────┐ + │ Initialize Round │ + └──────────┬───────────┘ + │ + ▼ + ┌──────────────────────┐ + │ Am I the leader? │ + └──────┬────────┬──────┘ + │ │ + Yes│ │No + │ │ + ▼ ▼ + ┌──────────┐ ┌──────────────┐ + │ Propose │ │ Wait & Vote │ + │ Block │ │ on Block │ + └──────────┘ └──────────────┘ + │ │ + └──────┬───────┘ + ▼ + ┌──────────────────────┐ + │ QC Threshold Met? │ + └──────┬───────────────┘ + │ + ▼ + ┌──────────────────────┐ + │ Advance Round │ + └──────────────────────┘ +``` + +--- + +### 2. XdcConsensusContext (State Manager) + +**Purpose**: Central state container for consensus + +**State Variables**: +```csharp +ulong CurrentRound // Current consensus round +QuorumCertificate HighestQC // Highest known QC +QuorumCertificate LockQC // Locked QC (safety) +TimeoutCertificate HighestTC // Highest timeout certificate +BlockRoundInfo HighestCommitBlock // Finalized block +int TimeoutCounter // Consecutive timeouts +DateTime RoundStarted // Round start time +``` + +**Events**: +- `NewRoundSetEvent` - Triggered when advancing to new round + +--- + +### 3. QuorumCertificateManager + +**Purpose**: QC verification and commitment + +**Key Operations**: + +``` +┌─────────────────────────────────────────┐ +│ QuorumCertificate Lifecycle │ +├─────────────────────────────────────────┤ +│ │ +│ 1. Receive QC from block header │ +│ │ │ +│ ▼ │ +│ 2. VerifyCertificate() │ +│ ├─ Check signature threshold │ +│ ├─ Verify each signature │ +│ ├─ Validate gap number │ +│ └─ Match block info │ +│ │ │ +│ ▼ │ +│ 3. CommitCertificate() │ +│ ├─ Update HighestQC │ +│ ├─ Update LockQC │ +│ ├─ Check 3-chain rule │ +│ └─ Finalize grandparent │ +│ │ │ +│ ▼ │ +│ 4. Advance Round │ +│ │ +└─────────────────────────────────────────┘ +``` + +**3-Chain Finalization Rule**: +``` +Block N-2 (Finalized) ←─ Block N-1 ←─ Block N (Current) + QC(N-2) QC(N-1) QC(N) + +Finalization requires: +- 3 consecutive blocks +- 3 consecutive rounds (no gaps) +- Valid QC chain +``` + +--- + +### 4. VotesManager + +**Purpose**: Vote collection and QC assembly + +**Vote Processing Flow**: + +``` +┌────────────────────────────────────────────────────┐ +│ Vote Processing Pipeline │ +├────────────────────────────────────────────────────┤ +│ │ +│ OnReceiveVote(vote) │ +│ │ │ +│ ├─▶ FilterVote() │ +│ │ ├─ Check round │ +│ │ ├─ Verify signature │ +│ │ └─ Check if signer in committee │ +│ │ │ +│ ├─▶ Add to XdcPool │ +│ │ │ +│ ├─▶ Check threshold │ +│ │ (votes >= masternodes * certThreshold) │ +│ │ │ +│ └─▶ OnVotePoolThresholdReached() │ +│ │ │ +│ ├─▶ Get valid signatures │ +│ ├─▶ Create QuorumCertificate │ +│ └─▶ CommitCertificate() │ +│ │ +└────────────────────────────────────────────────────┘ +``` + +**Voting Rules** (from `VerifyVotingRules`): +``` +Can vote if ALL of: +1. CurrentRound > HighestVotedRound (no double voting) +2. Block.Round == CurrentRound (right round) +3. LockQC is null OR + - Block.ParentQC.Round > LockQC.Round OR + - Block extends from LockQC ancestor +``` + +--- + +### 5. EpochSwitchManager + +**Purpose**: Manage validator set transitions at epoch boundaries + +**Epoch Structure**: +``` +Epoch N Epoch N+1 +├────────────────────┤ ├────────────────────┤ +│ │ │ │ +│ Regular Blocks │ │ Regular Blocks │ +│ (EpochLength-Gap) │ │ │ +│ │ │ │ +├────────────────────┤ │ │ +│ │ │ │ +│ Gap (Snapshot) │────▶│ New Validators │ +│ Block N*EpochLen │ │ Applied Here │ +│ -Gap │ │ │ +│ │ │ │ +└────────────────────┘ └────────────────────┘ +``` + +**Epoch Switch Detection**: +```csharp +IsEpochSwitchAtBlock(header): + - Is it the switch block? → TRUE + - parentRound < epochStartRound? → TRUE + - Otherwise → FALSE + +epochStartRound = round - (round % EpochLength) +``` + +--- + +### 6. SnapshotManager + +**Purpose**: Store and retrieve validator snapshots + +**Snapshot Data**: +```csharp +class Snapshot { + long BlockNumber // Snapshot block + Hash256 HeaderHash // Block hash + Address[] NextEpochCandidates // Validator candidates +} +``` + +**Snapshot Storage**: +``` +Block Number Snapshot Usage +───────────────────────────────────────────────────────── +0 (Genesis) → GenesisMasterNodes → Epoch 0-899 +900-Gap=850 → Candidates from block → Epoch 900-1799 +1800-Gap=1750 → Candidates from block → Epoch 1800-2699 +... +``` + +--- + +### 7. TimeoutCertificateManager + +**Purpose**: Handle timeouts and ensure liveness + +**Timeout Flow**: +``` +Node N Pool Threshold Met + │ │ │ + │ OnCountdownTimer() │ │ + ├────────────────────────▶│ │ + │ SendTimeout(round) │ │ + │ │ │ + │ │ Collect timeouts │ + │ │ for same round │ + │ │ │ + │ │ Count >= threshold? │ + │ ├─────────────────────────▶│ + │ │ │ + │ │ Create TimeoutCertificate + │ │ │ + │◀────────────────────────┴──────────────────────────┤ + │ Broadcast TC & SyncInfo │ + │ │ + │ ProcessTimeoutCertificate() │ + ├─ Update HighestTC │ + ├─ Advance to TC.Round + 1 │ + └─ Reset timeout counter │ +``` + +--- + +## Consensus Flow + +### Complete Round Lifecycle + +``` +┌──────────────────────────────────────────────────────────────┐ +│ ROUND N LIFECYCLE │ +└──────────────────────────────────────────────────────────────┘ + +Phase 1: INITIALIZATION +──────────────────────── +┌─────────────────┐ +│ SetNewRound(N) │ +│ - Reset timeout │ +│ - Clear state │ +└────────┬────────┘ + │ + ▼ + +Phase 2: LEADER SELECTION +────────────────────────── +┌──────────────────────────────┐ +│ leaderIndex = │ +│ (round % epoch) % nodeCount│ +└──────────┬───────────────────┘ + │ + ▼ + ┌──────────────┐ + │ Am I leader?│ + └──┬────────┬──┘ + │ │ + YES│ │NO + │ │ + ▼ ▼ + +Phase 3a: BLOCK PROPOSAL (Leader) +────────────────────────────────── +┌────────────────────┐ Phase 3b: VOTING (Non-leader) +│ BuildBlock() │ ──────────────────────────── +│ - With HighestQC │ ┌──────────────────────┐ +│ - Seal & Sign │ │ Receive Block │ +└─────────┬──────────┘ │ Verify: │ + │ │ - QC valid │ + ▼ │ - Round correct │ +┌────────────────────┐ │ - Voting rules OK │ +│ Broadcast Block │ └──────────┬───────────┘ +└─────────┬──────────┘ │ + │ ▼ + │ ┌──────────────────────┐ + │ │ CastVote() │ + │ │ - Sign vote │ + │ │ - Broadcast │ + │ └──────────┬───────────┘ + │ │ + └─────────────┬───────────────┘ + │ + ▼ + +Phase 4: QC AGGREGATION +──────────────────────── +┌────────────────────────────────┐ +│ Collect Votes in Pool │ +│ Wait for threshold: │ +│ votes >= nodes * certThreshold│ +└──────────────┬─────────────────┘ + │ + ▼ +┌────────────────────────────────┐ +│ Create QuorumCertificate │ +│ - Aggregate signatures │ +│ - Verify all valid │ +└──────────────┬─────────────────┘ + │ + ▼ + +Phase 5: QC COMMITMENT +─────────────────────── +┌────────────────────────────────┐ +│ CommitCertificate() │ +│ 1. Update HighestQC │ +│ 2. Check 3-chain rule │ +│ 3. Finalize grandparent? │ +│ 4. Update LockQC │ +└──────────────┬─────────────────┘ + │ + ▼ +┌────────────────────────────────┐ +│ SetNewRound(N+1) │ +└────────────────────────────────┘ + +Alternative: TIMEOUT PATH +───────────────────────── +If no QC formed: + ┌─────────────────┐ + │ Timer expires │ + └────────┬────────┘ + │ + ▼ + ┌─────────────────┐ + │ SendTimeout() │ + └────────┬────────┘ + │ + ▼ + ┌─────────────────┐ + │ Collect TCs │ + │ Form TC │ + └────────┬────────┘ + │ + ▼ + ┌─────────────────┐ + │ SetNewRound(N+1)│ + └─────────────────┘ +``` + +--- + +## Component Interactions + +### Block Proposal & Validation Pipeline + +``` +┌──────────────┐ +│ XdcHotStuff │ Orchestrator triggers +└──────┬───────┘ + │ + │ 1. BuildAndProposeBlock() + ▼ +┌──────────────────┐ +│ XdcBlockProducer │ Creates block structure +└──────┬───────────┘ + │ + │ 2. PrepareBlockHeader() + │ ├─ Add QuorumCert + │ ├─ Set round number + │ └─ Set validators (if epoch switch) + ▼ +┌──────────────────┐ +│ XdcSealer │ Signs block +└──────┬───────────┘ + │ + │ 3. SealBlock() + │ └─ Create ECDSA signature + ▼ +┌──────────────────┐ +│ XdcBlockTree │ Adds to chain +└──────┬───────────┘ + │ + │ 4. SuggestBlock() + │ └─ Validate against finalized block + ▼ +┌──────────────────┐ +│XdcHeaderValidator│ Validates header +└──────┬───────────┘ + │ + │ 5. Validate() + │ ├─ Check QC + │ ├─ Verify seal + │ └─ Check consensus rules + ▼ +┌──────────────────┐ +│ XdcSealValidator │ Validates seal & params +└──────┬───────────┘ + │ + │ 6. ValidateParams() + │ ├─ Verify leader + │ ├─ Check round sequence + │ └─ Validate epoch data + ▼ +┌──────────────────┐ +│ XdcBlockProcessor│ Executes transactions +└──────┬───────────┘ + │ + │ 7. Process() + │ └─ Apply state changes + ▼ +┌──────────────────┐ +│ Block Committed │ +└──────────────────┘ +``` + +--- + +### Vote Collection & QC Formation + +``` +Validator Nodes VotesManager QCManager +──────────────── ───────────── ────────── +Node 1 Node 2 Node 3 + │ │ │ + │ Receives Block + │ │ │ + ├───────┴───────┤ + │ CastVote() │ + ├──────────────▶│ + │ │ Add to XdcPool + │ ├─────────────────┐ + │ │ │ + │ │ votes[round,hash].Add(vote) + │ │ │ + │ │◀────────────────┘ + │ │ + │ │ Check threshold + │ ├─────────────────┐ + │ │ │ + │ │ if count >= threshold + │ │ │ + │ │ ▼ + │ │ OnVotePoolThresholdReached() + │ │ │ + │ │ ├─ GetValidSignatures() + │ │ ├─ Create QC + │ │ │ + │ │ ▼ + │ ├────────────────▶│ + │ │ CommitCertificate(qc) + │ │ │ + │ │ ├─ Verify QC + │ │ ├─ Update HighestQC + │ │ ├─ Check 3-chain + │ │ ├─ Finalize blocks + │ │ └─ Advance round + │ │ │ + │ │◀────────────────┤ + │ │ Round advanced + │◀──────────────┤ + │ NewRoundEvent │ +``` + +--- + +## Data Structures + +### 1. XdcBlockHeader + +Extended Ethereum header with consensus fields: + +```csharp +XdcBlockHeader { + // Standard Ethereum fields + Hash256 ParentHash + Address Beneficiary + Hash256 StateRoot + long Number + ulong Timestamp + ... + + // XDC-specific fields + byte[] Validators // Validator addresses (epoch switch blocks) + byte[] Validator // Block signer signature + byte[] Penalties // Penalized validators + + // Consensus data (in ExtraData) + ExtraFieldsV2 { + ulong BlockRound // Consensus round + QuorumCertificate QuorumCert // Parent block's QC + } +} +``` + +--- + +### 2. QuorumCertificate + +Proof of 2f+1 votes for a block: + +```csharp +QuorumCertificate { + BlockRoundInfo ProposedBlockInfo { + Hash256 Hash // Block hash + ulong Round // Block round + long BlockNumber // Block height + } + Signature[] Signatures // Aggregated signatures + ulong GapNumber // Snapshot gap block number +} +``` + +**QC Formation**: +``` +Votes (2f+1) ──────▶ Aggregate Signatures ──────▶ QC + Vote₁ │ + Vote₂ Sign(Hash(BlockInfo)) │ + Vote₃ ↓ │ + ... [Sig₁, Sig₂, ...] │ + Voteₙ │ + │ + QC included in ────────────┘ + next block's header +``` + +--- + +### 3. Vote + +Individual validator vote: + +```csharp +Vote { + BlockRoundInfo ProposedBlockInfo // Block being voted on + ulong GapNumber // Snapshot reference + Signature Signature // Validator's signature + Address Signer // Validator address (recovered) +} +``` + +**Vote Hash Calculation**: +``` +VoteHash = Keccak256( + RLP([ + BlockInfo{hash, round, number}, + GapNumber + ]) +) +``` + +--- + +### 4. TimeoutCertificate + +Proof of 2f+1 timeout votes: + +```csharp +TimeoutCertificate { + ulong Round // Timed-out round + Signature[] Signatures // Timeout signatures + ulong GapNumber // Snapshot reference +} +``` + +--- + +### 5. EpochSwitchInfo + +Validator set for an epoch: + +```csharp +EpochSwitchInfo { + Address[] Masternodes // Active validators + Address[] StandbyNodes // Standby validators + Address[] Penalties // Penalized validators + BlockRoundInfo EpochSwitchBlockInfo // Epoch start block + BlockRoundInfo EpochSwitchParentBlockInfo // Previous epoch +} +``` + +--- + +## Key Algorithms + +### 1. Leader Selection + +Round-robin rotation within epoch: + +``` +GetLeaderAddress(round, header): + 1. Get current masternodes for round + 2. If epoch switch at round: + Calculate new masternodes + 3. leaderIndex = (round % EpochLength) % masternodes.Length + 4. Return masternodes[leaderIndex] +``` + +**Example** (EpochLength=900, 5 validators): +``` +Round Leader Index Leader +──────────────────────────────── +0 0 % 5 = 0 Node 0 +1 1 % 5 = 1 Node 1 +2 2 % 5 = 2 Node 2 +... +5 5 % 5 = 0 Node 0 +900 0 % 5 = 0 Node 0 (new epoch) +``` + +--- + +### 2. 3-Chain Finalization Rule + +Commit grandparent when three consecutive rounds exist: + +``` +CommitBlock(proposedBlock, proposedRound, proposedQC): + 1. parent = proposedBlock.parent + 2. grandparent = parent.parent + + 3. Check conditions: + ✓ proposedRound - 1 == parent.round + ✓ proposedRound - 2 == grandparent.round + ✓ proposedRound > grandparent.round + 1 + + 4. If all pass: + HighestCommitBlock = grandparent + Finalize(grandparent) +``` + +**Visual**: +``` +Round N-2 Round N-1 Round N + │ │ │ + ▼ ▼ ▼ +┌────────┐ ┌────────┐ ┌────────┐ +│Block B │◀───────│Block C │◀───────│Block D │ +│QC(A) │ │QC(B) │ │QC(C) │ +└────────┘ └────────┘ └────────┘ + ▲ + │ + └─── FINALIZED when Block D commits +``` + +--- + +### 3. Voting Safety Rule + +Prevent conflicting votes: + +``` +VerifyVotingRules(block, round, parentQC): + 1. CurrentRound > HighestVotedRound? ✓ No double voting + 2. block.round == CurrentRound? ✓ Right round + 3. LockQC safety: + IF LockQC exists: + - parentQC.round > LockQC.round OR + - block extends LockQC ancestor + ELSE: + - Always safe +``` + +**Lock-based Safety**: +``` +Scenario: Node locked on Block A (round 10) + +Can vote for Block B (round 15)? + ├─ B's parent QC.round > 10 ──▶ YES ✓ + └─ B extends from A ──▶ YES ✓ + +Can vote for Block C (round 12, different fork)? + ├─ C's parent QC.round > 10 ──▶ YES + └─ C extends from A? ──▶ NO ✗ REJECT +``` + +--- + +### 4. Epoch Transition + +Calculate next validator set: + +``` +CalculateNextEpochMasternodes(blockNumber, parentHash): + 1. Load previous snapshot (gap block) + 2. Get candidates from snapshot + 3. Calculate penalties (forensics) + 4. masterodes = candidates - penalties + 5. Enforce maximum: Take(MaxMasternodes) + 6. Return (masternodes, penalties) +``` + +**Timeline**: +``` +Block Number Action +───────────────────────────────────────── +0-849 Normal blocks +850 (Gap) Store snapshot with candidates +851-899 Normal blocks +900 (Epoch switch) Apply new validators from snapshot +901-1749 Use new validator set +``` + +--- + +## Security Mechanisms + +### 1. Byzantine Fault Tolerance + +``` +Threshold Requirements: +───────────────────────── +Total Validators: N +Byzantine Tolerance: f +Honest Majority: N ≥ 3f + 1 + +Quorum Certificate: ⌈N * certThreshold⌉ signatures +Default: certThreshold = 2/3 +Minimum: 2f + 1 = ⌈2N/3⌉ +``` + +### 2. Fork Prevention + +``` +XdcBlockTree.Suggest(): + 1. Check if new block builds on finalized chain + 2. Search up to MaxSearchDepth (1024 blocks) + 3. Must find finalized block in ancestry + 4. Reject blocks on dead forks +``` + +**Fork Resistance**: +``` + Finalized Block + │ + ├─────────────┐ + │ │ (rejected) + Main Chain Dead Fork + │ + (accepted) +``` + +### 3. Forensics & Slashing + +Equivocation detection: + +``` +DetectEquivocation(vote, votePool): + 1. For each existing vote in pool: + IF same round AND same signer: + IF different blocks: + ─▶ Equivocation detected! + ─▶ SendVoteEquivocationProof() +``` + +--- + +## Performance Characteristics + +### Consensus Latency + +``` +Block Production Time: +───────────────────────────────── +MinePeriod (default: 2s) Time between blocks ++ Network Propagation ~100-500ms ++ Vote Collection ~100-500ms ++ QC Formation ~10-50ms +──────────────────────────────── +Total: ~2.2 - 3.0 seconds/block +``` + +### Finality + +``` +Finalization Depth: 3 blocks (3-chain rule) + +Time to Finality: +───────────────── +3 blocks × 2s = ~6 seconds + (with optimal conditions) + +Plus: Network delays, vote collection +Practical: 8-12 seconds +``` + +### Throughput + +``` +Transactions per Block: ~2000-5000 (based on gas limit) +Gas Limit: 84,000,000 +Block Time: 2 seconds +──────────────────────────────────── +TPS (theoretical): 1000 - 2500 tx/s +TPS (practical): 500 - 1000 tx/s +``` + +--- + +## Configuration Parameters + +### XdcReleaseSpec + +```csharp +EpochLength: 900 // Blocks per epoch +Gap: 450 // Snapshot before epoch end +SwitchBlock: // V2 activation block +MaxMasternodes: 108 // Maximum validators +CertThreshold: 0.67 // 2/3 quorum +TimeoutPeriod: 4000ms // Round timeout +MinePeriod: 2000ms // Minimum block time +TimeoutSyncThreshold: 3 // SyncInfo after N timeouts +``` + +### V2 Dynamic Configuration + +Allows runtime parameter adjustments: + +```csharp +V2ConfigParams[] { + { + SwitchRound: 0, + MaxMasternodes: 108, + CertThreshold: 0.67, + TimeoutPeriod: 4000, + MinePeriod: 2000 + }, + { + SwitchRound: 1000000, // Future upgrade + MaxMasternodes: 150, + CertThreshold: 0.70, + ... + } +} +``` + +--- + +## Module Integration + +### Autofac Dependency Injection + +```csharp +XdcModule registrations: +───────────────────────────────────── +ISpecProvider → XdcChainSpecBasedSpecProvider +IBlockTree → XdcBlockTree +IHeaderStore → XdcHeaderStore +IBlockStore → XdcBlockStore +ISealer → XdcSealer +IHeaderValidator → XdcHeaderValidator +ISealValidator → XdcSealValidator +IVotesManager → VotesManager +IQuorumCertificateManager → QuorumCertificateManager +IEpochSwitchManager → EpochSwitchManager +ISnapshotManager → SnapshotManager +IXdcConsensusContext → XdcConsensusContext +... +``` + +--- + +## Critical Paths + +### 1. Happy Path (Normal Block Production) + +``` +Time Component Action +────────────────────────────────────────────────── +T+0s XdcHotStuff Timer triggers + └─▶ IsMyTurn? Check if leader +T+0.1s XdcBlockProducer Build block + ├─▶ PrepareHeader Add QC, set round + └─▶ ExecuteTxs Process transactions +T+0.5s XdcSealer Sign block +T+0.6s XdcBlockTree Suggest block +T+0.7s Validator Nodes Receive block + └─▶ Validate Check QC, seal +T+0.9s VotesManager Cast votes +T+1.5s VotesManager Threshold reached + └─▶ Create QC Aggregate signatures +T+1.6s QCManager Commit QC + ├─▶ Update HighestQC + ├─▶ Check 3-chain + └─▶ Finalize grandparent +T+1.7s XdcConsensusContext SetNewRound(N+1) +``` + +### 2. Timeout Path (No Block Received) + +``` +Time Component Action +────────────────────────────────────────────────── +T+0s Round N starts +T+4s TimeoutTimer Expires + └─▶ OnCountdownTimer +T+4.1s TCManager SendTimeout + └─▶ Sign timeout +T+4.5s TCManager Collect TCs +T+5s TCManager Threshold reached + └─▶ Create TC +T+5.1s TCManager ProcessTC + └─▶ Update HighestTC +T+5.2s XdcConsensusContext SetNewRound(N+1) +``` + +--- + +## Testing Considerations + +### Unit Test Coverage Areas + +1. **Consensus Logic**: + - Leader selection algorithm + - Voting rule verification + - QC verification + - 3-chain finalization + +2. **State Transitions**: + - Round advancement + - Lock updates + - Finalization triggers + +3. **Edge Cases**: + - Epoch boundaries + - Network partitions + - Byzantine behavior + +### Integration Test Scenarios + +``` +Test: 3-Chain Finalization +────────────────────────── +Setup: 5 validators, normal operation +Steps: + 1. Produce 3 consecutive blocks + 2. Each block gets 2f+1 votes + 3. Form QC for each + 4. Verify block N-2 finalized + +Test: Epoch Transition +────────────────────── +Setup: 5 validators, epoch length = 10 +Steps: + 1. Produce blocks 0-9 + 2. At block 10-Gap(5), store snapshot + 3. At block 10, switch validators + 4. Verify new committee active + +Test: Timeout & Recovery +──────────────────────── +Setup: 5 validators, leader crashes +Steps: + 1. Leader fails to propose + 2. Nodes timeout after 4s + 3. Form TC with 2f+1 timeouts + 4. Advance round + 5. New leader proposes +``` + +--- + +## Common Issues & Solutions + +### Issue 1: Fork Detection Failures + +**Problem**: Blocks built on non-finalized forks accepted + +**Solution**: `XdcBlockTree.Suggest()` checks ancestry up to finalized block + +```csharp +protected override AddBlockResult Suggest(Block block, ...) { + if (finalizedBlock.BlockNumber >= header.Number) + return InvalidBlock; + + // Search ancestry up to MaxSearchDepth + for (long i = header.Number; i >= finalized; i--) { + if (finalizedHash == current.ParentHash) + return base.Suggest(...); // Valid + } + return InvalidBlock; // On dead fork +} +``` + +--- + +### Issue 2: Double Voting + +**Problem**: Node votes twice in same round + +**Solution**: `VerifyVotingRules` tracks `_highestVotedRound` + +```csharp +public bool VerifyVotingRules(...) { + if ((long)_ctx.CurrentRound <= _highestVotedRound) + return false; // Already voted + + // ... other checks + + _highestVotedRound = votingRound; // Update after voting +} +``` + +--- + +### Issue 3: Epoch Snapshot Mismatch + +**Problem**: Validators don't match expected set + +**Solution**: Snapshot stored at Gap block before epoch end + +``` +Block 850 (Gap): Store snapshot with candidates +Block 900 (Switch): Load snapshot, calculate masternodes + masterodes = snapshot.candidates - penalties +``` + +--- + +## Monitoring & Observability + +### Key Metrics + +``` +Consensus Health: +───────────────── +- Current Round Number +- Time Since Last Block +- Finalized Block Height +- QC Aggregation Time +- Vote Pool Size +- Timeout Counter +- Active Validator Count + +Performance: +──────────── +- Block Production Rate +- Average Block Time +- Finalization Lag +- Vote Collection Latency +- Network Message Rate +``` + +--- + +## References + +### Academic Papers + +1. **HotStuff: BFT Consensus with Linearity and Responsiveness** + - Yin et al., 2019 + - PODC '19 + +2. **Practical Byzantine Fault Tolerance** + - Castro & Liskov, 1999 + - OSDI '99 + +### XDC Documentation + +- XDPoS 2.0 White Paper +- XDC Network GitHub +- Nethermind Documentation + +--- + +## Appendix: Component Checklist + +### Essential Components ✓ + +- [x] XdcHotStuff - Consensus orchestrator +- [x] XdcConsensusContext - State management +- [x] QuorumCertificateManager - QC handling +- [x] VotesManager - Vote aggregation +- [x] TimeoutCertificateManager - Timeout handling +- [x] EpochSwitchManager - Validator rotation +- [x] SnapshotManager - Validator snapshots +- [x] XdcBlockProducer - Block creation +- [x] XdcSealer - Block signing +- [x] XdcBlockTree - Chain management +- [x] XdcHeaderValidator - Header validation +- [x] XdcSealValidator - Seal validation + +--- + +## Quick Reference + +### Key Interfaces + +```csharp +IBlockProducerRunner // Main consensus loop +IQuorumCertificateManager // QC operations +IVotesManager // Vote handling +IEpochSwitchManager // Epoch logic +ISnapshotManager // Validator snapshots +ITimeoutCertificateManager // Timeout handling +IXdcConsensusContext // Consensus state +``` + +### Key Classes + +```csharp +XdcHotStuff // Main orchestrator +XdcConsensusContext // State container +QuorumCertificateManager // QC logic +VotesManager // Vote aggregation +EpochSwitchManager // Epoch management +SnapshotManager // Snapshot storage +``` + +### Key Types + +```csharp +XdcBlockHeader // Extended header +QuorumCertificate // Aggregated votes +Vote // Individual vote +TimeoutCertificate // Timeout proof +EpochSwitchInfo // Validator set +Snapshot // Validator candidates +``` + +--- diff --git a/src/Nethermind/Nethermind.Xdc/RLP/BaseSnapshotDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/BaseSnapshotDecoder.cs new file mode 100644 index 000000000000..c682d7127150 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/RLP/BaseSnapshotDecoder.cs @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.Types; + +namespace Nethermind.Xdc.RLP; + +internal abstract class BaseSnapshotDecoder : RlpValueDecoder where T : Snapshot +{ + protected TResult DecodeBase(ref Rlp.ValueDecoderContext decoderContext, Func createSnapshot, RlpBehaviors rlpBehaviors = RlpBehaviors.None) where TResult : Snapshot + { + if (decoderContext.IsNextItemNull()) + return null; + + decoderContext.ReadSequenceLength(); + long number = decoderContext.DecodeLong(); + Hash256 hash256 = decoderContext.DecodeKeccak(); + Address[] candidates = DecodeAddressArray(ref decoderContext); + return createSnapshot(number, hash256, candidates); + } + public static Address[] DecodeAddressArray(ref Rlp.ValueDecoderContext decoderContext) + { + if (decoderContext.IsNextItemNull()) + { + _ = decoderContext.ReadByte(); + return []; + } + + int length = decoderContext.ReadSequenceLength(); + + Address[] addresses = new Address[length / Rlp.LengthOfAddressRlp]; + + int index = 0; + while (length > 0) + { + addresses[index++] = decoderContext.DecodeAddress(); + length -= Rlp.LengthOfAddressRlp; + } + + return addresses; + } + + public Rlp Encode(T item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + return Rlp.OfEmptySequence; + + RlpStream rlpStream = new(GetLength(item, rlpBehaviors)); + Encode(rlpStream, item, rlpBehaviors); + return new Rlp(rlpStream.Data.ToArray()); + } + + protected TResult DecodeBase(RlpStream rlpStream, Func createSnapshot, RlpBehaviors rlpBehaviors = RlpBehaviors.None) where TResult : Snapshot + { + if (rlpStream.IsNextItemNull()) + return null; + + rlpStream.ReadSequenceLength(); + + long number = rlpStream.DecodeLong(); + Hash256 hash256 = rlpStream.DecodeKeccak(); + Address[] candidate = rlpStream.DecodeArray
(s => s.DecodeAddress()) ?? []; + + return createSnapshot(number, hash256, candidate); + } + + public override void Encode(RlpStream stream, T item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + { + stream.EncodeNullObject(); + return; + } + + stream.StartSequence(GetContentLength(item, rlpBehaviors)); + EncodeContent(stream, item, rlpBehaviors); + } + + protected virtual void EncodeContent(RlpStream stream, T item, RlpBehaviors rlpBehaviors) + { + stream.Encode(item.BlockNumber); + stream.Encode(item.HeaderHash); + + if (item.NextEpochCandidates is null) + stream.EncodeArray
([]); + else + EncodeAddressSequence(stream, item.NextEpochCandidates); + } + + protected void EncodeAddressSequence(RlpStream stream, Address[] nextEpochCandidates) + { + int length = nextEpochCandidates.Length; + stream.StartSequence(Rlp.LengthOfAddressRlp * length); + for (int i = 0; i < length; i++) + { + stream.Encode(nextEpochCandidates[i]); + } + } + + public override int GetLength(T item, RlpBehaviors rlpBehaviors) + { + return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); + } + protected virtual int GetContentLength(T item, RlpBehaviors rlpBehaviors) + { + if (item is null) + return 0; + + int length = 0; + length += Rlp.LengthOf(item.BlockNumber); + length += Rlp.LengthOf(item.HeaderHash); + length += Rlp.LengthOfSequence(Rlp.LengthOfAddressRlp * item.NextEpochCandidates?.Length ?? 0); + return length; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/RLP/BaseXdcHeaderDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/BaseXdcHeaderDecoder.cs new file mode 100644 index 000000000000..7bd9ad2437e9 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/RLP/BaseXdcHeaderDecoder.cs @@ -0,0 +1,214 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Int256; +using Nethermind.Serialization.Rlp; +using System; + +namespace Nethermind.Xdc; + +public abstract class BaseXdcHeaderDecoder : IHeaderDecoder where TH : XdcBlockHeader +{ + private const int NonceLength = 8; + + protected static bool IsForSealing(RlpBehaviors beh) + => (beh & RlpBehaviors.ForSealing) == RlpBehaviors.ForSealing; + + protected abstract TH CreateHeader( + Hash256? parentHash, + Hash256? unclesHash, + Address? beneficiary, + UInt256 difficulty, + long number, + long gasLimit, + ulong timestamp, + byte[]? extraData); + + protected abstract void DecodeHeaderSpecificFields(ref Rlp.ValueDecoderContext decoderContext, TH header, RlpBehaviors rlpBehaviors, int headerCheck); + protected abstract void DecodeHeaderSpecificFields(RlpStream rlpStream, TH header, RlpBehaviors rlpBehaviors, int headerCheck); + protected abstract void EncodeHeaderSpecificFields(RlpStream rlpStream, TH header, RlpBehaviors rlpBehaviors); + protected abstract int GetHeaderSpecificContentLength(TH header, RlpBehaviors rlpBehaviors); + + public BlockHeader? Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (decoderContext.IsNextItemNull()) + { + return null; + } + + ReadOnlySpan headerRlp = decoderContext.PeekNextItem(); + int headerSequenceLength = decoderContext.ReadSequenceLength(); + int headerCheck = decoderContext.Position + headerSequenceLength; + + // Common fields + Hash256? parentHash = decoderContext.DecodeKeccak(); + Hash256? unclesHash = decoderContext.DecodeKeccak(); + Address? beneficiary = decoderContext.DecodeAddress(); + Hash256? stateRoot = decoderContext.DecodeKeccak(); + Hash256? transactionsRoot = decoderContext.DecodeKeccak(); + Hash256? receiptsRoot = decoderContext.DecodeKeccak(); + Bloom? bloom = decoderContext.DecodeBloom(); + UInt256 difficulty = decoderContext.DecodeUInt256(); + long number = decoderContext.DecodeLong(); + long gasLimit = decoderContext.DecodeLong(); + long gasUsed = decoderContext.DecodeLong(); + ulong timestamp = decoderContext.DecodeULong(); + byte[]? extraData = decoderContext.DecodeByteArray(); + + TH header = CreateHeader( + parentHash, unclesHash, beneficiary, + difficulty, number, gasLimit, timestamp, extraData); + + header.StateRoot = stateRoot; + header.TxRoot = transactionsRoot; + header.ReceiptsRoot = receiptsRoot; + header.Bloom = bloom; + header.GasUsed = gasUsed; + header.Hash = Keccak.Compute(headerRlp); + + header.MixHash = decoderContext.DecodeKeccak(); + header.Nonce = (ulong)decoderContext.DecodeUInt256(NonceLength); + + DecodeHeaderSpecificFields(ref decoderContext, header, rlpBehaviors, headerCheck); + + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + decoderContext.Check(headerCheck); + } + + return header; + } + + public BlockHeader? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (rlpStream.IsNextItemNull()) + { + rlpStream.ReadByte(); + return null; + } + + Span headerRlp = rlpStream.PeekNextItem(); + int headerSequenceLength = rlpStream.ReadSequenceLength(); + int headerCheck = rlpStream.Position + headerSequenceLength; + + // Common fields + Hash256? parentHash = rlpStream.DecodeKeccak(); + Hash256? unclesHash = rlpStream.DecodeKeccak(); + Address? beneficiary = rlpStream.DecodeAddress(); + Hash256? stateRoot = rlpStream.DecodeKeccak(); + Hash256? transactionsRoot = rlpStream.DecodeKeccak(); + Hash256? receiptsRoot = rlpStream.DecodeKeccak(); + Bloom? bloom = rlpStream.DecodeBloom(); + UInt256 difficulty = rlpStream.DecodeUInt256(); + long number = rlpStream.DecodeLong(); + long gasLimit = rlpStream.DecodeLong(); + long gasUsed = rlpStream.DecodeLong(); + ulong timestamp = rlpStream.DecodeULong(); + byte[]? extraData = rlpStream.DecodeByteArray(); + + TH header = CreateHeader( + parentHash, unclesHash, beneficiary, + difficulty, number, gasLimit, timestamp, extraData); + + header.StateRoot = stateRoot; + header.TxRoot = transactionsRoot; + header.ReceiptsRoot = receiptsRoot; + header.Bloom = bloom; + header.GasUsed = gasUsed; + header.Hash = Keccak.Compute(headerRlp); + + header.MixHash = rlpStream.DecodeKeccak(); + header.Nonce = (ulong)rlpStream.DecodeUInt256(NonceLength); + + DecodeHeaderSpecificFields(rlpStream, header, rlpBehaviors, headerCheck); + + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + rlpStream.Check(headerCheck); + } + + return header; + } + + public void Encode(RlpStream rlpStream, BlockHeader? header, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (header is null) + { + rlpStream.EncodeNullObject(); + return; + } + + if (header is not TH h) + throw new ArgumentException($"Must be {typeof(TH).Name}.", nameof(header)); + + rlpStream.StartSequence(GetContentLength(h, rlpBehaviors)); + + // Common fields + rlpStream.Encode(h.ParentHash); + rlpStream.Encode(h.UnclesHash); + rlpStream.Encode(h.Beneficiary); + rlpStream.Encode(h.StateRoot); + rlpStream.Encode(h.TxRoot); + rlpStream.Encode(h.ReceiptsRoot); + rlpStream.Encode(h.Bloom); + rlpStream.Encode(h.Difficulty); + rlpStream.Encode(h.Number); + rlpStream.Encode(h.GasLimit); + rlpStream.Encode(h.GasUsed); + rlpStream.Encode(h.Timestamp); + rlpStream.Encode(h.ExtraData); + rlpStream.Encode(h.MixHash); + rlpStream.Encode(h.Nonce, NonceLength); + + EncodeHeaderSpecificFields(rlpStream, h, rlpBehaviors); + } + + public Rlp Encode(BlockHeader? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + { + return Rlp.OfEmptySequence; + } + + if (item is not TH header) + throw new ArgumentException($"Must be {typeof(TH).Name}.", nameof(item)); + + RlpStream rlpStream = new(GetLength(item, rlpBehaviors)); + Encode(rlpStream, item, rlpBehaviors); + return new Rlp(rlpStream.Data.ToArray()); + } + + public int GetLength(BlockHeader? item, RlpBehaviors rlpBehaviors) + { + if (item is not TH header) + throw new ArgumentException($"Must be {typeof(TH).Name}.", nameof(item)); + + return Rlp.LengthOfSequence(GetContentLength(header, rlpBehaviors)); + } + + private int GetContentLength(TH header, RlpBehaviors rlpBehaviors) + { + int contentLength = + +Rlp.LengthOf(header.ParentHash) + + Rlp.LengthOf(header.UnclesHash) + + Rlp.LengthOf(header.Beneficiary) + + Rlp.LengthOf(header.StateRoot) + + Rlp.LengthOf(header.TxRoot) + + Rlp.LengthOf(header.ReceiptsRoot) + + Rlp.LengthOf(header.Bloom) + + Rlp.LengthOf(header.Difficulty) + + Rlp.LengthOf(header.Number) + + Rlp.LengthOf(header.GasLimit) + + Rlp.LengthOf(header.GasUsed) + + Rlp.LengthOf(header.Timestamp) + + Rlp.LengthOf(header.ExtraData) + + Rlp.LengthOf(header.MixHash) + + Rlp.LengthOfNonce(header.Nonce); + + contentLength += GetHeaderSpecificContentLength(header, rlpBehaviors); + return contentLength; + } + +} diff --git a/src/Nethermind/Nethermind.Xdc/RLP/ExtraConsensusDataDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/ExtraConsensusDataDecoder.cs index b83a6cee8f38..216b6fe59f4d 100644 --- a/src/Nethermind/Nethermind.Xdc/RLP/ExtraConsensusDataDecoder.cs +++ b/src/Nethermind/Nethermind.Xdc/RLP/ExtraConsensusDataDecoder.cs @@ -3,17 +3,13 @@ using Nethermind.Serialization.Rlp; using Nethermind.Xdc.Types; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Nethermind.Xdc.RLP; -internal class ExtraConsensusDataDecoder : IRlpValueDecoder, IRlpStreamDecoder + +internal sealed class ExtraConsensusDataDecoder : RlpValueDecoder { - private QuorumCertificateDecoder _quorumCertificateDecoder = new(); - public ExtraFieldsV2 Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + private readonly QuorumCertificateDecoder _quorumCertificateDecoder = new(); + protected override ExtraFieldsV2 DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (decoderContext.IsNextItemNull()) return null; @@ -21,14 +17,14 @@ public ExtraFieldsV2 Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehav int endPosition = decoderContext.Position + sequenceLength; ulong round = decoderContext.DecodeULong(); - QuorumCert? quorumCert = _quorumCertificateDecoder.Decode(ref decoderContext, rlpBehaviors); + QuorumCertificate? quorumCert = _quorumCertificateDecoder.Decode(ref decoderContext, rlpBehaviors); if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) decoderContext.Check(endPosition); return new ExtraFieldsV2(round, quorumCert); } - public ExtraFieldsV2 Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override ExtraFieldsV2 DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (rlpStream.IsNextItemNull()) return null; @@ -36,7 +32,7 @@ public ExtraFieldsV2 Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = Rlp int endPosition = rlpStream.Position + sequenceLength; ulong round = rlpStream.DecodeULong(); - QuorumCert? quorumCert = _quorumCertificateDecoder.Decode(rlpStream, rlpBehaviors); + QuorumCertificate? quorumCert = _quorumCertificateDecoder.Decode(rlpStream, rlpBehaviors); if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) rlpStream.Check(endPosition); return new ExtraFieldsV2(round, quorumCert); @@ -52,7 +48,7 @@ public Rlp Encode(ExtraFieldsV2 item, RlpBehaviors rlpBehaviors = RlpBehaviors.N return new Rlp(rlpStream.Data.ToArray()); } - public void Encode(RlpStream stream, ExtraFieldsV2 item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, ExtraFieldsV2 item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) { @@ -61,11 +57,11 @@ public void Encode(RlpStream stream, ExtraFieldsV2 item, RlpBehaviors rlpBehavio } stream.StartSequence(GetContentLength(item, rlpBehaviors)); - stream.Encode(item.CurrentRound); + stream.Encode(item.BlockRound); _quorumCertificateDecoder.Encode(stream, item.QuorumCert, rlpBehaviors); } - public int GetLength(ExtraFieldsV2 item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override int GetLength(ExtraFieldsV2 item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); } @@ -74,7 +70,7 @@ private int GetContentLength(ExtraFieldsV2? item, RlpBehaviors rlpBehaviors = Rl if (item is null) return 0; int length = _quorumCertificateDecoder.GetLength(item.QuorumCert, rlpBehaviors); - return Rlp.LengthOf(item.CurrentRound) + length; + return Rlp.LengthOf(item.BlockRound) + length; } } diff --git a/src/Nethermind/Nethermind.Xdc/RLP/QuorumCertificateDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/QuorumCertificateDecoder.cs index c8b3052696e5..938ae908dfb7 100644 --- a/src/Nethermind/Nethermind.Xdc/RLP/QuorumCertificateDecoder.cs +++ b/src/Nethermind/Nethermind.Xdc/RLP/QuorumCertificateDecoder.cs @@ -1,30 +1,24 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Nethermind.Core; -using Nethermind.Core.Collections; using Nethermind.Core.Crypto; -using Nethermind.Int256; using Nethermind.Serialization.Rlp; using Nethermind.Xdc.RLP; using Nethermind.Xdc.Types; -using Org.BouncyCastle.Utilities.Encoders; using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using BlockRoundInfo = Nethermind.Xdc.Types.BlockRoundInfo; namespace Nethermind.Xdc; -internal class QuorumCertificateDecoder : IRlpValueDecoder, IRlpStreamDecoder + +internal sealed class QuorumCertificateDecoder : RlpValueDecoder { - private XdcBlockInfoDecoder _blockInfoDecoder = new XdcBlockInfoDecoder(); - public QuorumCert Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + private readonly XdcBlockInfoDecoder _blockInfoDecoder = new XdcBlockInfoDecoder(); + protected override QuorumCertificate DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { - if (decoderContext.IsNextItemNull()) - return null; int sequenceLength = decoderContext.ReadSequenceLength(); + if (sequenceLength == 0) + return null; int endPosition = decoderContext.Position + sequenceLength; BlockRoundInfo? blockInfo = _blockInfoDecoder.Decode(ref decoderContext, rlpBehaviors); @@ -43,16 +37,19 @@ public QuorumCert Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehavior } ulong gap = decoderContext.DecodeULong(); - return new QuorumCert(blockInfo, signatures, gap); + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + decoderContext.Check(endPosition); + } + return new QuorumCertificate(blockInfo, signatures, gap); } - public QuorumCert Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override QuorumCertificate DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { - if (rlpStream.IsNextItemNull()) - return null; int sequenceLength = rlpStream.ReadSequenceLength(); + if (sequenceLength == 0) + return null; int endPosition = rlpStream.Position + sequenceLength; - BlockRoundInfo? blockInfo = _blockInfoDecoder.Decode(rlpStream, rlpBehaviors); byte[][]? signatureBytes = rlpStream.DecodeByteArrays(); @@ -69,10 +66,14 @@ public QuorumCert Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBeh } ulong gap = rlpStream.DecodeULong(); - return new QuorumCert(blockInfo, signatures, gap); + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + rlpStream.Check(endPosition); + } + return new QuorumCertificate(blockInfo, signatures, gap); } - public Rlp Encode(QuorumCert item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public Rlp Encode(QuorumCertificate item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) return Rlp.OfEmptySequence; @@ -83,7 +84,7 @@ public Rlp Encode(QuorumCert item, RlpBehaviors rlpBehaviors = RlpBehaviors.None return new Rlp(rlpStream.Data.ToArray()); } - public void Encode(RlpStream stream, QuorumCert item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, QuorumCertificate item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) { @@ -113,11 +114,11 @@ public void Encode(RlpStream stream, QuorumCert item, RlpBehaviors rlpBehaviors stream.Encode(item.GapNumber); } - public int GetLength(QuorumCert item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override int GetLength(QuorumCertificate item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); } - private int GetContentLength(QuorumCert? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + private int GetContentLength(QuorumCertificate? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) return 0; @@ -130,7 +131,7 @@ private int GetContentLength(QuorumCert? item, RlpBehaviors rlpBehaviors = RlpBe + sigLength; } - private static int SignaturesLength(QuorumCert item) + private static int SignaturesLength(QuorumCertificate item) { return Rlp.LengthOfSequence(Signature.Size) * (item.Signatures != null ? item.Signatures.Length : 0); } diff --git a/src/Nethermind/Nethermind.Xdc/RLP/SnapshotDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/SnapshotDecoder.cs index eafa36f6a290..55ccbd365e08 100644 --- a/src/Nethermind/Nethermind.Xdc/RLP/SnapshotDecoder.cs +++ b/src/Nethermind/Nethermind.Xdc/RLP/SnapshotDecoder.cs @@ -1,122 +1,16 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Serialization.Rlp; using Nethermind.Xdc.Types; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Nethermind.Xdc.RLP; -internal class SnapshotDecoder : IRlpStreamDecoder, IRlpValueDecoder -{ - public Snapshot Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) - { - if (decoderContext.IsNextItemNull()) - return null; - - decoderContext.ReadSequenceLength(); - - long number = decoderContext.DecodeLong(); - Hash256 hash256 = decoderContext.DecodeKeccak(); - Address[] candidates = DecodeAddressArray(ref decoderContext); - - return new Snapshot(number, hash256, candidates); - } - - public Rlp Encode(Snapshot item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) - { - if (item is null) - return Rlp.OfEmptySequence; - - RlpStream rlpStream = new(GetLength(item, rlpBehaviors)); - Encode(rlpStream, item, rlpBehaviors); - return new Rlp(rlpStream.Data.ToArray()); - } - - private Address[] DecodeAddressArray(ref Rlp.ValueDecoderContext decoderContext) - { - if (decoderContext.IsNextItemNull()) - { - _ = decoderContext.ReadByte(); - return []; - } - - int length = decoderContext.ReadSequenceLength(); - - Address[] addresses = new Address[length / Rlp.LengthOfAddressRlp]; - - int index = 0; - while (length > 0) - { - addresses[index++] = decoderContext.DecodeAddress(); - length -= Rlp.LengthOfAddressRlp; - } - - return addresses; - } - public Snapshot Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) - { - if (rlpStream.IsNextItemNull()) - return null; - - rlpStream.ReadSequenceLength(); - - long number = rlpStream.DecodeLong(); - Hash256 hash256 = rlpStream.DecodeKeccak(); - Address[] candidate = rlpStream.DecodeArray
(s => s.DecodeAddress()) ?? []; - - return new Snapshot(number, hash256, candidate); - } - - public void Encode(RlpStream stream, Snapshot item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) - { - if (item is null) - { - stream.EncodeNullObject(); - return; - } - - var contentLength = GetLength(item, rlpBehaviors); - - stream.StartSequence(contentLength); - stream.Encode(item.BlockNumber); - stream.Encode(item.HeaderHash); - - if (item.NextEpochCandidates is null) - stream.EncodeArray
([]); - else - EncodeAddressSequence(stream, item.NextEpochCandidates); - } - - private void EncodeAddressSequence(RlpStream stream, Address[] nextEpochCandidates) - { - int length = nextEpochCandidates.Length; - stream.StartSequence(Rlp.LengthOfAddressRlp * length); - for (int i = 0; i < length; i++) - { - stream.Encode(nextEpochCandidates[i]); - } - } - - public int GetLength(Snapshot item, RlpBehaviors rlpBehaviors) - { - return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); - } - private int GetContentLength(Snapshot item, RlpBehaviors rlpBehaviors) - { - if (item is null) - return 0; +internal sealed class SnapshotDecoder : BaseSnapshotDecoder +{ + protected override Snapshot DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + => DecodeBase(ref decoderContext, (number, hash, candidates) => new Snapshot(number, hash, candidates), rlpBehaviors); - int length = 0; - length += Rlp.LengthOf(item.BlockNumber); - length += Rlp.LengthOf(item.HeaderHash); - length += Rlp.LengthOfSequence(Rlp.LengthOfAddressRlp * item.NextEpochCandidates?.Length ?? 0); - return length; - } + protected override Snapshot DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + => DecodeBase(rlpStream, (number, hash, candidates) => new Snapshot(number, hash, candidates), rlpBehaviors); } diff --git a/src/Nethermind/Nethermind.Xdc/RLP/SubnetSnapshotDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/SubnetSnapshotDecoder.cs new file mode 100644 index 000000000000..1cc90405eba7 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/RLP/SubnetSnapshotDecoder.cs @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.Types; + +namespace Nethermind.Xdc.RLP; + +internal sealed class SubnetSnapshotDecoder : BaseSnapshotDecoder +{ + + protected override SubnetSnapshot DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + SubnetSnapshot subnetSnapshot = DecodeBase(ref decoderContext, (number, hash, candidates) => new SubnetSnapshot(number, hash, candidates), rlpBehaviors); + if (subnetSnapshot is null) + return null; + + subnetSnapshot.NextEpochPenalties = DecodeAddressArray(ref decoderContext); + return subnetSnapshot; + } + + protected override SubnetSnapshot DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + SubnetSnapshot subnetSnapshot = DecodeBase(rlpStream, (number, hash, candidates) => new SubnetSnapshot(number, hash, candidates), rlpBehaviors); + if (subnetSnapshot is null) + return null; + + subnetSnapshot.NextEpochPenalties = rlpStream.DecodeArray
(s => s.DecodeAddress()) ?? []; + return subnetSnapshot; + } + + protected override void EncodeContent(RlpStream stream, SubnetSnapshot item, RlpBehaviors rlpBehaviors) + { + base.EncodeContent(stream, item, rlpBehaviors); + + if (item.NextEpochPenalties is null) + stream.EncodeArray
([]); + else + EncodeAddressSequence(stream, item.NextEpochPenalties); + } + + protected override int GetContentLength(SubnetSnapshot item, RlpBehaviors rlpBehaviors) + { + if (item is null) + return 0; + int length = base.GetContentLength(item, rlpBehaviors); + length += Rlp.LengthOfSequence(Rlp.LengthOfAddressRlp * item.NextEpochPenalties?.Length ?? 0); + return length; + } + +} diff --git a/src/Nethermind/Nethermind.Xdc/RLP/SyncInfoDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/SyncInfoDecoder.cs new file mode 100644 index 000000000000..0ebaf9d39ad8 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/RLP/SyncInfoDecoder.cs @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.RLP; +using Nethermind.Xdc.Types; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Nethermind.Xdc; + +internal class SyncInfoDecoder : RlpValueDecoder +{ + private readonly QuorumCertificateDecoder _quorumCertificateDecoder = new(); + private readonly TimeoutCertificateDecoder _timeoutCertificateDecoder = new(); + + protected override SyncInfo DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (decoderContext.IsNextItemNull()) + return null; + + int sequenceLength = decoderContext.ReadSequenceLength(); + int endPosition = decoderContext.Position + sequenceLength; + + QuorumCertificate highestQuorumCert = _quorumCertificateDecoder.Decode(ref decoderContext, rlpBehaviors); + TimeoutCertificate highestTimeoutCert = _timeoutCertificateDecoder.Decode(ref decoderContext, rlpBehaviors); + + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + decoderContext.Check(endPosition); + } + + return new SyncInfo(highestQuorumCert, highestTimeoutCert); + } + + protected override SyncInfo DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (rlpStream.IsNextItemNull()) + return null; + + int sequenceLength = rlpStream.ReadSequenceLength(); + int endPosition = rlpStream.Position + sequenceLength; + + QuorumCertificate highestQuorumCert = _quorumCertificateDecoder.Decode(rlpStream, rlpBehaviors); + TimeoutCertificate highestTimeoutCert = _timeoutCertificateDecoder.Decode(rlpStream, rlpBehaviors); + + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + rlpStream.Check(endPosition); + } + + return new SyncInfo(highestQuorumCert, highestTimeoutCert); + } + + public Rlp Encode(SyncInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + return Rlp.OfEmptySequence; + + RlpStream rlpStream = new(GetLength(item, rlpBehaviors)); + Encode(rlpStream, item, rlpBehaviors); + + return new Rlp(rlpStream.Data.ToArray()); + } + + public override void Encode(RlpStream stream, SyncInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + { + stream.EncodeNullObject(); + return; + } + + stream.StartSequence(GetContentLength(item, rlpBehaviors)); + _quorumCertificateDecoder.Encode(stream, item.HighestQuorumCert, rlpBehaviors); + _timeoutCertificateDecoder.Encode(stream, item.HighestTimeoutCert, rlpBehaviors); + } + + public override int GetLength(SyncInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); + } + + public int GetContentLength(SyncInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + return 0; + + return _quorumCertificateDecoder.GetLength(item.HighestQuorumCert, rlpBehaviors) + + _timeoutCertificateDecoder.GetLength(item.HighestTimeoutCert, rlpBehaviors); + } +} diff --git a/src/Nethermind/Nethermind.Xdc/RLP/TimeoutCertificateDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/TimeoutCertificateDecoder.cs new file mode 100644 index 000000000000..cead863edab5 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/RLP/TimeoutCertificateDecoder.cs @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.Types; +using System; +using System.Linq; + +namespace Nethermind.Xdc.RLP; + +public sealed class TimeoutCertificateDecoder : RlpValueDecoder +{ + protected override TimeoutCertificate DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (decoderContext.IsNextItemNull()) + return null; + int sequenceLength = decoderContext.ReadSequenceLength(); + int endPosition = decoderContext.Position + sequenceLength; + + ulong round = decoderContext.DecodeULong(); + + byte[][]? signatureBytes = decoderContext.DecodeByteArrays(); + if (signatureBytes is not null && signatureBytes.Any(s => s.Length != 65)) + throw new RlpException("One or more invalid signature lengths in timeout certificate."); + Signature[]? signatures = null; + if (signatureBytes is not null) + { + signatures = new Signature[signatureBytes.Length]; + for (int i = 0; i < signatures.Length; i++) + { + signatures[i] = new Signature(signatureBytes[i].AsSpan(0, 64), signatureBytes[i][64]); + } + } + + ulong gapNumber = decoderContext.DecodeULong(); + + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + decoderContext.Check(endPosition); + } + + return new TimeoutCertificate(round, signatures, gapNumber); + } + + protected override TimeoutCertificate DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (rlpStream.IsNextItemNull()) + return null; + int sequenceLength = rlpStream.ReadSequenceLength(); + int endPosition = rlpStream.Position + sequenceLength; + + ulong round = rlpStream.DecodeULong(); + + byte[][]? signatureBytes = rlpStream.DecodeByteArrays(); + if (signatureBytes is not null && signatureBytes.Any(s => s.Length != 65)) + throw new RlpException("One or more invalid signature lengths in timeout certificate."); + Signature[]? signatures = null; + if (signatureBytes is not null) + { + signatures = new Signature[signatureBytes.Length]; + for (int i = 0; i < signatures.Length; i++) + { + signatures[i] = new Signature(signatureBytes[i].AsSpan(0, 64), signatureBytes[i][64]); + } + } + + ulong gapNumber = rlpStream.DecodeUlong(); + + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + rlpStream.Check(endPosition); + } + + return new TimeoutCertificate(round, signatures, gapNumber); + } + + public Rlp Encode(TimeoutCertificate item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + return Rlp.OfEmptySequence; + + RlpStream rlpStream = new(GetLength(item, rlpBehaviors)); + Encode(rlpStream, item, rlpBehaviors); + + return new Rlp(rlpStream.Data.ToArray()); + } + + public override void Encode(RlpStream stream, TimeoutCertificate item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + { + stream.EncodeNullObject(); + return; + } + + stream.StartSequence(GetContentLength(item, rlpBehaviors)); + + stream.Encode(item.Round); + + if (item.Signatures is null) + stream.EncodeNullObject(); + else + { + stream.StartSequence(SignaturesLength(item)); + foreach (Signature sig in item.Signatures) + stream.Encode(sig.BytesWithRecovery); + } + + stream.Encode(item.GapNumber); + } + + public override int GetLength(TimeoutCertificate item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); + } + private int GetContentLength(TimeoutCertificate? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + return 0; + + return Rlp.LengthOf(item.Round) + + Rlp.LengthOfSequence(SignaturesLength(item)) + + Rlp.LengthOf(item.GapNumber); + } + + private static int SignaturesLength(TimeoutCertificate item) + { + return item.Signatures is not null ? item.Signatures.Length * Rlp.LengthOfSequence(Signature.Size) : 0; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/RLP/TimeoutDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/TimeoutDecoder.cs new file mode 100644 index 000000000000..a7e7274d059d --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/RLP/TimeoutDecoder.cs @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.Types; +using System; + +namespace Nethermind.Xdc.RLP; + +public sealed class TimeoutDecoder : RlpValueDecoder +{ + protected override Timeout DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (decoderContext.IsNextItemNull()) + return null; + int sequenceLength = decoderContext.ReadSequenceLength(); + int endPosition = decoderContext.Position + sequenceLength; + + ulong round = decoderContext.DecodeULong(); + + Signature signature = null; + if ((rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing) + { + signature = decoderContext.DecodeSignature(); + } + + ulong gapNumber = decoderContext.DecodeULong(); + + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + decoderContext.Check(endPosition); + } + + return new Timeout(round, signature, gapNumber); + } + + protected override Timeout DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (rlpStream.IsNextItemNull()) + return null; + int sequenceLength = rlpStream.ReadSequenceLength(); + int endPosition = rlpStream.Position + sequenceLength; + + ulong round = rlpStream.DecodeULong(); + + Signature signature = null; + if ((rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing) + { + signature = rlpStream.DecodeSignature(); + } + + ulong gapNumber = rlpStream.DecodeUlong(); + + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + rlpStream.Check(endPosition); + + return new Timeout(round, signature, gapNumber); + } + + public Rlp Encode(Timeout item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + return Rlp.OfEmptySequence; + + RlpStream rlpStream = new(GetLength(item, rlpBehaviors)); + Encode(rlpStream, item, rlpBehaviors); + + return new Rlp(rlpStream.Data.ToArray()); + } + + public override void Encode(RlpStream stream, Timeout item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + { + stream.EncodeNullObject(); + return; + } + + stream.StartSequence(GetContentLength(item, rlpBehaviors)); + + stream.Encode(item.Round); + + // When encoding for sealing, signature is not included + if ((rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing) + { + if (item.Signature is null) + stream.EncodeNullObject(); + else + stream.Encode(item.Signature.BytesWithRecovery); + } + + stream.Encode(item.GapNumber); + } + + public override int GetLength(Timeout item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); + } + private int GetContentLength(Timeout? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + return 0; + int contentLength = Rlp.LengthOf(item.Round); + if ((rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing) + { + contentLength += Rlp.LengthOfSequence(Signature.Size); + } + contentLength += Rlp.LengthOf(item.GapNumber); + return contentLength; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/RLP/VoteDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/VoteDecoder.cs new file mode 100644 index 000000000000..74ed50f7feff --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/RLP/VoteDecoder.cs @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.RLP; +using Nethermind.Xdc.Types; +using System; + +namespace Nethermind.Xdc; + +public sealed class VoteDecoder : RlpValueDecoder +{ + private static readonly XdcBlockInfoDecoder _xdcBlockInfoDecoder = new(); + + protected override Vote DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (decoderContext.IsNextItemNull()) + return null; + int sequenceLength = decoderContext.ReadSequenceLength(); + int endPosition = decoderContext.Position + sequenceLength; + + BlockRoundInfo proposedBlockInfo = _xdcBlockInfoDecoder.Decode(ref decoderContext, rlpBehaviors); + Signature signature = null; + if ((rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing) + { + signature = decoderContext.DecodeSignature(); + } + ulong gapNumber = decoderContext.DecodeULong(); + + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + decoderContext.Check(endPosition); + } + return new Vote(proposedBlockInfo, gapNumber, signature); + } + + protected override Vote DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (rlpStream.IsNextItemNull()) + return null; + int sequenceLength = rlpStream.ReadSequenceLength(); + int endPosition = rlpStream.Position + sequenceLength; + + BlockRoundInfo proposedBlockInfo = _xdcBlockInfoDecoder.Decode(rlpStream, rlpBehaviors); + Signature signature = null; + if ((rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing) + { + signature = rlpStream.DecodeSignature(); + } + ulong gapNumber = rlpStream.DecodeULong(); + + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + rlpStream.Check(endPosition); + } + return new Vote(proposedBlockInfo, gapNumber, signature); + } + + public override void Encode(RlpStream stream, Vote item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + { + stream.EncodeNullObject(); + return; + } + stream.StartSequence(GetContentLength(item, rlpBehaviors)); + _xdcBlockInfoDecoder.Encode(stream, item.ProposedBlockInfo, rlpBehaviors); + if ((rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing) + stream.Encode(item.Signature.BytesWithRecovery); + stream.Encode(item.GapNumber); + } + + public Rlp Encode(Vote item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + return Rlp.OfEmptySequence; + + RlpStream rlpStream = new(GetLength(item, rlpBehaviors)); + Encode(rlpStream, item, rlpBehaviors); + + return new Rlp(rlpStream.Data.ToArray()); + } + + public override int GetLength(Vote item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); + } + + private int GetContentLength(Vote item, RlpBehaviors rlpBehaviors) + { + return + ((rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing ? Rlp.LengthOfSequence(Signature.Size) : 0) + + Rlp.LengthOf(item.GapNumber) + + _xdcBlockInfoDecoder.GetLength(item.ProposedBlockInfo, rlpBehaviors); + } +} diff --git a/src/Nethermind/Nethermind.Xdc/RLP/XdcBlockInfoDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/XdcBlockInfoDecoder.cs index c634fd4a2b11..43ec69c605c6 100644 --- a/src/Nethermind/Nethermind.Xdc/RLP/XdcBlockInfoDecoder.cs +++ b/src/Nethermind/Nethermind.Xdc/RLP/XdcBlockInfoDecoder.cs @@ -6,9 +6,10 @@ using Nethermind.Xdc.Types; namespace Nethermind.Xdc.RLP; -internal class XdcBlockInfoDecoder : IRlpValueDecoder, IRlpStreamDecoder + +internal sealed class XdcBlockInfoDecoder : RlpValueDecoder { - public BlockRoundInfo Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override BlockRoundInfo DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (decoderContext.IsNextItemNull()) return null; @@ -25,7 +26,7 @@ public BlockRoundInfo Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBeha } - public BlockRoundInfo Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override BlockRoundInfo DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (rlpStream.IsNextItemNull()) return null; @@ -41,7 +42,7 @@ public BlockRoundInfo Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = Rl return new BlockRoundInfo(new Hash256(hashBytes), round, number); } - public void Encode(RlpStream stream, BlockRoundInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override void Encode(RlpStream stream, BlockRoundInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) { @@ -54,7 +55,7 @@ public void Encode(RlpStream stream, BlockRoundInfo item, RlpBehaviors rlpBehavi stream.Encode(item.BlockNumber); } - public int GetLength(BlockRoundInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + public override int GetLength(BlockRoundInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); } diff --git a/src/Nethermind/Nethermind.Xdc/RLP/XdcHeaderDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/XdcHeaderDecoder.cs index f74430c91a89..46baed79b4ae 100644 --- a/src/Nethermind/Nethermind.Xdc/RLP/XdcHeaderDecoder.cs +++ b/src/Nethermind/Nethermind.Xdc/RLP/XdcHeaderDecoder.cs @@ -1,239 +1,88 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// 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; using Nethermind.Serialization.Rlp; + namespace Nethermind.Xdc; -public sealed class XdcHeaderDecoder : IHeaderDecoder -{ - private const int NonceLength = 8; - public BlockHeader? Decode(ref Rlp.ValueDecoderContext decoderContext, - RlpBehaviors rlpBehaviors = RlpBehaviors.None) +public sealed class XdcHeaderDecoder : BaseXdcHeaderDecoder +{ + protected override XdcBlockHeader CreateHeader( + Hash256? parentHash, + Hash256? unclesHash, + Address? beneficiary, + UInt256 difficulty, + long number, + long gasLimit, + ulong timestamp, + byte[]? extraData) + => new(parentHash, unclesHash, beneficiary, difficulty, number, gasLimit, timestamp, extraData); + + protected override void DecodeHeaderSpecificFields(ref Rlp.ValueDecoderContext decoderContext, XdcBlockHeader header, RlpBehaviors rlpBehaviors, int headerCheck) { - if (decoderContext.IsNextItemNull()) - { - return null; - } - ReadOnlySpan headerRlp = decoderContext.PeekNextItem(); - int headerSequenceLength = decoderContext.ReadSequenceLength(); - int headerCheck = decoderContext.Position + headerSequenceLength; - var x = new BlockDecoder(new XdcHeaderDecoder()); - Hash256? parentHash = decoderContext.DecodeKeccak(); - Hash256? unclesHash = decoderContext.DecodeKeccak(); - Address? beneficiary = decoderContext.DecodeAddress(); - Hash256? stateRoot = decoderContext.DecodeKeccak(); - Hash256? transactionsRoot = decoderContext.DecodeKeccak(); - Hash256? receiptsRoot = decoderContext.DecodeKeccak(); - Bloom? bloom = decoderContext.DecodeBloom(); - UInt256 difficulty = decoderContext.DecodeUInt256(); - long number = decoderContext.DecodeLong(); - long gasLimit = decoderContext.DecodeLong(); - long gasUsed = decoderContext.DecodeLong(); - ulong timestamp = decoderContext.DecodeULong(); - byte[]? extraData = decoderContext.DecodeByteArray(); - - XdcBlockHeader blockHeader = new( - parentHash, - unclesHash, - beneficiary, - difficulty, - number, - gasLimit, - timestamp, - extraData) - { - StateRoot = stateRoot, - TxRoot = transactionsRoot, - ReceiptsRoot = receiptsRoot, - Bloom = bloom, - GasUsed = gasUsed, - Hash = Keccak.Compute(headerRlp) - }; - - blockHeader.MixHash = decoderContext.DecodeKeccak(); - blockHeader.Nonce = (ulong)decoderContext.DecodeUInt256(NonceLength); - - blockHeader.Validators = decoderContext.DecodeByteArray(); - if ((rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing) + header.Validators = decoderContext.DecodeByteArray(); + if (!IsForSealing(rlpBehaviors)) { - blockHeader.Validator = decoderContext.DecodeByteArray(); + header.Validator = decoderContext.DecodeByteArray(); } - blockHeader.Penalties = decoderContext.DecodeByteArray(); + header.Penalties = decoderContext.DecodeByteArray(); - if (decoderContext.Position != headerCheck) blockHeader.BaseFeePerGas = decoderContext.DecodeUInt256(); - - if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + // Optional tail: BaseFeePerGas exists if there are remaining bytes + if (decoderContext.Position != headerCheck) { - decoderContext.Check(headerCheck); + header.BaseFeePerGas = decoderContext.DecodeUInt256(); } - - return blockHeader; } - public BlockHeader? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override void DecodeHeaderSpecificFields(RlpStream rlpStream, XdcBlockHeader header, RlpBehaviors rlpBehaviors, int headerCheck) { - if (rlpStream.IsNextItemNull()) - { - rlpStream.ReadByte(); - return null; - } - - Span headerRlp = rlpStream.PeekNextItem(); - int headerSequenceLength = rlpStream.ReadSequenceLength(); - int headerCheck = rlpStream.Position + headerSequenceLength; - - Hash256? parentHash = rlpStream.DecodeKeccak(); - Hash256? unclesHash = rlpStream.DecodeKeccak(); - Address? beneficiary = rlpStream.DecodeAddress(); - Hash256? stateRoot = rlpStream.DecodeKeccak(); - Hash256? transactionsRoot = rlpStream.DecodeKeccak(); - Hash256? receiptsRoot = rlpStream.DecodeKeccak(); - Bloom? bloom = rlpStream.DecodeBloom(); - UInt256 difficulty = rlpStream.DecodeUInt256(); - long number = rlpStream.DecodeLong(); - long gasLimit = rlpStream.DecodeLong(); - long gasUsed = rlpStream.DecodeLong(); - ulong timestamp = rlpStream.DecodeULong(); - byte[]? extraData = rlpStream.DecodeByteArray(); - - XdcBlockHeader blockHeader = new( - parentHash, - unclesHash, - beneficiary, - difficulty, - number, - gasLimit, - timestamp, - extraData) - { - StateRoot = stateRoot, - TxRoot = transactionsRoot, - ReceiptsRoot = receiptsRoot, - Bloom = bloom, - GasUsed = gasUsed, - Hash = Keccak.Compute(headerRlp) - }; - - blockHeader.MixHash = rlpStream.DecodeKeccak(); - blockHeader.Nonce = (ulong)rlpStream.DecodeUInt256(NonceLength); - - blockHeader.Validators = rlpStream.DecodeByteArray(); - if ((rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing) + header.Validators = rlpStream.DecodeByteArray(); + if (!IsForSealing(rlpBehaviors)) { - blockHeader.Validator = rlpStream.DecodeByteArray(); + header.Validator = rlpStream.DecodeByteArray(); } - blockHeader.Penalties = rlpStream.DecodeByteArray(); - - if (rlpStream.Position != headerCheck) blockHeader.BaseFeePerGas = rlpStream.DecodeUInt256(); + header.Penalties = rlpStream.DecodeByteArray(); - if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + if (rlpStream.Position != headerCheck) { - rlpStream.Check(headerCheck); + header.BaseFeePerGas = rlpStream.DecodeUInt256(); } - - return blockHeader; } - public void Encode(RlpStream rlpStream, BlockHeader? header, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override void EncodeHeaderSpecificFields(RlpStream rlpStream, XdcBlockHeader header, RlpBehaviors rlpBehaviors) { - if (header is null) + rlpStream.Encode(header.Validators); + if (!IsForSealing(rlpBehaviors)) { - rlpStream.EncodeNullObject(); - return; + rlpStream.Encode(header.Validator); } + rlpStream.Encode(header.Penalties); - if (header is not XdcBlockHeader h) - throw new ArgumentException("Must be XdcBlockHeader.", nameof(header)); - - bool notForSealing = (rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing; - rlpStream.StartSequence(GetContentLength(h, rlpBehaviors)); - rlpStream.Encode(h.ParentHash); - rlpStream.Encode(h.UnclesHash); - rlpStream.Encode(h.Beneficiary); - rlpStream.Encode(h.StateRoot); - rlpStream.Encode(h.TxRoot); - rlpStream.Encode(h.ReceiptsRoot); - rlpStream.Encode(h.Bloom); - rlpStream.Encode(h.Difficulty); - rlpStream.Encode(h.Number); - rlpStream.Encode(h.GasLimit); - rlpStream.Encode(h.GasUsed); - rlpStream.Encode(h.Timestamp); - rlpStream.Encode(h.ExtraData); - rlpStream.Encode(h.MixHash); - rlpStream.Encode(h.Nonce, NonceLength); - rlpStream.Encode(h.Validators); - if (notForSealing) + if (!header.BaseFeePerGas.IsZero) { - rlpStream.Encode(h.Validator); + rlpStream.Encode(header.BaseFeePerGas); } - rlpStream.Encode(h.Penalties); - - if (!h.BaseFeePerGas.IsZero) rlpStream.Encode(h.BaseFeePerGas); - } - public Rlp Encode(BlockHeader? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected override int GetHeaderSpecificContentLength(XdcBlockHeader header, RlpBehaviors rlpBehaviors) { - if (item is null) - { - return Rlp.OfEmptySequence; - } - - if (item is not XdcBlockHeader header) - throw new ArgumentException("Must be XdcBlockHeader.", nameof(header)); + int len = 0 + + Rlp.LengthOf(header.Validators) + + Rlp.LengthOf(header.Penalties); - RlpStream rlpStream = new(GetLength(header, rlpBehaviors)); - Encode(rlpStream, item, rlpBehaviors); - - return new Rlp(rlpStream.Data.ToArray()); - } - - private static int GetContentLength(XdcBlockHeader? item, RlpBehaviors rlpBehaviors) - { - if (item is null) + if (!IsForSealing(rlpBehaviors)) { - return 0; + len += Rlp.LengthOf(header.Validator); } - bool notForSealing = (rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing; - int contentLength = 0 - + Rlp.LengthOf(item.ParentHash) - + Rlp.LengthOf(item.UnclesHash) - + Rlp.LengthOf(item.Beneficiary) - + Rlp.LengthOf(item.StateRoot) - + Rlp.LengthOf(item.TxRoot) - + Rlp.LengthOf(item.ReceiptsRoot) - + Rlp.LengthOf(item.Bloom) - + Rlp.LengthOf(item.Difficulty) - + Rlp.LengthOf(item.Number) - + Rlp.LengthOf(item.GasLimit) - + Rlp.LengthOf(item.GasUsed) - + Rlp.LengthOf(item.Timestamp) - + Rlp.LengthOf(item.ExtraData) - + Rlp.LengthOf(item.MixHash) - + Rlp.LengthOfNonce(item.Nonce); - - if (notForSealing) + if (!header.BaseFeePerGas.IsZero) { - contentLength += Rlp.LengthOf(item.Validator); + len += Rlp.LengthOf(header.BaseFeePerGas); } - contentLength += Rlp.LengthOf(item.Validators); - contentLength += Rlp.LengthOf(item.Penalties); - if (!item.BaseFeePerGas.IsZero) contentLength += Rlp.LengthOf(item.BaseFeePerGas); - return contentLength; - } - - public int GetLength(BlockHeader? item, RlpBehaviors rlpBehaviors) - { - if (item is not XdcBlockHeader header) - throw new ArgumentException("Must be XdcBlockHeader.", nameof(header)); - return Rlp.LengthOfSequence(GetContentLength(header, rlpBehaviors)); + return len; } } - diff --git a/src/Nethermind/Nethermind.Xdc/RLP/XdcSubnetHeaderDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/XdcSubnetHeaderDecoder.cs new file mode 100644 index 000000000000..166dcf5df115 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/RLP/XdcSubnetHeaderDecoder.cs @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Int256; +using Nethermind.Serialization.Rlp; + +namespace Nethermind.Xdc; + +public sealed class XdcSubnetHeaderDecoder : BaseXdcHeaderDecoder +{ + protected override XdcSubnetBlockHeader CreateHeader( + Hash256? parentHash, + Hash256? unclesHash, + Address? beneficiary, + UInt256 difficulty, + long number, + long gasLimit, + ulong timestamp, + byte[]? extraData) + => new(parentHash, unclesHash, beneficiary, difficulty, number, gasLimit, timestamp, extraData); + + protected override void DecodeHeaderSpecificFields(ref Rlp.ValueDecoderContext decoderContext, XdcSubnetBlockHeader header, RlpBehaviors rlpBehaviors, int headerCheck) + { + if (!IsForSealing(rlpBehaviors)) + { + header.Validator = decoderContext.DecodeByteArray(); + } + + header.Validators = decoderContext.DecodeByteArray(); + + if (!IsForSealing(rlpBehaviors)) + { + header.NextValidators = decoderContext.DecodeByteArray(); + } + + header.Penalties = decoderContext.DecodeByteArray(); + } + + protected override void DecodeHeaderSpecificFields(RlpStream rlpStream, XdcSubnetBlockHeader header, RlpBehaviors rlpBehaviors, int headerCheck) + { + if (!IsForSealing(rlpBehaviors)) + { + header.Validator = rlpStream.DecodeByteArray(); + } + + header.Validators = rlpStream.DecodeByteArray(); + + if (!IsForSealing(rlpBehaviors)) + { + header.NextValidators = rlpStream.DecodeByteArray(); + } + + header.Penalties = rlpStream.DecodeByteArray(); + } + + protected override void EncodeHeaderSpecificFields(RlpStream rlpStream, XdcSubnetBlockHeader header, RlpBehaviors rlpBehaviors) + { + if (!IsForSealing(rlpBehaviors)) + { + rlpStream.Encode(header.Validator); + } + + rlpStream.Encode(header.Validators); + + if (!IsForSealing(rlpBehaviors)) + { + rlpStream.Encode(header.NextValidators); + } + + rlpStream.Encode(header.Penalties); + } + + protected override int GetHeaderSpecificContentLength(XdcSubnetBlockHeader header, RlpBehaviors rlpBehaviors) + { + int len = 0 + + Rlp.LengthOf(header.Validators) + + Rlp.LengthOf(header.Penalties); + + if (!IsForSealing(rlpBehaviors)) + { + len += Rlp.LengthOf(header.Validator); + len += Rlp.LengthOf(header.NextValidators); + } + + return len; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/SignTransactionManager.cs b/src/Nethermind/Nethermind.Xdc/SignTransactionManager.cs new file mode 100644 index 000000000000..c9e97ef8dbbd --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/SignTransactionManager.cs @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + + +using Nethermind.Blockchain; +using Nethermind.Consensus; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Crypto; +using Nethermind.Int256; +using Nethermind.TxPool; +using Nethermind.Xdc.Spec; +using Nethermind.Logging; +using System.Threading.Tasks; + +namespace Nethermind.Xdc; + +internal class SignTransactionManager(ISigner signer, ITxPool txPool, ILogger logger) : ISignTransactionManager +{ + public async Task SubmitTransactionSign(XdcBlockHeader header, IXdcReleaseSpec spec) + { + UInt256 nonce = txPool.GetLatestPendingNonce(signer.Address); + Transaction transaction = CreateTxSign((UInt256)header.Number, header.Hash ?? header.CalculateHash().ToHash256(), nonce, spec.BlockSignerContract, signer.Address); + + await signer.Sign(transaction); + + bool added = txPool.SubmitTx(transaction, TxHandlingOptions.PersistentBroadcast); + if (!added) + { + logger.Info("Failed to add signed transaction to the pool."); + } + } + + internal static Transaction CreateTxSign(UInt256 number, Hash256 hash, UInt256 nonce, Address blockSignersAddress, Address sender) + { + byte[] inputData = [.. XdcConstants.SignMethod, .. number.PaddedBytes(32), .. hash.Bytes.PadLeft(32)]; + + var transaction = new Transaction(); + transaction.Nonce = nonce; + transaction.To = blockSignersAddress; + transaction.Value = 0; + transaction.GasLimit = 200_000; + transaction.GasPrice = 0; + transaction.Data = inputData; + transaction.SenderAddress = sender; + + transaction.Type = TxType.Legacy; + + transaction.Hash = transaction.CalculateHash(); + + return transaction; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/SnapshotManager.cs b/src/Nethermind/Nethermind.Xdc/SnapshotManager.cs index 1b01fb1ee77a..05ff2b963925 100644 --- a/src/Nethermind/Nethermind.Xdc/SnapshotManager.cs +++ b/src/Nethermind/Nethermind.Xdc/SnapshotManager.cs @@ -4,82 +4,97 @@ using Nethermind.Blockchain; using Nethermind.Core; using Nethermind.Core.Caching; -using Nethermind.Core.Collections; 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; using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Nethermind.Xdc; + internal class SnapshotManager : ISnapshotManager { - private LruCache _snapshotCache = new(128, 128, "XDC Snapshot cache"); - private IDb _snapshotDb { get; } - private IPenaltyHandler _penaltyHandler { get; } + 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 SnapshotManager(IDb snapshotDb, IPenaltyHandler penaltyHandler) + public SnapshotManager(IDb snapshotDb, IBlockTree blockTree, IPenaltyHandler penaltyHandler, IMasternodeVotingContract votingContract, ISpecProvider specProvider) { - _snapshotDb = snapshotDb; - _penaltyHandler = penaltyHandler; + blockTree.NewHeadBlock += OnNewHeadBlock; + this.snapshotDb = snapshotDb; + this.blockTree = blockTree; + this.votingContract = votingContract; + this.specProvider = specProvider; + this.penaltyHandler = penaltyHandler; } - public Snapshot? GetSnapshot(Hash256 hash) + public Snapshot? GetSnapshotByGapNumber(long gapNumber) { - Snapshot? snapshot = _snapshotCache.Get(hash); + var gapBlockHeader = blockTree.FindHeader((long)gapNumber) as XdcBlockHeader; + + if (gapBlockHeader is null) + return null; + + Snapshot? snapshot = _snapshotCache.Get(gapBlockHeader.Hash); if (snapshot is not null) { return snapshot; } - Span key = hash.Bytes; - if (!_snapshotDb.KeyExists(key)) + Span key = gapBlockHeader.Hash.Bytes; + if (!snapshotDb.KeyExists(key)) return null; - Span value = _snapshotDb.Get(key); + Span value = snapshotDb.Get(key); if (value.IsEmpty) return null; - var decoded = _snapshotDecoder.Decode(value, RlpBehaviors.None); + Snapshot decoded = _snapshotDecoder.Decode(value); snapshot = decoded; - _snapshotCache.Set(hash, snapshot); + _snapshotCache.Set(gapBlockHeader.Hash, snapshot); return snapshot; } + public Snapshot? GetSnapshotByBlockNumber(long blockNumber, IXdcReleaseSpec spec) + { + var gapBlockNum = Math.Max(0, blockNumber - blockNumber % spec.EpochLength - spec.Gap); + return GetSnapshotByGapNumber(gapBlockNum); + } + public void StoreSnapshot(Snapshot snapshot) { Span key = snapshot.HeaderHash.Bytes; - if (_snapshotDb.KeyExists(key)) + if (snapshotDb.KeyExists(key)) return; - var rlpEncodedSnapshot = _snapshotDecoder.Encode(snapshot, RlpBehaviors.None); + Rlp rlpEncodedSnapshot = _snapshotDecoder.Encode(snapshot); - _snapshotDb.Set(key, rlpEncodedSnapshot.Bytes); + snapshotDb.Set(key, rlpEncodedSnapshot.Bytes); + _snapshotCache.Set(snapshot.HeaderHash, snapshot); } - public (Address[] Masternodes, Address[] PenalizedNodes) CalculateNextEpochMasternodes(XdcBlockHeader header, IXdcReleaseSpec spec) + public (Address[] Masternodes, Address[] PenalizedNodes) CalculateNextEpochMasternodes(long blockNumber, Hash256 parentHash, IXdcReleaseSpec spec) { int maxMasternodes = spec.MaxMasternodes; - var previousSnapshot = GetSnapshot(header.Hash); + Snapshot previousSnapshot = GetSnapshotByBlockNumber(blockNumber, spec); if (previousSnapshot is null) - throw new InvalidOperationException($"No snapshot found for header {header.Number}:{header.Hash.ToShortString()}"); + throw new InvalidOperationException($"No snapshot found for header #{blockNumber}"); - var candidates = previousSnapshot.NextEpochCandidates; + Address[] candidates = previousSnapshot.NextEpochCandidates; - if (header.Number == spec.SwitchBlock + 1) + if (blockNumber == spec.SwitchBlock + 1) { if (candidates.Length > maxMasternodes) { @@ -90,7 +105,7 @@ public void StoreSnapshot(Snapshot snapshot) return (candidates, []); } - var penalties = _penaltyHandler.HandlePenalties(header.Number, header.ParentHash, candidates); + Address[] penalties = penaltyHandler.HandlePenalties(blockNumber, parentHash, candidates); candidates = candidates .Except(penalties) // remove penalties @@ -99,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 735060caeba9..3578a29b95eb 100644 --- a/src/Nethermind/Nethermind.Xdc/Spec/XdcChainSpecBasedSpecProvider.cs +++ b/src/Nethermind/Nethermind.Xdc/Spec/XdcChainSpecBasedSpecProvider.cs @@ -1,10 +1,11 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Collections.Generic; +using Nethermind.Core; using Nethermind.Logging; using Nethermind.Specs; using Nethermind.Specs.ChainSpecStyle; +using System; namespace Nethermind.Xdc.Spec; @@ -13,19 +14,64 @@ public class XdcChainSpecBasedSpecProvider(ChainSpec chainSpec, ILogManager logManager) : ChainSpecBasedSpecProvider(chainSpec, logManager) { + private const int ExtraVanity = 32; // Fixed number of extra-data prefix bytes reserved for signer vanity + private const int ExtraSeal = 65; // Fixed number of extra-data suffix bytes reserved for signer seal + protected override ReleaseSpec CreateEmptyReleaseSpec() => new XdcReleaseSpec(); protected override ReleaseSpec CreateReleaseSpec(ChainSpec chainSpec, long releaseStartBlock, ulong? releaseStartTimestamp = null) { var releaseSpec = (XdcReleaseSpec)base.CreateReleaseSpec(chainSpec, releaseStartBlock, releaseStartTimestamp); releaseSpec.EpochLength = chainSpecEngineParameters.Epoch; + releaseSpec.Gap = chainSpecEngineParameters.Gap; 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.IsTipTrc21FeeEnabled = (chainSpecEngineParameters.TipTrc21Fee ?? 0) <= releaseStartBlock; + releaseSpec.IsBlackListingEnabled = chainSpecEngineParameters.BlackListHFNumber <= releaseStartBlock; + releaseSpec.IsTIP2019 = chainSpecEngineParameters.TIP2019Block <= releaseStartBlock; + releaseSpec.IsTIPXDCXMiner = chainSpecEngineParameters.TipXDCX <= releaseStartBlock && releaseStartBlock < chainSpecEngineParameters.TIPXDCXMinerDisable; + releaseSpec.IsDynamicGasLimitBlock = chainSpecEngineParameters.DynamicGasLimitBlock <= releaseStartBlock; + + releaseSpec.MergeSignRange = chainSpecEngineParameters.MergeSignRange; + releaseSpec.BlackListedAddresses = new(chainSpecEngineParameters.BlackListedAddresses ?? []); + + releaseSpec.RandomizeSMCBinary = chainSpecEngineParameters.RandomizeSMCBinary; + + releaseSpec.XDCXLendingFinalizedTradeAddressBinary = chainSpecEngineParameters.XDCXLendingFinalizedTradeAddressBinary; + releaseSpec.XDCXLendingAddressBinary = chainSpecEngineParameters.XDCXLendingAddressBinary; + releaseSpec.XDCXAddressBinary = chainSpecEngineParameters.XDCXAddressBinary; + releaseSpec.TradingStateAddressBinary = chainSpecEngineParameters.TradingStateAddressBinary; releaseSpec.ApplyV2Config(0); + if (releaseSpec.SwitchBlock == 0) + { + //We can parse genesis masternodes from genesis if the chain starts as V2 + releaseSpec.GenesisMasterNodes = ParseGenesisMasternodes(chainSpec); + } + else + { + releaseSpec.GenesisMasterNodes = chainSpecEngineParameters.GenesisMasternodes; + } + return releaseSpec; } + private Address[] ParseGenesisMasternodes(ChainSpec chainSpec) + { + int length = (chainSpec.Genesis.ExtraData.Length - ExtraVanity - ExtraSeal) / Address.Size; + Address[] signers = new Address[length]; + for (int i = 0; i < length; i++) + { + signers[i] = new Address(chainSpec.Genesis.ExtraData.AsSpan(ExtraVanity + i * Address.Size, Address.Size)); + } + return signers; + } + } diff --git a/src/Nethermind/Nethermind.Xdc/Spec/XdcChainSpecEngineParameters.cs b/src/Nethermind/Nethermind.Xdc/Spec/XdcChainSpecEngineParameters.cs index 8eacd9e46bcc..b9a6e5e3f27f 100644 --- a/src/Nethermind/Nethermind.Xdc/Spec/XdcChainSpecEngineParameters.cs +++ b/src/Nethermind/Nethermind.Xdc/Spec/XdcChainSpecEngineParameters.cs @@ -2,12 +2,13 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core; -using Nethermind.Int256; +using Nethermind.Specs; using Nethermind.Specs.ChainSpecStyle; using System; using System.Collections.Generic; namespace Nethermind.Xdc.Spec; + public class XdcChainSpecEngineParameters : IChainSpecEngineParameters { public string EngineName => SealEngineType; @@ -19,7 +20,17 @@ public class XdcChainSpecEngineParameters : IChainSpecEngineParameters public Address FoundationWalletAddr { get; set; } public int Reward { get; set; } public int SwitchEpoch { get; set; } - public UInt256 SwitchBlock { get; set; } + public long SwitchBlock { get; set; } + public Address[] GenesisMasternodes { get; set; } = Array.Empty
(); + + public Address BlockSignerContract { get; set; } + public Address RandomizeSMCBinary { get; set; } + public Address XDCXLendingFinalizedTradeAddressBinary { get; set; } + public Address XDCXLendingAddressBinary { get; set; } + public Address XDCXAddressBinary { get; set; } + public Address TradingStateAddressBinary { get; set; } + + public Address MasternodeVotingContract { get; set; } private List _v2Configs = new(); @@ -33,6 +44,14 @@ public List V2Configs CheckConfig(_v2Configs); } } + public long? TipTrc21Fee { get; set; } + public long TIP2019Block { get; set; } + public long MergeSignRange { get; set; } + public Address[] BlackListedAddresses { get; set; } + public long BlackListHFNumber { get; set; } + public long TipXDCX { get; set; } + public long TIPXDCXMinerDisable { get; set; } + public long? DynamicGasLimitBlock { get; set; } private static void CheckConfig(List list) { @@ -44,6 +63,17 @@ private static void CheckConfig(List list) throw new InvalidOperationException($"Duplicate config for round {list[i].SwitchRound}."); } } + + public void ApplyToReleaseSpec(ReleaseSpec spec, long startBlock, ulong? startTimestamp) + { + spec.BaseFeeCalculator = new XdcBaseFeeCalculator(); + } + + public void AddTransitions(SortedSet blockNumbers, SortedSet timestamps) + { + if (TipTrc21Fee is not null) + blockNumbers.Add(TipTrc21Fee.Value); + } } public sealed class V2ConfigParams diff --git a/src/Nethermind/Nethermind.Xdc/Spec/XdcReleaseSpec.cs b/src/Nethermind/Nethermind.Xdc/Spec/XdcReleaseSpec.cs index 875f2ef4734d..1151e81b043a 100644 --- a/src/Nethermind/Nethermind.Xdc/Spec/XdcReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Xdc/Spec/XdcReleaseSpec.cs @@ -1,17 +1,20 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Core; using Nethermind.Core.Specs; -using Nethermind.Int256; using Nethermind.Specs; using System.Collections.Generic; namespace Nethermind.Xdc.Spec; + 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 UInt256 SwitchBlock { get; set; } + public long SwitchBlock { get; set; } public int MaxMasternodes { get; set; } // v2 max masternodes public int MaxProtectorNodes { get; set; } // v2 max ProtectorNodes public int MaxObserverNodes { get; set; } // v2 max ObserverNodes @@ -26,7 +29,24 @@ public class XdcReleaseSpec : ReleaseSpec, IXdcReleaseSpec public int MinimumMinerBlockPerEpoch { get; set; } // Minimum block per epoch for a miner to not be penalized public int LimitPenaltyEpoch { get; set; } // Epochs in a row that a penalty node needs to be penalized 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; } + public List V2Configs { get; set; } = new List(); + + public Address[] GenesisMasterNodes { get; set; } + public long MergeSignRange { get; set; } + public HashSet
BlackListedAddresses { get; set; } + public Address BlockSignerContract { get; set; } + public Address RandomizeSMCBinary { get; set; } + public Address XDCXLendingFinalizedTradeAddressBinary { get; set; } + public Address XDCXLendingAddressBinary { get; set; } + public Address XDCXAddressBinary { get; set; } + public Address TradingStateAddressBinary { get; set; } + public Address FoundationWallet { get; set; } + public Address MasternodeVotingContract { get; set; } + public bool IsTipTrc21FeeEnabled { get; set; } + public bool IsBlackListingEnabled { get; set; } + public bool IsTIP2019 { get; set; } + public bool IsTIPXDCXMiner { get; set; } + public bool IsDynamicGasLimitBlock { get; set; } public void ApplyV2Config(ulong round) { @@ -51,13 +71,33 @@ internal static V2ConfigParams GetConfigAtRound(List list, ulong } return list[lo]; } + + public static XdcReleaseSpec FromReleaseSpec(IReleaseSpec spec) + { + var xdcSpec = new XdcReleaseSpec(); + + var baseType = typeof(ReleaseSpec); + var properties = baseType.GetProperties(); + foreach (var property in properties) + { + if (property.CanRead && property.CanWrite) + { + var value = property.GetValue(spec); + property.SetValue(xdcSpec, value); + } + } + + return xdcSpec; + } } public interface IXdcReleaseSpec : IReleaseSpec { public int EpochLength { get; } + public int Gap { get; } + public long Reward { get; } public int SwitchEpoch { get; set; } - public UInt256 SwitchBlock { get; set; } + public long SwitchBlock { get; set; } public int MaxMasternodes { get; set; } // v2 max masternodes public int MaxProtectorNodes { get; set; } // v2 max ProtectorNodes public int MaxObserverNodes { get; set; } // v2 max ObserverNodes @@ -73,5 +113,22 @@ public interface IXdcReleaseSpec : IReleaseSpec public int LimitPenaltyEpoch { get; set; } // Epochs in a row that a penalty node needs to be penalized 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; } + public Address[] GenesisMasterNodes { get; set; } + public long MergeSignRange { get; set; } + + public Address BlockSignerContract { get; set; } + public Address RandomizeSMCBinary { get; set; } + public Address XDCXLendingFinalizedTradeAddressBinary { get; set; } + public Address XDCXLendingAddressBinary { get; set; } + public Address XDCXAddressBinary { get; set; } + public Address TradingStateAddressBinary { get; set; } + public HashSet
BlackListedAddresses { get; set; } + public Address FoundationWallet { get; set; } + public Address MasternodeVotingContract { get; set; } + public bool IsTipTrc21FeeEnabled { get; set; } + public bool IsBlackListingEnabled { get; set; } + public bool IsTIP2019 { get; set; } + public bool IsTIPXDCXMiner { get; set; } + public bool IsDynamicGasLimitBlock { get; set; } public void ApplyV2Config(ulong round); } diff --git a/src/Nethermind/Nethermind.Xdc/SyncInfoManager.cs b/src/Nethermind/Nethermind.Xdc/SyncInfoManager.cs new file mode 100644 index 000000000000..b41a5f59aec5 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/SyncInfoManager.cs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Xdc.Types; + +namespace Nethermind.Xdc; +/// +/// Skeleton implementation +/// +internal class SyncInfoManager(IXdcConsensusContext xdcConsensusContext) : ISyncInfoManager +{ + public SyncInfo GetSyncInfo() => new SyncInfo(xdcConsensusContext.HighestQC, xdcConsensusContext.HighestTC); + + public void ProcessSyncInfo(SyncInfo syncInfo) + { + + } + + public bool VerifySyncInfo(SyncInfo syncInfo) + { + return true; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/TimeoutCertificateManager.cs b/src/Nethermind/Nethermind.Xdc/TimeoutCertificateManager.cs new file mode 100644 index 000000000000..83f6224378d6 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/TimeoutCertificateManager.cs @@ -0,0 +1,264 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Consensus; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.Errors; +using Nethermind.Xdc.RLP; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Nethermind.Xdc; + +public class TimeoutCertificateManager : ITimeoutCertificateManager +{ + private readonly EthereumEcdsa _ethereumEcdsa = new EthereumEcdsa(0); + private static readonly TimeoutDecoder _timeoutDecoder = new(); + private readonly IXdcConsensusContext _consensusContext; + private readonly ISnapshotManager _snapshotManager; + private readonly IEpochSwitchManager _epochSwitchManager; + private readonly ISpecProvider _specProvider; + private readonly IBlockTree _blockTree; + private readonly ISyncInfoManager _syncInfoManager; + private readonly ISigner _signer; + private readonly XdcPool _timeouts = new(); + + public TimeoutCertificateManager(IXdcConsensusContext context, ISnapshotManager snapshotManager, IEpochSwitchManager epochSwitchManager, ISpecProvider specProvider, IBlockTree blockTree, ISyncInfoManager syncInfoManager, ISigner signer) + { + _consensusContext = context; + this._snapshotManager = snapshotManager; + this._epochSwitchManager = epochSwitchManager; + this._specProvider = specProvider; + this._blockTree = blockTree; + this._syncInfoManager = syncInfoManager; + this._signer = signer; + } + + public Task HandleTimeoutVote(Timeout timeout) + { + if (timeout.Round != _consensusContext.CurrentRound) + { + // Not interested in processing timeout for round different from the current one + return Task.CompletedTask; + } + + _timeouts.Add(timeout); + var collectedTimeouts = _timeouts.GetItems(timeout); + + var xdcHeader = _blockTree.Head?.Header as XdcBlockHeader; + EpochSwitchInfo epochSwitchInfo = _epochSwitchManager.GetEpochSwitchInfo(xdcHeader); + if (epochSwitchInfo is null) + { + // Failed to get epoch switch info, cannot process timeout + return Task.CompletedTask; + } + + IXdcReleaseSpec spec = _specProvider.GetXdcSpec(xdcHeader, timeout.Round); + var certThreshold = spec.CertThreshold; + if (collectedTimeouts.Count >= epochSwitchInfo.Masternodes.Length * certThreshold) + { + OnTimeoutPoolThresholdReached(collectedTimeouts, timeout); + } + return Task.CompletedTask; + } + + + + private void OnTimeoutPoolThresholdReached(IEnumerable timeouts, Timeout timeout) + { + Signature[] signatures = timeouts.Select(t => t.Signature).ToArray(); + + var timeoutCertificate = new TimeoutCertificate(timeout.Round, signatures, timeout.GapNumber); + + ProcessTimeoutCertificate(timeoutCertificate); + + SyncInfo syncInfo = _syncInfoManager.GetSyncInfo(); + //TODO: Broadcast syncInfo + } + + public void ProcessTimeoutCertificate(TimeoutCertificate timeoutCertificate) + { + if (_consensusContext.HighestTC is null || timeoutCertificate.Round > _consensusContext.HighestTC.Round) + { + _consensusContext.HighestTC = timeoutCertificate; + } + + if (timeoutCertificate.Round >= _consensusContext.CurrentRound) + { + _timeouts.EndRound(timeoutCertificate.Round); + _consensusContext.SetNewRound(timeoutCertificate.Round + 1); + } + } + + public bool VerifyTimeoutCertificate(TimeoutCertificate timeoutCertificate, out string errorMessage) + { + if (timeoutCertificate is null) throw new ArgumentNullException(nameof(timeoutCertificate)); + if (timeoutCertificate.Signatures is null) throw new ArgumentNullException(nameof(timeoutCertificate.Signatures)); + + Snapshot snapshot = _snapshotManager.GetSnapshotByGapNumber((long)timeoutCertificate.GapNumber); + if (snapshot is null) + { + errorMessage = $"Failed to get snapshot using gap number {timeoutCertificate.GapNumber}"; + return false; + } + + if (snapshot.NextEpochCandidates.Length == 0) + { + errorMessage = "Empty master node list from snapshot"; + return false; + } + var nextEpochCandidates = new HashSet
(snapshot.NextEpochCandidates); + + var signatures = new HashSet(timeoutCertificate.Signatures); + var xdcHeader = _blockTree.Head?.Header as XdcBlockHeader; + IXdcReleaseSpec spec = _specProvider.GetXdcSpec(xdcHeader, timeoutCertificate.Round); + EpochSwitchInfo epochInfo = _epochSwitchManager.GetTimeoutCertificateEpochInfo(timeoutCertificate); + if (epochInfo is null) + { + errorMessage = $"Failed to get epoch switch info for timeout certificate with round {timeoutCertificate.Round}"; + return false; + } + if (signatures.Count < epochInfo.Masternodes.Length * spec.CertThreshold) + { + errorMessage = $"Number of unique signatures {signatures.Count} does not meet threshold of {epochInfo.Masternodes.Length * spec.CertThreshold}"; + return false; + } + + ValueHash256 timeoutMsgHash = ComputeTimeoutMsgHash(timeoutCertificate.Round, timeoutCertificate.GapNumber); + bool allValid = true; + Parallel.ForEach(signatures, + (signature, state) => + { + Address signer = _ethereumEcdsa.RecoverAddress(signature, in timeoutMsgHash); + if (!nextEpochCandidates.Contains(signer)) + { + allValid = false; + state.Stop(); + } + }); + if (!allValid) + { + errorMessage = "One or more invalid signatures"; + return false; + } + + errorMessage = null; + return true; + } + + public void OnCountdownTimer() + { + if (!AllowedToSend()) + return; + + SendTimeout(); + _consensusContext.TimeoutCounter++; + + var xdcHeader = _blockTree.Head?.Header as XdcBlockHeader; + IXdcReleaseSpec spec = _specProvider.GetXdcSpec(xdcHeader!, _consensusContext.CurrentRound); + + if (_consensusContext.TimeoutCounter % spec.TimeoutSyncThreshold == 0) + { + SyncInfo syncInfo = _syncInfoManager.GetSyncInfo(); + //TODO: Broadcast syncInfo + } + } + + public Task OnReceiveTimeout(Timeout timeout) + { + var currentBlock = _blockTree.Head ?? throw new InvalidOperationException("Failed to get current block"); + var currentHeader = currentBlock.Header as XdcBlockHeader; + var currentBlockNumber = currentBlock.Number; + var epochLength = _specProvider.GetXdcSpec(currentHeader, timeout.Round).EpochLength; + if (Math.Abs((long)timeout.GapNumber - currentBlockNumber) > 3 * epochLength) + { + // Discarded propagated timeout, too far away + return Task.CompletedTask; + } + + if (FilterTimeout(timeout)) + { + //TODO: Broadcast Timeout + return HandleTimeoutVote(timeout); + } + return Task.CompletedTask; + } + + internal bool FilterTimeout(Timeout timeout) + { + if (timeout.Round < _consensusContext.CurrentRound) return false; + Snapshot snapshot = _snapshotManager.GetSnapshotByGapNumber((long)timeout.GapNumber); + if (snapshot is null || snapshot.NextEpochCandidates.Length == 0) return false; + + // Verify msg signature + ValueHash256 timeoutMsgHash = ComputeTimeoutMsgHash(timeout.Round, timeout.GapNumber); + Address signer = _ethereumEcdsa.RecoverAddress(timeout.Signature, in timeoutMsgHash); + timeout.Signer = signer; + + return snapshot.NextEpochCandidates.Contains(signer); + } + + private void SendTimeout() + { + long gapNumber = 0; + var currentHeader = (XdcBlockHeader)_blockTree.Head?.Header; + if (currentHeader is null) throw new InvalidOperationException("Failed to retrieve current header"); + IXdcReleaseSpec spec = _specProvider.GetXdcSpec(currentHeader, _consensusContext.CurrentRound); + if (_epochSwitchManager.IsEpochSwitchAtRound(_consensusContext.CurrentRound, currentHeader)) + { + var currentNumber = currentHeader.Number + 1; + gapNumber = Math.Max(0, currentNumber - currentNumber % spec.EpochLength - spec.Gap); + } + else + { + EpochSwitchInfo epochSwitchInfo = _epochSwitchManager.GetEpochSwitchInfo(currentHeader); + if (epochSwitchInfo is null) + throw new DataExtractionException(nameof(EpochSwitchInfo)); + + var currentNumber = epochSwitchInfo.EpochSwitchBlockInfo.BlockNumber; + gapNumber = Math.Max(0, currentNumber - currentNumber % spec.EpochLength - spec.Gap); + } + + ValueHash256 msgHash = ComputeTimeoutMsgHash(_consensusContext.CurrentRound, (ulong)gapNumber); + Signature signedHash = _signer.Sign(msgHash); + var timeoutMsg = new Timeout(_consensusContext.CurrentRound, signedHash, (ulong)gapNumber); + timeoutMsg.Signer = _signer.Address; + + HandleTimeoutVote(timeoutMsg); + + //TODO: Broadcast _ctx.HighestTC + } + + // Returns true if the signer is within the master node list + private bool AllowedToSend() + { + var currentHeader = (XdcBlockHeader)_blockTree.Head?.Header; + EpochSwitchInfo epochSwitchInfo = _epochSwitchManager.GetEpochSwitchInfo(currentHeader); + if (epochSwitchInfo is null) + return false; + return epochSwitchInfo.Masternodes.Contains(_signer.Address); + } + + internal static ValueHash256 ComputeTimeoutMsgHash(ulong round, ulong gap) + { + Timeout timeout = new(round, null, gap); + KeccakRlpStream stream = new KeccakRlpStream(); + _timeoutDecoder.Encode(stream, timeout, RlpBehaviors.ForSealing); + return stream.GetValueHash(); + } + + public long GetTimeoutsCount(Timeout timeout) + { + return _timeouts.GetCount(timeout); + } + +} diff --git a/src/Nethermind/Nethermind.Xdc/TimeoutTimer.cs b/src/Nethermind/Nethermind.Xdc/TimeoutTimer.cs new file mode 100644 index 000000000000..3e6a995359d4 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/TimeoutTimer.cs @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Xdc +{ + internal class TimeoutTimer : IDisposable, ITimeoutTimer + { + private readonly System.Timers.Timer timer; + private readonly ITimeoutCertificateManager _timeoutCertificateManager; + + public TimeoutTimer(ITimeoutCertificateManager timeoutCertificateManager) + { + timer = new System.Timers.Timer(); + timer.AutoReset = false; + timer.Elapsed += (s, e) => Callback(); + _timeoutCertificateManager = timeoutCertificateManager; + } + + public void Start(TimeSpan period) + { + timer.Interval = period.TotalMilliseconds; + timer.Enabled = true; + timer.Start(); + } + + public void Reset(TimeSpan period) + { + timer.Interval = period.TotalMilliseconds; + timer.Enabled = true; + } + + public void Dispose() + { + timer.Elapsed -= (s, e) => Callback(); + timer?.Dispose(); + } + + public void TriggerTimeout() + { + Callback(); + } + private void Callback() + { + _timeoutCertificateManager.OnCountdownTimer(); + } + } +} diff --git a/src/Nethermind/Nethermind.Xdc/TransactionResult.cs b/src/Nethermind/Nethermind.Xdc/TransactionResult.cs new file mode 100644 index 000000000000..2c1f696a33a1 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/TransactionResult.cs @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Evm.TransactionProcessing; +using System; +using System.Collections.Generic; +using System.Text; +using static Nethermind.Evm.TransactionProcessing.TransactionResult; + +namespace Nethermind.Xdc; + +internal struct XdcTransactionResult +{ + public const ErrorType ContainsBlacklistedAddressError = (ErrorType)12; + public const ErrorType NonceTooHighError = (ErrorType)13; + public const ErrorType NonceTooLowError = (ErrorType)14; + + public static TransactionResult ContainsBlacklistedAddress => (TransactionResult)ContainsBlacklistedAddressError; + public static TransactionResult NonceTooHigh => (TransactionResult)NonceTooHighError; + public static TransactionResult NonceTooLow => (TransactionResult)NonceTooLowError; +} diff --git a/src/Nethermind/Nethermind.Xdc/TxPool/SignTransactionFilter.cs b/src/Nethermind/Nethermind.Xdc/TxPool/SignTransactionFilter.cs new file mode 100644 index 000000000000..1ccebbc66c61 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/TxPool/SignTransactionFilter.cs @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Consensus; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Int256; +using Nethermind.TxPool; +using Nethermind.TxPool.Filters; +using Nethermind.Xdc; +using Nethermind.Xdc.Spec; +using System; + +namespace Nethermind.Xdc.TxPool; + +internal sealed class SignTransactionFilter(ISigner signer, IBlockTree blockTree, ISpecProvider specProvider) : IIncomingTxFilter +{ + private (long, IXdcReleaseSpec) GetSpecAndHeader() + { + XdcBlockHeader header = (XdcBlockHeader)blockTree.Head.Header; + var currentHeaderNumber = header.Number + 1; + var xdcSpec = specProvider.GetXdcSpec(currentHeaderNumber); + + return (currentHeaderNumber, xdcSpec); + } + + private AcceptTxResult ValidateSignTransaction(Transaction tx, long headerNumber, IXdcReleaseSpec xdcSpec) + { + if (tx.Data.Length < 68) + { + return AcceptTxResult.Invalid; + } + + UInt256 blkNumber = new UInt256(tx.Data.Span.Slice(4, 32), true); + if (blkNumber >= headerNumber || blkNumber <= (headerNumber - (xdcSpec.EpochLength * 2))) + { + // Invalid block number in special transaction data + return AcceptTxResult.Invalid; + } + + return AcceptTxResult.Accepted; + } + + public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandlingOptions txHandlingOptions) + { + var (headerNumber, spec) = GetSpecAndHeader(); + + if (tx.IsSpecialTransaction((IXdcReleaseSpec)specProvider.GetFinalSpec())) + { + if (tx.IsSignTransaction(spec) && !ValidateSignTransaction(tx, headerNumber, spec)) + { + return AcceptTxResult.Invalid; + } + + if (tx.SenderAddress != signer.Address) + { + return AcceptTxResult.Invalid; + } + } + + return AcceptTxResult.Accepted; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/TxPool/XdcTransactionComparerProvider.cs b/src/Nethermind/Nethermind.Xdc/TxPool/XdcTransactionComparerProvider.cs new file mode 100644 index 000000000000..4a994bd7e044 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/TxPool/XdcTransactionComparerProvider.cs @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain.Find; +using Nethermind.Consensus; +using Nethermind.Consensus.Comparers; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.TxPool.Comparison; +using Nethermind.Xdc.Spec; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Nethermind.Xdc.TxPool; + +internal class CompareTxBySender(IXdcReleaseSpec spec) : IComparer +{ + public CompareTxBySender(ISpecProvider specProvider) + : this((IXdcReleaseSpec)specProvider.GetFinalSpec()) + { + } + + public int Compare(Transaction? newTx, Transaction? oldTx) + { + if (ReferenceEquals(newTx, oldTx)) return TxComparisonResult.NotDecided; + if (oldTx is null) return TxComparisonResult.KeepOld; + if (newTx is null) return TxComparisonResult.TakeNew; + + bool isNewTxSpecial = newTx.IsSpecialTransaction(spec); + bool isOldTxSpecial = oldTx.IsSpecialTransaction(spec); + + if (!isNewTxSpecial && !isOldTxSpecial) return TxComparisonResult.NotDecided; + if (isNewTxSpecial && !isOldTxSpecial) return TxComparisonResult.TakeNew; + if (!isNewTxSpecial && isOldTxSpecial) return TxComparisonResult.KeepOld; + + return TxComparisonResult.NotDecided; + } +} + +internal class XdcTransactionComparerProvider(ISpecProvider specProvider, IBlockFinder blockFinder) + : ITransactionComparerProvider +{ + private readonly ITransactionComparerProvider defaultComparerProvider = new TransactionComparerProvider(specProvider, blockFinder); + + public IComparer GetDefaultComparer() + { + var defaultComparer = defaultComparerProvider.GetDefaultComparer(); + + var signerFilter = new CompareTxBySender(specProvider); + + return signerFilter.ThenBy(defaultComparer); + } + + public IComparer GetDefaultProducerComparer(BlockPreparationContext blockPreparationContext) + { + var defaultComparer = defaultComparerProvider.GetDefaultProducerComparer(blockPreparationContext); + + var currentSpec = specProvider.GetXdcSpec(blockPreparationContext.BlockNumber); + + var signerFilter = new CompareTxBySender(currentSpec); + + return signerFilter.ThenBy(defaultComparer); + } +} diff --git a/src/Nethermind/Nethermind.Xdc/TxPool/XdcTxGossipPolicy.cs b/src/Nethermind/Nethermind.Xdc/TxPool/XdcTxGossipPolicy.cs new file mode 100644 index 000000000000..a67386c0b3f2 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/TxPool/XdcTxGossipPolicy.cs @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.TxPool; +using Nethermind.Xdc.Spec; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Nethermind.Xdc.TxPool; + +internal class XdcTxGossipPolicy(ISpecProvider provider, IChainHeadInfoProvider chainHeadInfoProvider) : ITxGossipPolicy +{ + public bool ShouldGossipTransaction(Transaction tx) + { + var spec = (IXdcReleaseSpec)provider.GetXdcSpec(chainHeadInfoProvider.HeadNumber); + + return !tx.RequiresSpecialHandling(spec); + } +} diff --git a/src/Nethermind/Nethermind.Xdc/TxPool/XdcTxPoolTxSourceFactory.cs b/src/Nethermind/Nethermind.Xdc/TxPool/XdcTxPoolTxSourceFactory.cs new file mode 100644 index 000000000000..41986e81fc4a --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/TxPool/XdcTxPoolTxSourceFactory.cs @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain.Find; +using Nethermind.Config; +using Nethermind.Consensus; +using Nethermind.Consensus.Comparers; +using Nethermind.Consensus.Producers; +using Nethermind.Consensus.Transactions; +using Nethermind.Core.Specs; +using Nethermind.Logging; +using Nethermind.TxPool; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Nethermind.Xdc.TxPool; + +internal class XdcTxPoolTxSourceFactory( + ITxPool txPool, + ISpecProvider specProvider, + IBlocksConfig blocksConfig, + IBlockFinder blockFinder, + ILogManager logManager) : IBlockProducerTxSourceFactory +{ + public virtual ITxSource Create() + { + ITxFilterPipeline txSourceFilterPipeline = TxFilterPipelineBuilder.CreateStandardFilteringPipeline(logManager, blocksConfig); + return new TxPoolTxSource(txPool, specProvider, new XdcTransactionComparerProvider(specProvider, blockFinder), logManager, txSourceFilterPipeline, blocksConfig); + } +} diff --git a/src/Nethermind/Nethermind.Xdc/Types/BlockRoundInfo.cs b/src/Nethermind/Nethermind.Xdc/Types/BlockRoundInfo.cs index 057aadfbcafe..76f24fe24efa 100644 --- a/src/Nethermind/Nethermind.Xdc/Types/BlockRoundInfo.cs +++ b/src/Nethermind/Nethermind.Xdc/Types/BlockRoundInfo.cs @@ -2,12 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core.Crypto; -using Nethermind.Int256; using Nethermind.Serialization.Rlp; -using System.Linq; -using System.Text; -using System.Text.Json.Serialization; -using System.Threading.Tasks; namespace Nethermind.Xdc.Types; diff --git a/src/Nethermind/Nethermind.Xdc/Types/EpochSwitchInfo.cs b/src/Nethermind/Nethermind.Xdc/Types/EpochSwitchInfo.cs index a3d7c8c404e3..a18bd504bd00 100644 --- a/src/Nethermind/Nethermind.Xdc/Types/EpochSwitchInfo.cs +++ b/src/Nethermind/Nethermind.Xdc/Types/EpochSwitchInfo.cs @@ -2,15 +2,14 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core; -using System.Collections.Generic; namespace Nethermind.Xdc.Types; -public class EpochSwitchInfo(Address[] penalties, Address[] standbynodes, Address[] masternodes, BlockRoundInfo epochSwitchBlockInfo, BlockRoundInfo epochSwitchParentBlockInfo) +public class EpochSwitchInfo(Address[] masternodes, Address[] StandbyNodes, Address[] penalties, BlockRoundInfo epochSwitchCurrentBlockInfo) { - public Address[] Penalties { get; set; } = penalties; - public Address[] Standbynodes { get; set; } = standbynodes; public Address[] Masternodes { get; set; } = masternodes; - public BlockRoundInfo EpochSwitchBlockInfo { get; set; } = epochSwitchBlockInfo; - public BlockRoundInfo EpochSwitchParentBlockInfo { get; set; } = epochSwitchParentBlockInfo; + public Address[] StandbyNodes { get; } = StandbyNodes; + public Address[] Penalties { get; set; } = penalties; + public BlockRoundInfo EpochSwitchBlockInfo { get; set; } = epochSwitchCurrentBlockInfo; + public BlockRoundInfo? EpochSwitchParentBlockInfo { get; set; } } diff --git a/src/Nethermind/Nethermind.Xdc/Types/ExpTimeoutConfig.cs b/src/Nethermind/Nethermind.Xdc/Types/ExpTimeoutConfig.cs deleted file mode 100644 index 79ef4873b2bb..000000000000 --- a/src/Nethermind/Nethermind.Xdc/Types/ExpTimeoutConfig.cs +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.Json.Serialization; -using System.Threading.Tasks; - -namespace Nethermind.Xdc.Types; -public class ExpTimeoutConfig(int @base, byte maxExponent) -{ - public int Base { get; set; } = @base; - public byte MaxExponent { get; set; } = maxExponent; -} diff --git a/src/Nethermind/Nethermind.Xdc/Types/ExtraFieldsV2.cs b/src/Nethermind/Nethermind.Xdc/Types/ExtraFieldsV2.cs index 39835886ad41..7745ff389b5f 100644 --- a/src/Nethermind/Nethermind.Xdc/Types/ExtraFieldsV2.cs +++ b/src/Nethermind/Nethermind.Xdc/Types/ExtraFieldsV2.cs @@ -1,12 +1,10 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Nethermind.Serialization.Rlp; - namespace Nethermind.Xdc.Types; -public class ExtraFieldsV2(ulong round, QuorumCert quorumCert) +public class ExtraFieldsV2(ulong round, QuorumCertificate quorumCert) { - public ulong CurrentRound { get; } = round; - public QuorumCert QuorumCert { get; } = quorumCert; + public ulong BlockRound { get; } = round; + public QuorumCertificate QuorumCert { get; } = quorumCert; } diff --git a/src/Nethermind/Nethermind.Xdc/Types/IExpCountDown.cs b/src/Nethermind/Nethermind.Xdc/Types/IExpCountDown.cs index 568b3bffb313..d1ca34df4760 100644 --- a/src/Nethermind/Nethermind.Xdc/Types/IExpCountDown.cs +++ b/src/Nethermind/Nethermind.Xdc/Types/IExpCountDown.cs @@ -4,6 +4,7 @@ using System; namespace Nethermind.Xdc.Types; + public interface IExpCountDown { void Dispose(); diff --git a/src/Nethermind/Nethermind.Xdc/Types/MasterNode.cs b/src/Nethermind/Nethermind.Xdc/Types/MasterNode.cs index 8f3b306dea3b..925a93862c54 100644 --- a/src/Nethermind/Nethermind.Xdc/Types/MasterNode.cs +++ b/src/Nethermind/Nethermind.Xdc/Types/MasterNode.cs @@ -3,13 +3,9 @@ using Nethermind.Core; using Nethermind.Int256; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Nethermind.Xdc.Types; + public class MasterNode(Address address, UInt256 stake) { public Address Address { get; set; } = address; diff --git a/src/Nethermind/Nethermind.Xdc/Types/QuorumCert.cs b/src/Nethermind/Nethermind.Xdc/Types/QuorumCertificate.cs similarity index 67% rename from src/Nethermind/Nethermind.Xdc/Types/QuorumCert.cs rename to src/Nethermind/Nethermind.Xdc/Types/QuorumCertificate.cs index 22c9f208183f..7a6d67de9f5b 100644 --- a/src/Nethermind/Nethermind.Xdc/Types/QuorumCert.cs +++ b/src/Nethermind/Nethermind.Xdc/Types/QuorumCertificate.cs @@ -2,12 +2,10 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core.Crypto; -using System.Collections.Generic; -using System.Text.Json.Serialization; namespace Nethermind.Xdc.Types; -public class QuorumCert(BlockRoundInfo proposedBlockInfo, Signature[]? signatures, ulong gapNumber) +public class QuorumCertificate(BlockRoundInfo proposedBlockInfo, Signature[]? signatures, ulong gapNumber) { public BlockRoundInfo ProposedBlockInfo { get; set; } = proposedBlockInfo; public Signature[] Signatures { get; set; } = signatures; diff --git a/src/Nethermind/Nethermind.Xdc/Types/SignFn.cs b/src/Nethermind/Nethermind.Xdc/Types/SignFn.cs index c986599ff228..f4b730a7a8f0 100644 --- a/src/Nethermind/Nethermind.Xdc/Types/SignFn.cs +++ b/src/Nethermind/Nethermind.Xdc/Types/SignFn.cs @@ -2,11 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Nethermind.Xdc.Types; + public delegate byte[] SignFn(Address address, byte[] data); diff --git a/src/Nethermind/Nethermind.Xdc/Types/Snapshot.cs b/src/Nethermind/Nethermind.Xdc/Types/Snapshot.cs index cc0aa616fd03..9b1f3cc1c42a 100644 --- a/src/Nethermind/Nethermind.Xdc/Types/Snapshot.cs +++ b/src/Nethermind/Nethermind.Xdc/Types/Snapshot.cs @@ -1,9 +1,6 @@ -// 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 Nethermind.Xdc.Types; using Nethermind.Core; using Nethermind.Core.Crypto; diff --git a/src/Nethermind/Nethermind.Xdc/Types/SubnetSnapshot.cs b/src/Nethermind/Nethermind.Xdc/Types/SubnetSnapshot.cs new file mode 100644 index 000000000000..ab8bb807df20 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/Types/SubnetSnapshot.cs @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Crypto; + +namespace Nethermind.Xdc.Types; + +public class SubnetSnapshot : Snapshot +{ + public Address[] NextEpochPenalties { get; set; } + + public SubnetSnapshot(long number, Hash256 hash, Address[] validators) : base(number, hash, validators) + { + NextEpochPenalties = []; + } + + public SubnetSnapshot(long number, Hash256 hash, Address[] validators, Address[] penalties) : base(number, hash, validators) + { + NextEpochPenalties = penalties ?? []; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/Types/SyncInfo.cs b/src/Nethermind/Nethermind.Xdc/Types/SyncInfo.cs index fcdfeeeeaaf6..a415e8f364d9 100644 --- a/src/Nethermind/Nethermind.Xdc/Types/SyncInfo.cs +++ b/src/Nethermind/Nethermind.Xdc/Types/SyncInfo.cs @@ -1,15 +1,10 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Nethermind.Core.Crypto; -using Nethermind.Serialization.Rlp; - namespace Nethermind.Xdc.Types; -public class SyncInfo(QuorumCert highestQuorumCert, TimeoutCert highestTimeoutCert) +public class SyncInfo(QuorumCertificate highestQuorumCert, TimeoutCertificate highestTimeoutCert) { - public QuorumCert HighestQuorumCert { get; set; } = highestQuorumCert; - public TimeoutCert HighestTimeoutCert { get; set; } = highestTimeoutCert; - - public Hash256 SigHash() => Keccak.Compute(Rlp.Encode(this).Bytes); + public QuorumCertificate HighestQuorumCert { get; set; } = highestQuorumCert; + public TimeoutCertificate HighestTimeoutCert { get; set; } = highestTimeoutCert; } diff --git a/src/Nethermind/Nethermind.Xdc/Types/Timeout.cs b/src/Nethermind/Nethermind.Xdc/Types/Timeout.cs index 3cf5a78f5524..ef1df36cabd7 100644 --- a/src/Nethermind/Nethermind.Xdc/Types/Timeout.cs +++ b/src/Nethermind/Nethermind.Xdc/Types/Timeout.cs @@ -4,22 +4,17 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Serialization.Rlp; -using System; +using Nethermind.Xdc.RLP; namespace Nethermind.Xdc.Types; -public class Timeout(ulong round, Signature signature, ulong gapNumber) +public class Timeout(ulong round, Signature? signature, ulong gapNumber) : IXdcPoolItem { - private Address signer; - + private static readonly TimeoutDecoder _decoder = new(); public ulong Round { get; set; } = round; - public Signature Signature { get; set; } = signature; + public Signature? Signature { get; set; } = signature; public ulong GapNumber { get; set; } = gapNumber; - - public Hash256 SigHash() => Keccak.Compute(Rlp.Encode(this).Bytes); - + public Address? Signer { get; set; } public override string ToString() => $"{Round}:{GapNumber}"; - - public Address GetSigner() => signer; - public void SetSigner(Address signer) => this.signer = signer; + public (ulong Round, Hash256 hash) PoolKey() => (Round, Keccak.Compute(_decoder.Encode(this, RlpBehaviors.ForSealing).Bytes)); } diff --git a/src/Nethermind/Nethermind.Xdc/Types/TimeoutCert.cs b/src/Nethermind/Nethermind.Xdc/Types/TimeoutCertificate.cs similarity index 74% rename from src/Nethermind/Nethermind.Xdc/Types/TimeoutCert.cs rename to src/Nethermind/Nethermind.Xdc/Types/TimeoutCertificate.cs index 4997429880f3..9c9cebf2fe49 100644 --- a/src/Nethermind/Nethermind.Xdc/Types/TimeoutCert.cs +++ b/src/Nethermind/Nethermind.Xdc/Types/TimeoutCertificate.cs @@ -2,11 +2,10 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core.Crypto; -using System.Collections.Generic; namespace Nethermind.Xdc.Types; -public class TimeoutCert(ulong round, Signature[] signatures, ulong gapNumber) +public class TimeoutCertificate(ulong round, Signature[] signatures, ulong gapNumber) { public ulong Round { get; set; } = round; public Signature[] Signatures { get; set; } = signatures; diff --git a/src/Nethermind/Nethermind.Xdc/Types/TimeoutForSign.cs b/src/Nethermind/Nethermind.Xdc/Types/TimeoutForSign.cs deleted file mode 100644 index 092147e26b19..000000000000 --- a/src/Nethermind/Nethermind.Xdc/Types/TimeoutForSign.cs +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Core.Crypto; -using Nethermind.Serialization.Rlp; - -namespace Nethermind.Xdc.Types; - -public class TimeoutForSign(ulong round, ulong gapNumber) -{ - public ulong Round { get; set; } = round; - public ulong GapNumber { get; set; } = gapNumber; - public Hash256 SigHash() => Keccak.Compute(Rlp.Encode(this).Bytes); -} diff --git a/src/Nethermind/Nethermind.Xdc/Types/Vote.cs b/src/Nethermind/Nethermind.Xdc/Types/Vote.cs index f7ec509b8298..cce3d5490339 100644 --- a/src/Nethermind/Nethermind.Xdc/Types/Vote.cs +++ b/src/Nethermind/Nethermind.Xdc/Types/Vote.cs @@ -3,25 +3,20 @@ using Nethermind.Core; using Nethermind.Core.Crypto; -using System.Text.Json.Serialization; +using RlpBehaviors = Nethermind.Serialization.Rlp.RlpBehaviors; namespace Nethermind.Xdc.Types; -public class Vote(Address signer, BlockRoundInfo proposedBlockInfo, Signature signature, long gapNumber) +public class Vote(BlockRoundInfo proposedBlockInfo, ulong gapNumber, Signature signature = null) : IXdcPoolItem { - public Vote(BlockRoundInfo proposedBlockInfo, Signature signature, long gapNumber) - : this(default, proposedBlockInfo, signature, gapNumber) - { - } - - private Address _signer = signer; + private static readonly VoteDecoder _decoder = new(); public BlockRoundInfo ProposedBlockInfo { get; set; } = proposedBlockInfo; - public Signature Signature { get; set; } = signature; - public long GapNumber { get; set; } = gapNumber; + public ulong GapNumber { get; set; } = gapNumber; + public Signature? Signature { get; set; } = signature; + public Address? Signer { get; set; } public override string ToString() => - $"{ProposedBlockInfo.Round}:{GapNumber}:{ProposedBlockInfo.BlockNumber}:{ProposedBlockInfo.SigHash()}"; + $"{ProposedBlockInfo.Round}:{GapNumber}:{ProposedBlockInfo.BlockNumber}"; - public Address GetSigner() => _signer; - public void SetSigner(Address signer) => _signer = signer; + public (ulong Round, Hash256 hash) PoolKey() => (ProposedBlockInfo.Round, Keccak.Compute(_decoder.Encode(this, RlpBehaviors.ForSealing).Bytes)); } diff --git a/src/Nethermind/Nethermind.Xdc/Types/VoteForSign.cs b/src/Nethermind/Nethermind.Xdc/Types/VoteForSign.cs deleted file mode 100644 index a8b7e1a075f9..000000000000 --- a/src/Nethermind/Nethermind.Xdc/Types/VoteForSign.cs +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Core.Crypto; -using Nethermind.Serialization.Rlp; - -namespace Nethermind.Xdc.Types; - -public class VoteForSign(BlockRoundInfo proposedBlockInfo, long gapNumber) -{ - public BlockRoundInfo ProposedBlockInfo { get; set; } = proposedBlockInfo; - public long GapNumber { get; set; } = gapNumber; - public Hash256 SigHash() => Keccak.Compute(Rlp.Encode(this).Bytes); -} diff --git a/src/Nethermind/Nethermind.Xdc/VotesManager.cs b/src/Nethermind/Nethermind.Xdc/VotesManager.cs new file mode 100644 index 000000000000..5687caed3be5 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/VotesManager.cs @@ -0,0 +1,252 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Consensus; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Types; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Nethermind.Xdc; + +internal class VotesManager( + IXdcConsensusContext context, + IBlockTree tree, + IEpochSwitchManager epochSwitchManager, + ISnapshotManager snapshotManager, + IQuorumCertificateManager quorumCertificateManager, + ISpecProvider specProvider, + ISigner signer, + IForensicsProcessor forensicsProcessor) : IVotesManager +{ + private readonly IBlockTree _blockTree = tree; + private readonly IEpochSwitchManager _epochSwitchManager = epochSwitchManager; + private readonly ISnapshotManager _snapshotManager = snapshotManager; + private readonly IQuorumCertificateManager _quorumCertificateManager = quorumCertificateManager; + private readonly IXdcConsensusContext _ctx = context; + private readonly IForensicsProcessor _forensicsProcessor = forensicsProcessor; + private readonly ISpecProvider _specProvider = specProvider; + private readonly ISigner _signer = signer; + + private readonly XdcPool _votePool = new(); + private static readonly VoteDecoder _voteDecoder = new(); + private static readonly EthereumEcdsa _ethereumEcdsa = new(0); + private readonly ConcurrentDictionary _qcBuildStartedByRound = new(); + private const int _maxBlockDistance = 7; // Maximum allowed backward distance from the chain head + private long _highestVotedRound = -1; + + public Task CastVote(BlockRoundInfo blockInfo) + { + EpochSwitchInfo epochSwitchInfo = _epochSwitchManager.GetEpochSwitchInfo(blockInfo.Hash); + if (epochSwitchInfo is null) + throw new ArgumentException($"Cannot find epoch info for block {blockInfo.Hash}", nameof(EpochSwitchInfo)); + //Optimize this by fetching with block number and round only + + XdcBlockHeader header = _blockTree.FindHeader(blockInfo.Hash) as XdcBlockHeader; + if (header is null) + throw new ArgumentException($"Cannot find block header for block {blockInfo.Hash}"); + + IXdcReleaseSpec spec = _specProvider.GetXdcSpec(header, blockInfo.Round); + long epochSwitchNumber = epochSwitchInfo.EpochSwitchBlockInfo.BlockNumber; + long gapNumber = epochSwitchNumber == 0 ? 0 : Math.Max(0, epochSwitchNumber - epochSwitchNumber % spec.EpochLength - spec.Gap); + + var vote = new Vote(blockInfo, (ulong)gapNumber); + // Sets signature and signer for the vote + Sign(vote); + + _highestVotedRound = (long)blockInfo.Round; + + HandleVote(vote); + //TODO Broadcast vote to peers + return Task.CompletedTask; + } + + public Task HandleVote(Vote vote) + { + if ((vote.ProposedBlockInfo.Round != _ctx.CurrentRound) && (vote.ProposedBlockInfo.Round != _ctx.CurrentRound + 1)) + { + //We only care about votes for the current round or the next round + return Task.CompletedTask; + } + + // Collect votes + _votePool.Add(vote); + IReadOnlyCollection roundVotes = _votePool.GetItems(vote); + _ = _forensicsProcessor.DetectEquivocationInVotePool(vote, roundVotes); + _ = _forensicsProcessor.ProcessVoteEquivocation(vote); + + XdcBlockHeader proposedHeader = _blockTree.FindHeader(vote.ProposedBlockInfo.Hash, vote.ProposedBlockInfo.BlockNumber) as XdcBlockHeader; + if (proposedHeader is null) + { + //This is a vote for a block we have not seen yet, just return for now + return Task.CompletedTask; + } + + EpochSwitchInfo epochInfo = _epochSwitchManager.GetEpochSwitchInfo(proposedHeader); + if (epochInfo is null) + { + //Unknown epoch switch info, cannot process vote + return Task.CompletedTask; + } + 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; + 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 < requiredVotes) + return Task.CompletedTask; + + // At this point, the QC should be processed for this *round*. + // Ensure this runs only once per round: + var round = vote.ProposedBlockInfo.Round; + if (!_qcBuildStartedByRound.TryAdd(round, 0)) + return Task.CompletedTask; + OnVotePoolThresholdReached(validSignatures, vote); + } + return Task.CompletedTask; + } + + private void EndRound(ulong round) + { + _votePool.EndRound(round); + + foreach (var key in _qcBuildStartedByRound.Keys) + if (key <= round) _qcBuildStartedByRound.TryRemove(key, out _); + } + + public bool VerifyVotingRules(BlockRoundInfo roundInfo, QuorumCertificate qc) => VerifyVotingRules(roundInfo.Hash, roundInfo.BlockNumber, roundInfo.Round, qc); + public bool VerifyVotingRules(XdcBlockHeader header) => VerifyVotingRules(header.Hash, header.Number, header.ExtraConsensusData.BlockRound, header.ExtraConsensusData.QuorumCert); + public bool VerifyVotingRules(Hash256 blockHash, long blockNumber, ulong roundNumber, QuorumCertificate qc) + { + if ((long)_ctx.CurrentRound <= _highestVotedRound) + { + return false; + } + + if (roundNumber != _ctx.CurrentRound) + { + return false; + } + + if (_ctx.LockQC is null) + { + return true; + } + + if (qc.ProposedBlockInfo.Round > _ctx.LockQC.ProposedBlockInfo.Round) + { + return true; + } + + if (!IsExtendingFromAncestor(blockHash, blockNumber, _ctx.LockQC.ProposedBlockInfo)) + { + return false; + } + + return true; + } + + public Task OnReceiveVote(Vote vote) + { + var voteBlockNumber = vote.ProposedBlockInfo.BlockNumber; + var currentBlockNumber = _blockTree.Head?.Number ?? throw new InvalidOperationException("Failed to get current block number"); + if (Math.Abs(voteBlockNumber - currentBlockNumber) > _maxBlockDistance) + { + // Discarded propagated vote, too far away + return Task.CompletedTask; + } + + if (FilterVote(vote)) + { + //TODO: Broadcast Vote + return HandleVote(vote); + } + return Task.CompletedTask; + } + + internal bool FilterVote(Vote vote) + { + if (vote.ProposedBlockInfo.Round < _ctx.CurrentRound) return false; + + Snapshot snapshot = _snapshotManager.GetSnapshotByGapNumber((long)vote.GapNumber); + if (snapshot is null) return false; + // Verify message signature + vote.Signer ??= _ethereumEcdsa.RecoverVoteSigner(vote); + return snapshot.NextEpochCandidates.Any(x => x == vote.Signer); + } + + private void OnVotePoolThresholdReached(Signature[] validSignatures, Vote currVote) + { + QuorumCertificate qc = new(currVote.ProposedBlockInfo, validSignatures, currVote.GapNumber); + _quorumCertificateManager.CommitCertificate(qc); + EndRound(currVote.ProposedBlockInfo.Round); + } + + private bool IsExtendingFromAncestor(Hash256 blockHash, long blockNumber, BlockRoundInfo ancestorBlockInfo) + { + long blockNumDiff = blockNumber - ancestorBlockInfo.BlockNumber; + Hash256 nextBlockHash = blockHash; + + for (int i = 0; i < blockNumDiff; i++) + { + XdcBlockHeader parentHeader = _blockTree.FindHeader(nextBlockHash) as XdcBlockHeader; + if (parentHeader is null) + return false; + + nextBlockHash = parentHeader.ParentHash; + } + + return nextBlockHash == ancestorBlockInfo.Hash; + } + + private Signature[] GetValidSignatures(IEnumerable votes, Address[] masternodes) + { + var masternodeSet = new HashSet
(masternodes); + var signatures = new List(); + foreach (var vote in votes) + { + if (vote.Signer is null) + { + vote.Signer = _ethereumEcdsa.RecoverVoteSigner(vote); + } + + if (masternodeSet.Contains(vote.Signer)) + { + signatures.Add(vote.Signature); + } + } + return signatures.ToArray(); + } + + private void Sign(Vote vote) + { + KeccakRlpStream stream = new(); + _voteDecoder.Encode(stream, vote, RlpBehaviors.ForSealing); + vote.Signature = _signer.Sign(stream.GetValueHash()); + vote.Signer = _signer.Address; + } + + public long GetVotesCount(Vote vote) + { + return _votePool.GetCount(vote); + } +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcBaseFeeCalculator.cs b/src/Nethermind/Nethermind.Xdc/XdcBaseFeeCalculator.cs new file mode 100644 index 000000000000..f3643d75c192 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcBaseFeeCalculator.cs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Int256; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Nethermind.Xdc; + +internal class XdcBaseFeeCalculator : IBaseFeeCalculator +{ + //XDC 1559 just used a const value + //https://github.com/XinFinOrg/XDPoSChain/blob/e324a78d9466c02a121d4931248f5dc9505b580a/consensus/misc/eip1559/eip1559.go#L56 + public const long BaseFee = 12500000000; + + public UInt256 Calculate(BlockHeader parent, IEip1559Spec specFor1559) + { + return BaseFee; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcBeaconSyncStrategy.cs b/src/Nethermind/Nethermind.Xdc/XdcBeaconSyncStrategy.cs new file mode 100644 index 000000000000..98d99707fa8b --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcBeaconSyncStrategy.cs @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain.Synchronization; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Synchronization; + +namespace Nethermind.Xdc; + +public class XdcBeaconSyncStrategy : IBeaconSyncStrategy +{ + private readonly ISyncConfig _syncConfig; + + public XdcBeaconSyncStrategy(ISyncConfig syncConfig) + { + _syncConfig = syncConfig; + } + + public void AllowBeaconHeaderSync() { } + + public bool ShouldBeInBeaconHeaders() => false; + + public bool ShouldBeInBeaconModeControl() => false; + + public bool IsBeaconSyncFinished(BlockHeader? blockHeader) => true; + + public bool MergeTransitionFinished => false; + + public long? GetTargetBlockHeight() => _syncConfig.PivotNumber > 0 ? _syncConfig.PivotNumber : null; + + public Hash256? GetFinalizedHash() => null; + + public Hash256? GetHeadBlockHash() => null; +} + diff --git a/src/Nethermind/Nethermind.Xdc/XdcBlockHeader.cs b/src/Nethermind/Nethermind.Xdc/XdcBlockHeader.cs index 282708019d61..6e57112efae7 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcBlockHeader.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcBlockHeader.cs @@ -3,24 +3,19 @@ using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Core.Specs; using Nethermind.Crypto; using Nethermind.Int256; using Nethermind.Serialization.Rlp; using Nethermind.Xdc.RLP; -using Nethermind.Xdc.Spec; using Nethermind.Xdc.Types; using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Nethermind.Xdc; + public class XdcBlockHeader : BlockHeader, IHashResolver { - private static XdcHeaderDecoder _headerDecoder = new(); + private static readonly XdcHeaderDecoder _headerDecoder = new(); private static readonly ExtraConsensusDataDecoder _extraConsensusDataDecoder = new(); public XdcBlockHeader( Hash256 parentHash, @@ -44,7 +39,7 @@ public ImmutableArray
? ValidatorsAddress { if (_validatorsAddress is not null) return _validatorsAddress; - _validatorsAddress = ExtractAddresses(Validators); + _validatorsAddress = XdcExtensions.ExtractAddresses(Validators); return _validatorsAddress; } set { _validatorsAddress = value; } @@ -59,80 +54,75 @@ public ImmutableArray
? PenaltiesAddress { if (_penaltiesAddress is not null) return _penaltiesAddress; - _penaltiesAddress = ExtractAddresses(Penalties); + _penaltiesAddress = XdcExtensions.ExtractAddresses(Penalties); return _penaltiesAddress; } set { _penaltiesAddress = value; } } - internal Address[] GetMasterNodesFromEpochSwitchHeader() - { - if (Validators == null) - throw new InvalidOperationException("Header has no validators."); - Address[] masterNodes = new Address[Validators.Length / 20]; - for (int i = 0; i < masterNodes.Length; i++) - { - masterNodes[i] = new Address(Validators.AsSpan(i * 20, 20)); - } - return masterNodes; - } - private ExtraFieldsV2 _extraFieldsV2; + /// + /// Consensus data that must be included in a V2 block, which contains the quorum certificate and round information. + /// public ExtraFieldsV2? ExtraConsensusData { get { + if (_extraFieldsV2 is not null) + { + return _extraFieldsV2; + } + if (ExtraData is null || ExtraData.Length == 0) return null; - if (_extraFieldsV2 == null) - { - //Check V2 consensus version in ExtraData field. - if (ExtraData.Length < 3 || ExtraData[0] != 2) - return null; - Rlp.ValueDecoderContext valueDecoderContext = new Rlp.ValueDecoderContext(ExtraData.AsSpan(1)); - _extraFieldsV2 = _extraConsensusDataDecoder.Decode(ref valueDecoderContext); - } + //Check V2 consensus version in ExtraData field. + if (ExtraData.Length < 3 || ExtraData[0] != XdcConstants.ConsensusVersion) + return null; + Rlp.ValueDecoderContext valueDecoderContext = new Rlp.ValueDecoderContext(ExtraData.AsSpan(1)); + _extraFieldsV2 = _extraConsensusDataDecoder.Decode(ref valueDecoderContext); return _extraFieldsV2; } set { _extraFieldsV2 = value; } } - public bool IsEpochSwitch(IXdcReleaseSpec spec) + public virtual ValueHash256 CalculateHash() { - if (spec.SwitchBlock == this.Number) - { - return true; - } - ExtraFieldsV2? extraFields = ExtraConsensusData; - if (extraFields is null) - { - //Should this throw instead? - return false; - } - ulong parentRound = extraFields.QuorumCert.ProposedBlockInfo.Round; - ulong epochStart = extraFields.CurrentRound - extraFields.CurrentRound % (ulong)spec.EpochLength; - - return parentRound < epochStart; + KeccakRlpStream rlpStream = new KeccakRlpStream(); + _headerDecoder.Encode(rlpStream, this); + return rlpStream.GetHash(); } - private static ImmutableArray
? ExtractAddresses(byte[]? data) + public static XdcBlockHeader FromBlockHeader(BlockHeader src) { - if (data is null || data.Length % Address.Size != 0) - return null; - - Address[] addresses = new Address[data.Length / Address.Size]; - for (int i = 0; i < addresses.Length; i++) + var x = new XdcBlockHeader( + src.ParentHash, + src.UnclesHash, + src.Beneficiary, + src.Difficulty, + src.Number, + src.GasLimit, + src.Timestamp, + src.ExtraData) { - addresses[i] = new Address(data.AsSpan(i * Address.Size, Address.Size)); - } - return addresses.ToImmutableArray(); - } + Bloom = src.Bloom ?? Bloom.Empty, + Hash = src.Hash, + MixHash = src.MixHash, + Nonce = src.Nonce, + TxRoot = src.TxRoot, + TotalDifficulty = src.TotalDifficulty, + AuRaStep = src.AuRaStep, + AuRaSignature = src.AuRaSignature, + ReceiptsRoot = src.ReceiptsRoot, + BaseFeePerGas = src.BaseFeePerGas, + WithdrawalsRoot = src.WithdrawalsRoot, + RequestsHash = src.RequestsHash, + IsPostMerge = src.IsPostMerge, + ParentBeaconBlockRoot = src.ParentBeaconBlockRoot, + ExcessBlobGas = src.ExcessBlobGas, + BlobGasUsed = src.BlobGasUsed, + }; - public ValueHash256 CalculateHash() - { - KeccakRlpStream rlpStream = new KeccakRlpStream(); - _headerDecoder.Encode(rlpStream, this); - return rlpStream.GetHash(); + return x; } } diff --git a/src/Nethermind/Nethermind.Xdc/XdcBlockProcessor.cs b/src/Nethermind/Nethermind.Xdc/XdcBlockProcessor.cs new file mode 100644 index 000000000000..f04af2ca6b87 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcBlockProcessor.cs @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain.BeaconBlockRoot; +using Nethermind.Blockchain.Blocks; +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.Specs; +using Nethermind.Evm.State; +using Nethermind.Logging; + +namespace Nethermind.Xdc; + +internal class XdcBlockProcessor : BlockProcessor +{ + public XdcBlockProcessor(ISpecProvider specProvider, IBlockValidator blockValidator, IRewardCalculator rewardCalculator, IBlockProcessor.IBlockTransactionsExecutor blockTransactionsExecutor, IWorldState stateProvider, IReceiptStorage receiptStorage, IBeaconBlockRootHandler beaconBlockRootHandler, IBlockhashStore blockHashStore, ILogManager logManager, IWithdrawalProcessor withdrawalProcessor, IExecutionRequestsProcessor executionRequestsProcessor) : base(specProvider, blockValidator, rewardCalculator, blockTransactionsExecutor, stateProvider, receiptStorage, beaconBlockRootHandler, blockHashStore, logManager, withdrawalProcessor, executionRequestsProcessor) + { + } + + protected override Block PrepareBlockForProcessing(Block suggestedBlock) + { + //TODO find a better way to do this copy + XdcBlockHeader bh = suggestedBlock.Header as XdcBlockHeader; + XdcBlockHeader headerForProcessing = new( + bh.ParentHash, + bh.UnclesHash, + bh.Beneficiary, + bh.Difficulty, + bh.Number, + bh.GasLimit, + bh.Timestamp, + bh.ExtraData + ) + { + Bloom = Bloom.Empty, + Author = bh.Author, + Hash = bh.Hash, + MixHash = bh.MixHash, + Nonce = bh.Nonce, + TxRoot = bh.TxRoot, + TotalDifficulty = bh.TotalDifficulty, + AuRaStep = bh.AuRaStep, + AuRaSignature = bh.AuRaSignature, + ReceiptsRoot = bh.ReceiptsRoot, + BaseFeePerGas = bh.BaseFeePerGas, + WithdrawalsRoot = bh.WithdrawalsRoot, + RequestsHash = bh.RequestsHash, + IsPostMerge = bh.IsPostMerge, + ParentBeaconBlockRoot = bh.ParentBeaconBlockRoot, + ExcessBlobGas = bh.ExcessBlobGas, + BlobGasUsed = bh.BlobGasUsed, + Validator = bh.Validator, + Validators = bh.Validators, + Penalties = bh.Penalties, + }; + + if (!ShouldComputeStateRoot(bh)) + { + headerForProcessing.StateRoot = bh.StateRoot; + } + + return suggestedBlock.WithReplacedHeader(headerForProcessing); + } +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs b/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs new file mode 100644 index 000000000000..aa9e502f41b3 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcBlockProducer.cs @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Config; +using Nethermind.Consensus; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Producers; +using Nethermind.Consensus.Transactions; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Evm.State; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Xdc.RLP; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Types; +using System; + +namespace Nethermind.Xdc; + +internal class XdcBlockProducer : BlockProducerBase +{ + protected readonly IEpochSwitchManager epochSwitchManager; + protected readonly ISnapshotManager snapshotManager; + protected readonly IXdcConsensusContext xdcContext; + protected readonly ISealer sealer; + protected readonly ISpecProvider specProvider; + private static readonly ExtraConsensusDataDecoder _extraConsensusDataDecoder = new(); + + public XdcBlockProducer(IEpochSwitchManager epochSwitchManager, ISnapshotManager snapshotManager, IXdcConsensusContext xdcContext, ITxSource txSource, IBlockchainProcessor processor, ISealer sealer, IBlockTree blockTree, IWorldState stateProvider, IGasLimitCalculator? gasLimitCalculator, ITimestamper? timestamper, ISpecProvider specProvider, ILogManager logManager, IDifficultyCalculator? difficultyCalculator, IBlocksConfig? blocksConfig) : base(txSource, processor, sealer, blockTree, stateProvider, gasLimitCalculator, timestamper, specProvider, logManager, difficultyCalculator, blocksConfig) + { + this.epochSwitchManager = epochSwitchManager; + this.snapshotManager = snapshotManager; + this.xdcContext = xdcContext; + this.sealer = sealer; + this.specProvider = specProvider; + } + + protected override BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAttributes payloadAttributes) + { + if (parent is not XdcBlockHeader xdcParent) + throw new ArgumentException("Only XDC header are supported."); + + QuorumCertificate highestCert = xdcContext.HighestQC; + var currentRound = xdcContext.CurrentRound; + + //TODO maybe some sanity checks here for round and hash + + byte[] extra = [XdcConstants.ConsensusVersion, .. _extraConsensusDataDecoder.Encode(new ExtraFieldsV2(currentRound, highestCert)).Bytes]; + + Address blockAuthor = sealer.Address; + long gasLimit = GasLimitCalculator.GetGasLimit(parent); + XdcBlockHeader xdcBlockHeader = new( + parent.Hash!, + Keccak.OfAnEmptySequenceRlp, + blockAuthor, + UInt256.Zero, + parent.Number + 1, + gasLimit, + 0, + extra); + + IXdcReleaseSpec spec = specProvider.GetXdcSpec(xdcBlockHeader, currentRound); + + xdcBlockHeader.Timestamp = payloadAttributes?.Timestamp ?? parent.Timestamp + (ulong)spec.MinePeriod; + + xdcBlockHeader.Difficulty = 1; + + xdcBlockHeader.TotalDifficulty = xdcParent.TotalDifficulty + 1; + + xdcBlockHeader.BaseFeePerGas = BaseFeeCalculator.Calculate(parent, spec); + + xdcBlockHeader.MixHash = Hash256.Zero; + + if (epochSwitchManager.IsEpochSwitchAtBlock(xdcBlockHeader)) + { + (Address[] masternodes, Address[] penalties) = snapshotManager.CalculateNextEpochMasternodes(xdcBlockHeader.Number, xdcBlockHeader.ParentHash, spec); + xdcBlockHeader.Validators = new byte[masternodes.Length * Address.Size]; + + for (int i = 0; i < masternodes.Length; i++) + { + Array.Copy(masternodes[i].Bytes, 0, xdcBlockHeader.Validators, i * Address.Size, Address.Size); + } + + xdcBlockHeader.Penalties = new byte[penalties.Length * Address.Size]; + + for (int i = 0; i < penalties.Length; i++) + { + Array.Copy(penalties[i].Bytes, 0, xdcBlockHeader.Penalties, i * Address.Size, Address.Size); + } + } + return xdcBlockHeader; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcBlockStore.cs b/src/Nethermind/Nethermind.Xdc/XdcBlockStore.cs new file mode 100644 index 000000000000..e0b84f111aa7 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcBlockStore.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Autofac.Features.AttributeFilters; +using Nethermind.Blockchain.Blocks; +using Nethermind.Db; + +namespace Nethermind.Xdc; + +internal class XdcBlockStore([KeyFilter(DbNames.Blocks)] IDb blockDb) : BlockStore(blockDb, new XdcHeaderDecoder()) +{ +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcBlockSuggester.cs b/src/Nethermind/Nethermind.Xdc/XdcBlockSuggester.cs new file mode 100644 index 000000000000..f39913bf456f --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcBlockSuggester.cs @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Consensus; +using Nethermind.Consensus.Producers; +using Nethermind.Core; + +namespace Nethermind.Xdc; + +internal class XdcBlockSuggester : IProducedBlockSuggester +{ + private readonly IBlockTree _blockTree; + private readonly IBlockProducerRunner _blockProducerRunner; + + //TODO consider getting rid of this entirely and suggest the block directly when building in HotStuff module + public XdcBlockSuggester(IBlockTree blockTree, IBlockProducerRunner blockProducer) + { + _blockTree = blockTree; + _blockProducerRunner = blockProducer; + _blockProducerRunner.BlockProduced += OnBlockProduced; + } + + private void OnBlockProduced(object? sender, BlockEventArgs e) + { + _blockTree.SuggestBlock(e.Block); + } + + public void Dispose() => _blockProducerRunner.BlockProduced -= OnBlockProduced; +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcBlockTree.cs b/src/Nethermind/Nethermind.Xdc/XdcBlockTree.cs new file mode 100644 index 000000000000..1037e1b6de5c --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcBlockTree.cs @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Autofac.Features.AttributeFilters; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Blocks; +using Nethermind.Blockchain.Headers; +using Nethermind.Blockchain.Synchronization; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Db; +using Nethermind.Db.Blooms; +using Nethermind.Logging; +using Nethermind.State.Repositories; +using Nethermind.Xdc.Types; + +namespace Nethermind.Xdc; + +internal class XdcBlockTree : BlockTree +{ + private const int MaxSearchDepth = 1024; + private readonly IXdcConsensusContext _xdcConsensus; + + public XdcBlockTree( + IXdcConsensusContext xdcConsensus, + IBlockStore? blockStore, + IHeaderStore? headerDb, + [KeyFilter("blockInfos")] IDb? blockInfoDb, + [KeyFilter("metadata")] IDb? metadataDb, + IBadBlockStore? badBlockStore, + IChainLevelInfoRepository? chainLevelInfoRepository, + ISpecProvider? specProvider, + IBloomStorage? bloomStorage, + ISyncConfig? syncConfig, + ILogManager? logManager, + long genesisBlockNumber = 0) : base(blockStore, headerDb, blockInfoDb, metadataDb, badBlockStore, chainLevelInfoRepository, specProvider, bloomStorage, syncConfig, logManager, genesisBlockNumber) + { + _xdcConsensus = xdcConsensus; + } + + protected override AddBlockResult Suggest(Block? block, BlockHeader header, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) + { + BlockRoundInfo finalizedBlockInfo = _xdcConsensus.HighestCommitBlock; + if (finalizedBlockInfo is null) + return base.Suggest(block, header, options); + if (finalizedBlockInfo.Hash == header.Hash) + { + //Weird case if re-suggesting the finalized block + return AddBlockResult.AlreadyKnown; + } + if (finalizedBlockInfo.BlockNumber >= header.Number) + { + return AddBlockResult.InvalidBlock; + } + if (header.Number - finalizedBlockInfo.BlockNumber > MaxSearchDepth) + { + //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; + } + BlockHeader current = header; + for (long i = header.Number; i >= finalizedBlockInfo.BlockNumber; i--) + { + if (finalizedBlockInfo.BlockNumber >= current.Number) + return AddBlockResult.InvalidBlock; + + if (finalizedBlockInfo.Hash == current.ParentHash) + return base.Suggest(block, header, options); + + current = FindHeader(current.ParentHash, BlockTreeLookupOptions.TotalDifficultyNotNeeded | BlockTreeLookupOptions.DoNotCreateLevelIfMissing); + if (current is null) + return AddBlockResult.UnknownParent; + } + //This is not possible to reach + return AddBlockResult.InvalidBlock; + } + +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcChainSpecLoader.cs b/src/Nethermind/Nethermind.Xdc/XdcChainSpecLoader.cs new file mode 100644 index 000000000000..a593754ea646 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcChainSpecLoader.cs @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Specs.ChainSpecStyle; + +namespace Nethermind.Xdc; + +public static class XdcChainSpecLoader +{ + public static void ProcessChainSpec(ChainSpec chainSpec) + { + if (chainSpec.Genesis is not null) + { + Block originalGenesis = chainSpec.Genesis; + chainSpec.Genesis = originalGenesis.WithReplacedHeader( + XdcBlockHeader.FromBlockHeader(originalGenesis.Header) + ); + } + } +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcConsensusContext.cs b/src/Nethermind/Nethermind.Xdc/XdcConsensusContext.cs new file mode 100644 index 000000000000..a9a8a764394a --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcConsensusContext.cs @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; +using Nethermind.Xdc.Types; +using System; +using System.Threading; + +namespace Nethermind.Xdc; + +public class XdcConsensusContext : IXdcConsensusContext +{ + private ulong _currentRound; + + public XdcConsensusContext() + { + HighestQC = new QuorumCertificate(new BlockRoundInfo(Hash256.Zero, 0, 0), [], 0); + } + + public DateTime RoundStarted { get; private set; } + public int TimeoutCounter { get; set; } + public ulong CurrentRound { get => _currentRound; set => _currentRound = value; } + public QuorumCertificate? HighestQC { get; set; } + public QuorumCertificate? LockQC { get; set; } + public TimeoutCertificate? HighestTC { get; set; } + public BlockRoundInfo HighestCommitBlock { get; set; } + + public event EventHandler NewRoundSetEvent; + + public void SetNewRound() => SetNewRound(Interlocked.Increment(ref _currentRound)); + public void SetNewRound(ulong round) + { + int previousTimeoutCounter = TimeoutCounter; + CurrentRound = round; + TimeoutCounter = 0; + RoundStarted = DateTime.UtcNow; + + // timer should be reset outside + NewRoundSetEvent?.Invoke(this, new NewRoundEventArgs(round, previousTimeoutCounter)); + } +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcConstants.cs b/src/Nethermind/Nethermind.Xdc/XdcConstants.cs index b12d5aa348f2..845f9a0b20e8 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcConstants.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcConstants.cs @@ -1,30 +1,32 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using Nethermind.Int256; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Nethermind.Xdc; internal static class XdcConstants { - public static readonly ulong EpochLength = 900UL; // Default number of blocks after which to checkpoint and reset the pending votes + public const ulong EpochLength = 900UL; // Default number of blocks after which to checkpoint and reset the pending votes - public static readonly int ExtraVanity = 32; // Fixed number of extra-data prefix bytes reserved for signer vanity - public static readonly int ExtraSeal = 65; // Fixed number of extra-data suffix bytes reserved for signer seal + public const int ExtraVanity = 32; // Fixed number of extra-data prefix bytes reserved for signer vanity + public const int ExtraSeal = 65; // Fixed number of extra-data suffix bytes reserved for signer seal public const ulong NonceAuthVoteValue = 0xffffffffffffffff; // Magic nonce number to vote on adding a new signer public const ulong NonceDropVoteValue = 0x0000000000000000; // Magic nonce number to vote on removing a signer - public static readonly Hash256 UncleHash = Keccak.OfAnEmptySequenceRlp; // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW - public static readonly ulong InMemoryEpochs = 5 * 900UL; // Number of mapping from block to epoch switch infos to keep in memory + public const int InMemoryEpochs = 5 * 900; // Number of mapping from block to epoch switch infos to keep in memory + + public const int InMemoryRound2Epochs = 65536; // One epoch ~ 0.5h, 65536 epochs ~ 3.7y, ~10MB memory + + public const long DefaultTargetGasLimit = 84000000; // XDC default gas limit per block - public static readonly int InMemoryRound2Epochs = 65536; // One epoch ~ 0.5h, 65536 epochs ~ 3.7y, ~10MB memory + public const byte ConsensusVersion = 0x02; + + public const int GasLimitBoundDivisor = 1024; // The bound divisor of gas limit adjustment per block // --- Compile-time constants --- public const int InMemorySnapshots = 128; // Number of recent vote snapshots to keep in memory @@ -33,7 +35,17 @@ internal static class XdcConstants public const int PeriodicJobPeriod = 60; public const int PoolHygieneRound = 10; - - public static UInt256 DifficultyDefault = UInt256.One; public const int InMemorySignatures = 4096; + + public static readonly Hash256 UncleHash = Keccak.OfAnEmptySequenceRlp; // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW + public static readonly UInt256 DifficultyDefault = UInt256.One; + + + public static readonly byte[] SetSecret = Bytes.FromHexString("34d38600"); + public static readonly byte[] SetOpening = Bytes.FromHexString("e11f5ba2"); + public static readonly byte[] VoteMethod = Bytes.FromHexString("0x6dd7d8ea"); + public static readonly byte[] UnvoteMethod = Bytes.FromHexString("0x02aa9be2"); + public static readonly byte[] ProposeMethod = Bytes.FromHexString("0x01267951"); + public static readonly byte[] ResignMethod = Bytes.FromHexString("0xae6e43f5"); + public static readonly byte[] SignMethod = Bytes.FromHexString("0xe341eaa4"); } diff --git a/src/Nethermind/Nethermind.Xdc/XdcExtensions.cs b/src/Nethermind/Nethermind.Xdc/XdcExtensions.cs index 7bf8556c1a62..2390412c9203 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcExtensions.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcExtensions.cs @@ -1,31 +1,37 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Nethermind.Blockchain; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Crypto; using Nethermind.Serialization.Rlp; -using Nethermind.Xdc; using Nethermind.Xdc.Spec; using Nethermind.Xdc.Types; using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Immutable; namespace Nethermind.Xdc; -public static class XdcExtensions + +internal static partial class XdcExtensions { //TODO can we wire up this so we can use Rlp.Encode()? - private static XdcHeaderDecoder _headerDecoder = new(); + private static readonly XdcHeaderDecoder _headerDecoder = new(); + private static readonly VoteDecoder _voteDecoder = new(); public static Signature Sign(this IEthereumEcdsa ecdsa, PrivateKey privateKey, XdcBlockHeader header) { ValueHash256 hash = ValueKeccak.Compute(_headerDecoder.Encode(header, RlpBehaviors.ForSealing).Bytes); return ecdsa.Sign(privateKey, in hash); } + public static Address RecoverVoteSigner(this IEthereumEcdsa ecdsa, Vote vote) + { + KeccakRlpStream stream = new(); + //TODO this could be optimized to encoding directly to KeccakRlpStream to avoid several allocation + _voteDecoder.Encode(stream, vote, RlpBehaviors.ForSealing); + ValueHash256 hash = stream.GetValueHash(); + return ecdsa.RecoverAddress(vote.Signature, in hash); + } - //TODO round parameter is given a default value since this function is being used in places where the current round is not known public static IXdcReleaseSpec GetXdcSpec(this ISpecProvider specProvider, XdcBlockHeader xdcBlockHeader, ulong round = 0) { IXdcReleaseSpec spec = specProvider.GetSpec(xdcBlockHeader) as IXdcReleaseSpec; @@ -35,31 +41,52 @@ public static IXdcReleaseSpec GetXdcSpec(this ISpecProvider specProvider, XdcBlo return spec; } - public static Snapshot? GetSnapshotByHeader(this ISnapshotManager snapshotManager, XdcBlockHeader? header) + public static IXdcReleaseSpec GetXdcSpec(this ISpecProvider specProvider, long blockNumber, ulong round = 0) { - if (header is null) - return null; - return snapshotManager.GetSnapshot(header.Hash); + IXdcReleaseSpec spec = specProvider.GetSpec(blockNumber, null) as IXdcReleaseSpec; + if (spec is null) + throw new InvalidOperationException($"Expected {nameof(IXdcReleaseSpec)}."); + spec.ApplyV2Config(round); + return spec; } - public static Snapshot? GetSnapshotByHeaderNumber(this ISnapshotManager snapshotManager, IBlockTree tree, ulong number, ulong xdcEpoch, ulong xdcGap) + public static ImmutableArray
? ExtractAddresses(this Span data) { - ulong gapBlockNum = Math.Max(0, number - number % xdcEpoch - xdcGap); + if (data.Length % Address.Size != 0) + return null; - return snapshotManager.GetSnapshotByGapNumber(tree, gapBlockNum); + Address[] addresses = new Address[data.Length / Address.Size]; + for (int i = 0; i < addresses.Length; i++) + { + addresses[i] = new Address(data.Slice(i * Address.Size, Address.Size)); + } + return addresses.ToImmutableArray(); } + public static bool ValidateBlockInfo(this BlockRoundInfo blockInfo, XdcBlockHeader blockHeader) => + (blockInfo.BlockNumber == blockHeader.Number) + && (blockInfo.Hash == blockHeader.Hash) + && (blockInfo.Round == blockHeader.ExtraConsensusData.BlockRound); - public static Snapshot? GetSnapshotByGapNumber(this ISnapshotManager snapshotManager, IBlockTree tree, ulong gapBlockNum) + public static Signature DecodeSignature(this ref Rlp.ValueDecoderContext decoderContext) { - Hash256 gapBlockHash = tree.FindHeader((long)gapBlockNum)?.Hash; - - if (gapBlockHash is null) - return null; - - return snapshotManager.GetSnapshot(gapBlockHash); + //includes the list prefix, which is 2 bytes for a 65 byte signature + ReadOnlySpan sigBytes = decoderContext.PeekNextItem(); + if (sigBytes.Length != Signature.Size + 2) + throw new RlpException($"Invalid signature length in '{nameof(Vote)}'"); + Signature signature = new Signature(sigBytes.Slice(2, 64), sigBytes[66]); + decoderContext.SkipItem(); + return signature; } - - + public static Signature DecodeSignature(this RlpStream stream) + { + //includes the list prefix, which is 2 bytes for a 65 byte signature + ReadOnlySpan sigBytes = stream.PeekNextItem(); + if (sigBytes.Length != Signature.Size + 2) + throw new RlpException($"Invalid signature length in '{nameof(Vote)}'"); + Signature signature = new Signature(sigBytes.Slice(2, 64), sigBytes[66]); + stream.SkipItem(); + return signature; + } } diff --git a/src/Nethermind/Nethermind.Xdc/XdcGasLimitCalculator.cs b/src/Nethermind/Nethermind.Xdc/XdcGasLimitCalculator.cs new file mode 100644 index 000000000000..f40892f00e22 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcGasLimitCalculator.cs @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Config; +using Nethermind.Consensus; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Xdc.Spec; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Nethermind.Xdc; + +internal class XdcGasLimitCalculator(ISpecProvider specProvider, IBlocksConfig blocksConfig) : IGasLimitCalculator +{ + private readonly TargetAdjustedGasLimitCalculator targetAdjustedGasLimitCalculator = new TargetAdjustedGasLimitCalculator(specProvider, blocksConfig); + public long GetGasLimit(BlockHeader parentHeader) + { + IXdcReleaseSpec spec = specProvider.GetXdcSpec(parentHeader.Number + 1); + if (spec.IsDynamicGasLimitBlock) + { + return targetAdjustedGasLimitCalculator.GetGasLimit(parentHeader); + } + return blocksConfig.TargetBlockGasLimit ?? XdcConstants.DefaultTargetGasLimit; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcGenesisBuilder.cs b/src/Nethermind/Nethermind.Xdc/XdcGenesisBuilder.cs new file mode 100644 index 000000000000..627da74e55f3 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcGenesisBuilder.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Xdc.Spec; + +namespace Nethermind.Xdc; + +public class XdcGenesisBuilder( + IGenesisBuilder genesisBuilder, + ISpecProvider specProvider, + ISnapshotManager snapshotManager +) : IGenesisBuilder +{ + public Block Build() + { + Block builtBlock = genesisBuilder.Build(); + + var finalSpec = (IXdcReleaseSpec)specProvider.GetFinalSpec(); + snapshotManager.StoreSnapshot(new Types.Snapshot(builtBlock.Number, builtBlock.Hash!, finalSpec.GenesisMasterNodes)); + + return builtBlock; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcHeaderStore.cs b/src/Nethermind/Nethermind.Xdc/XdcHeaderStore.cs new file mode 100644 index 000000000000..f99452a23de1 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcHeaderStore.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Autofac.Features.AttributeFilters; +using Nethermind.Blockchain.Headers; +using Nethermind.Db; + +namespace Nethermind.Xdc; + +internal class XdcHeaderStore([KeyFilter(DbNames.Headers)] IDb headerDb, [KeyFilter(DbNames.BlockNumbers)] IDb blockNumberDb) : HeaderStore(headerDb, blockNumberDb, new XdcHeaderDecoder()), IXdcHeaderStore +{ +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcHeaderValidator.cs b/src/Nethermind/Nethermind.Xdc/XdcHeaderValidator.cs index a27f13914255..b683eda19175 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcHeaderValidator.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcHeaderValidator.cs @@ -7,19 +7,22 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; -using Nethermind.Crypto; using Nethermind.Logging; using Nethermind.Xdc.Types; using System; namespace Nethermind.Xdc; -public class XdcHeaderValidator(IBlockTree blockTree, ISealValidator sealValidator, ISpecProvider specProvider, ILogManager? logManager = null) : HeaderValidator(blockTree, sealValidator, specProvider, logManager) +public class XdcHeaderValidator(IBlockTree blockTree, IQuorumCertificateManager quorumCertificateManager, ISealValidator sealValidator, ISpecProvider specProvider, ILogManager? logManager = null) : HeaderValidator(blockTree, sealValidator, specProvider, logManager) { - protected override bool Validate(BlockHeader header, BlockHeader? parent, bool isUncle, out string? error) + protected override bool Validate(BlockHeader header, BlockHeader parent, bool isUncle, out string? error) { + if (parent is null) + throw new ArgumentNullException(nameof(parent)); if (header is not XdcBlockHeader xdcHeader) throw new ArgumentException($"Only type of {nameof(XdcBlockHeader)} is allowed, but got type {header.GetType().Name}.", nameof(header)); + if (parent is not XdcBlockHeader parentXdcHeader) + throw new ArgumentException($"Only type of {nameof(XdcBlockHeader)} is allowed, but got type {parent.GetType().Name}.", nameof(parent)); if (xdcHeader.Validator is null || xdcHeader.Validator.Length == 0) { @@ -34,7 +37,10 @@ protected override bool Validate(BlockHeader header, BlockHeader? par return false; } - //TODO verify QC + if (!quorumCertificateManager.VerifyCertificate(extraFields.QuorumCert, parentXdcHeader, out error)) + { + return false; + } if (xdcHeader.Nonce != XdcConstants.NonceDropVoteValue && xdcHeader.Nonce != XdcConstants.NonceAuthVoteValue) { @@ -65,19 +71,20 @@ protected override bool Validate(BlockHeader header, BlockHeader? par protected override bool ValidateSeal(BlockHeader header, BlockHeader parent, bool isUncle, ref string? error) { - if (_sealValidator is XdcSealValidator xdcSealValidator) - return xdcSealValidator.ValidateParams(header, parent, out error); - - if (!_sealValidator.ValidateParams(parent, header, isUncle)) + if (!_sealValidator.ValidateSeal(header, false)) { - error = "Invalid consensus data in header."; + error = "Invalid validator signature."; return false; } - if (!_sealValidator.ValidateSeal(header, false)) + + if (_sealValidator is XdcSealValidator xdcSealValidator ? + !xdcSealValidator.ValidateParams(parent, header, out error) : + !_sealValidator.ValidateParams(parent, header, isUncle)) { - error = "Invalid validator signature."; + error = "Invalid consensus data in header."; return false; } + return true; } @@ -88,10 +95,10 @@ protected override bool ValidateTotalDifficulty(BlockHeader header, BlockHeader { if (header.Difficulty != 1) { - error = "Total difficulty must be 1."; + error = "Difficulty must be 1."; return false; } - return true; + return base.ValidateTotalDifficulty(header, parent, ref error); } protected override bool ValidateTimestamp(BlockHeader header, BlockHeader parent, ref string? error) diff --git a/src/Nethermind/Nethermind.Xdc/XdcHotStuff.cs b/src/Nethermind/Nethermind.Xdc/XdcHotStuff.cs new file mode 100644 index 000000000000..0c3ef38be0eb --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcHotStuff.cs @@ -0,0 +1,495 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Config; +using Nethermind.Consensus; +using Nethermind.Consensus.Producers; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Logging; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Types; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Nethermind.Xdc +{ + /// + /// This runner orchestrates the consensus loop: leader block proposal, voting, QC aggregation, + /// timeout handling, and 3-chain finalization + /// + internal class XdcHotStuff : IBlockProducerRunner + { + private readonly IBlockTree _blockTree; + private readonly IXdcConsensusContext _xdcContext; + private readonly ISpecProvider _specProvider; + private readonly IBlockProducer _blockBuilder; + private readonly IEpochSwitchManager _epochSwitchManager; + private readonly ISnapshotManager _snapshotManager; + private readonly IQuorumCertificateManager _quorumCertificateManager; + private readonly IVotesManager _votesManager; + private readonly ISigner _signer; + private readonly ITimeoutTimer _timeoutTimer; + private readonly IProcessExitSource _processExit; + private readonly ILogger _logger; + private readonly ISignTransactionManager _signTransactionManager; + + private CancellationTokenSource? _cancellationTokenSource; + private Task? _runTask; + private DateTime _lastActivityTime; + private readonly object _lockObject = new(); + + public event EventHandler? BlockProduced; + + private static readonly PayloadAttributes DefaultPayloadAttributes = new PayloadAttributes(); + private ulong _highestSelfMinedRound; + private ulong _highestVotedRound; + + + public XdcHotStuff( + IBlockTree blockTree, + IXdcConsensusContext xdcContext, + ISpecProvider specProvider, + IBlockProducer blockBuilder, + IEpochSwitchManager epochSwitchManager, + ISnapshotManager snapshotManager, + IQuorumCertificateManager quorumCertificateManager, + IVotesManager votesManager, + ISigner signer, + ITimeoutTimer timeoutTimer, + IProcessExitSource processExit, + ISignTransactionManager signTransactionManager, + ILogManager logManager) + { + _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); + _xdcContext = xdcContext; + _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); + _blockBuilder = blockBuilder ?? throw new ArgumentNullException(nameof(blockBuilder)); + _epochSwitchManager = epochSwitchManager ?? throw new ArgumentNullException(nameof(epochSwitchManager)); + _snapshotManager = snapshotManager; + _quorumCertificateManager = quorumCertificateManager ?? throw new ArgumentNullException(nameof(quorumCertificateManager)); + _votesManager = votesManager ?? throw new ArgumentNullException(nameof(votesManager)); + _signer = signer ?? throw new ArgumentNullException(nameof(signer)); + _signTransactionManager = signTransactionManager ?? throw new ArgumentNullException(nameof(signTransactionManager)); + _timeoutTimer = timeoutTimer; + _processExit = processExit; + _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + + _lastActivityTime = DateTime.UtcNow; + } + + /// + /// Starts the consensus runner. + /// + public void Start() + { + lock (_lockObject) + { + if (_cancellationTokenSource != null) + { + _logger.Info("XdcHotStuff already started, ignoring duplicate Start() call"); + return; + } + + _cancellationTokenSource = new CancellationTokenSource(); + + _processExit.Token.Register(() => + { + _logger.Info("Process exit detected, stopping XdcHotStuff consensus runner..."); + _cancellationTokenSource?.Cancel(); + }); + + _runTask = Run(); + _logger.Info("XdcHotStuff consensus runner started"); + } + } + + /// + /// Main execution loop - waits for blockchain initialization then starts consensus. + /// + private async Task Run() + { + try + { + await WaitForBlockTreeHead(_cancellationTokenSource!.Token); + + _blockTree.NewHeadBlock += OnNewHeadBlock; + + _xdcContext.NewRoundSetEvent += OnNewRound; + + // Initialize round from head + InitializeRoundFromHead(); + + // Main consensus flow + await MainFlow(); + } + catch (OperationCanceledException) + { + _logger.Info("XdcHotStuff consensus runner stopped"); + } + catch (Exception ex) + { + _logger.Error("XdcHotStuff consensus runner encountered fatal error", ex); + throw; + } + finally + { + _blockTree.NewHeadBlock -= OnNewHeadBlock; + _xdcContext.NewRoundSetEvent -= OnNewRound; + } + } + + private void OnNewRound(object sender, NewRoundEventArgs args) + { + XdcBlockHeader head = _blockTree.Head.Header as XdcBlockHeader + ?? throw new InvalidOperationException("BlockTree head is not XdcBlockHeader."); + //TODO Should this use be previous round ? + IXdcReleaseSpec spec = _specProvider.GetXdcSpec(head, args.NewRound); + + //TODO Technically we have to apply timeout exponents from spec, but they are always 1 + _timeoutTimer.Reset(TimeSpan.FromSeconds(spec.TimeoutPeriod)); + + TimeSpan roundDuration = DateTime.UtcNow - _xdcContext.RoundStarted; + _logger.Info($"Round {args.NewRound} completed in {roundDuration.TotalSeconds:F2}s"); + } + + /// + /// Wait for blockTree.Head to become non-null during bootstrap. + /// + private async Task WaitForBlockTreeHead(CancellationToken cancellationToken) + { + _logger.Debug("Waiting for blockTree.Head to initialize..."); + while (_blockTree.Head == null) + { + await Task.Delay(100, cancellationToken); + } + _logger.Debug($"BlockTree initialized with head at block #{_blockTree.Head.Number}"); + } + + /// + /// Initialize RoundCount from the current head's ExtraConsensusData.BlockRound. + /// + private void InitializeRoundFromHead() + { + if (_blockTree.Head.Header is not XdcBlockHeader xdcHead) + throw new InvalidBlockException(_blockTree.Head, "Head is not XdcBlockHeader."); + + _quorumCertificateManager.Initialize(xdcHead); + _logger.Info($"Initialized round {_xdcContext.CurrentRound} from head."); + } + + /// + /// Main consensus flow + /// + private async Task MainFlow() + { + CancellationToken ct = _cancellationTokenSource!.Token; + + while (!ct.IsCancellationRequested) + { + try + { + await RunRoundChecks(ct); + await Task.Delay(50, ct); + } + catch (OperationCanceledException) + { + throw; + } + catch (InvalidOperationException) + { + throw; + } + catch (Exception ex) + { + _logger.Error($"Error processing round {_xdcContext.CurrentRound}", ex); + } + } + } + + /// + /// Run checks for the current round: leader proposal, voting, timeout handling. + /// + internal async Task RunRoundChecks(CancellationToken ct) + { + ulong currentRound = _xdcContext.CurrentRound; + + XdcBlockHeader? roundParent = GetParentForRound(); + if (roundParent == null) + { + throw new InvalidOperationException($"Head is null or not XdcBlockHeader."); + } + + // Get XDC spec for this round + IXdcReleaseSpec spec = _specProvider.GetXdcSpec(roundParent, currentRound); + if (spec == null) + { + _logger.Error($"Round {currentRound}: Failed to get XDC spec, skipping"); + return; + } + + // Get epoch info and check for epoch switch + EpochSwitchInfo epochInfo = _epochSwitchManager.GetEpochSwitchInfo(roundParent); + if (epochInfo?.Masternodes == null || epochInfo.Masternodes.Length == 0) + { + _logger.Warn($"Round {currentRound}: No masternodes in epoch, skipping"); + return; + } + + bool isMyTurn = IsMyTurnAndTime(roundParent, currentRound, spec); + _logger.Info($"Round {currentRound}: Leader={GetLeaderAddress(roundParent, currentRound, spec)}, MyTurn={isMyTurn}, Committee={epochInfo.Masternodes.Length} nodes"); + + if (isMyTurn) + { + _highestSelfMinedRound = currentRound; + Task blockBuilder = BuildAndProposeBlock(roundParent, currentRound, spec, ct); + + } + + if (IsMasternode(epochInfo, _signer.Address) + && ((roundParent.Number % spec.MergeSignRange == 0))) + { + await _signTransactionManager.SubmitTransactionSign(roundParent, spec); + } + + if (spec.SwitchBlock < roundParent.Number) + { + await CommitCertificateAndVote(roundParent, epochInfo); + } + + } + + private XdcBlockHeader GetParentForRound() + { + return _blockTree.Head.Header as XdcBlockHeader; + } + + /// + /// Build block with parentQC. + /// + internal async Task BuildAndProposeBlock(XdcBlockHeader parent, ulong currentRound, IXdcReleaseSpec spec, CancellationToken ct) + { + DateTime now = DateTime.UtcNow; + + try + { + ulong parentTimestamp = parent.Timestamp; + ulong minTimestamp = parentTimestamp + (ulong)spec.MinePeriod; + ulong currentTimestamp = (ulong)new DateTimeOffset(now).ToUnixTimeSeconds(); + + _logger.Debug($"Round {currentRound}: Building proposal block"); + + DefaultPayloadAttributes.Timestamp = minTimestamp; + + if (currentTimestamp < minTimestamp) + { + TimeSpan delay = TimeSpan.FromSeconds(minTimestamp - currentTimestamp); + _logger.Debug($"Round {currentRound}: Waiting {delay.TotalSeconds:F1}s for minimum mining time"); + // Enforce minimum mining time per XDC rules + await Task.Delay(delay, ct); + } + + Task proposedBlockTask = + _blockBuilder.BuildBlock(parent, null, DefaultPayloadAttributes, IBlockProducer.Flags.None, ct); + + Block? proposedBlock = await proposedBlockTask; + + if (proposedBlock != null) + { + _lastActivityTime = DateTime.UtcNow; + _logger.Info($"Round {currentRound}: Block #{proposedBlock.Number} built successfully, hash={proposedBlock.Hash}"); + + // This will trigger broadcasting the block via P2P + BlockProduced?.Invoke(this, new BlockEventArgs(proposedBlock)); + } + else + { + _logger.Warn($"Round {currentRound}: Block builder returned null"); + } + } + catch (Exception ex) + { + _logger.Error($"Failed to build block in round {currentRound}", ex); + } + } + + /// + /// Voter path - commit received QC, check voting rule, cast vote. + /// + private async Task CommitCertificateAndVote(XdcBlockHeader head, EpochSwitchInfo epochInfo) + { + if (head.ExtraConsensusData?.QuorumCert is null) + throw new InvalidOperationException("Head block missing consensus data."); + var votingRound = head.ExtraConsensusData.BlockRound; + if (_highestVotedRound >= votingRound) + return; + + // Commit/record the header's QC + _quorumCertificateManager.CommitCertificate(head.ExtraConsensusData.QuorumCert); + + _highestVotedRound = votingRound; + + // Check if we are in the masternode set + if (!IsMasternode(epochInfo, _signer.Address)) + { + _logger.Info($"Round {votingRound}: Skipped voting (not in masternode set)"); + return; + } + + // Check voting rule + bool canVote = _votesManager.VerifyVotingRules(head); + if (!canVote) + { + _logger.Info($"Round {votingRound}: Voting rule not satisfied for block #{head.Number}, hash={head.Hash}"); + return; + } + + try + { + BlockRoundInfo voteInfo = new BlockRoundInfo(head.Hash!, head.ExtraConsensusData.BlockRound, head.Number); + await _votesManager.CastVote(voteInfo); + _lastActivityTime = DateTime.UtcNow; + _logger.Info($"Round {votingRound}: Voted for block #{head.Number}, hash={head.Hash}"); + } + catch (Exception ex) + { + _logger.Error($"Round {votingRound}: Failed to cast vote.", ex); + } + } + + /// + /// Handler for blockTree.NewHeadBlock event - signals new round on head changes. + /// + private void OnNewHeadBlock(object? sender, BlockEventArgs e) + { + if (e.Block.Header is not XdcBlockHeader xdcHead) + throw new InvalidOperationException($"Expected an XDC header, but got {e.Block.Header.GetType().FullName}"); + + _logger.Debug($"New head block #{xdcHead.Number}, round={xdcHead.ExtraConsensusData?.BlockRound}"); + + if (xdcHead.ExtraConsensusData is null) + throw new InvalidOperationException("New head block missing ExtraConsensusData"); + + ulong headRound = xdcHead.ExtraConsensusData.BlockRound; + if (headRound > _xdcContext.CurrentRound) + { + _logger.Warn($"New head block round is ahead of us."); + //TODO This should probably trigger a sync + } + + // Signal new round + _lastActivityTime = DateTime.UtcNow; + } + + /// + /// Check if the current node is the leader for the given round. + /// Uses epoch switch manager and spec to determine leader via round-robin rotation. + /// + private bool IsMyTurnAndTime(XdcBlockHeader parent, ulong round, IXdcReleaseSpec spec) + { + if (_highestSelfMinedRound >= round) + { + //Already produced block for this round + return false; + } + + if ((long)parent.Timestamp + spec.MinePeriod > DateTimeOffset.UtcNow.ToUnixTimeSeconds()) + { + //Not enough time has passed since last block + return false; + } + + if (parent.Hash != _xdcContext.HighestQC.ProposedBlockInfo.Hash) + { + //We have not reached QC vote threshold yet + return false; + } + + Address leaderAddress = GetLeaderAddress(parent, round, spec); + return leaderAddress == _signer.Address; + } + + /// + /// Get the leader address for a given round using round-robin rotation. + /// Leader selection: (round % epochLength) % masternodeCount + /// + public Address GetLeaderAddress(XdcBlockHeader currentHead, ulong round, IXdcReleaseSpec spec) + { + Address[] currentMasternodes; + if (_epochSwitchManager.IsEpochSwitchAtRound(round, currentHead)) + { + //TODO calculate master nodes based on the current round + (currentMasternodes, _) = _snapshotManager.CalculateNextEpochMasternodes(currentHead.Number + 1, currentHead.Hash, spec); + } + else + { + var epochSwitchInfo = _epochSwitchManager.GetEpochSwitchInfo(currentHead); + currentMasternodes = epochSwitchInfo.Masternodes; + } + + int currentLeaderIndex = ((int)round % spec.EpochLength % currentMasternodes.Length); + return currentMasternodes[currentLeaderIndex]; + } + + /// + /// Check if the runner is actively producing blocks within the given interval. + /// + public bool IsProducingBlocks(ulong? maxProducingInterval) + { + if (!maxProducingInterval.HasValue) + { + return _cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested; + } + + TimeSpan elapsed = DateTime.UtcNow - _lastActivityTime; + TimeSpan maxInterval = TimeSpan.FromSeconds(maxProducingInterval.Value); + + return elapsed <= maxInterval; + } + + /// + /// Stop the consensus runner gracefully. + /// + public async Task StopAsync() + { + Task? task; + + lock (_lockObject) + { + if (_cancellationTokenSource == null) + { + return; + } + + task = _runTask; + _cancellationTokenSource = null; + _runTask = null; + } + + _logger.Debug("Stopping XdcHotStuff consensus runner..."); + + // Signal cancellation + _cancellationTokenSource?.Cancel(); + // Wait for task completion + if (task != null) + { + try + { + await task; + } + catch (OperationCanceledException) + { + // Expected + } + } + + _cancellationTokenSource?.Dispose(); + _logger.Info("XdcHotStuff consensus runner stopped"); + } + + private static bool IsMasternode(EpochSwitchInfo epochInfo, Address node) => + epochInfo.Masternodes.AsSpan().IndexOf(node) != -1; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcModule.cs b/src/Nethermind/Nethermind.Xdc/XdcModule.cs new file mode 100644 index 000000000000..10286cd98407 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcModule.cs @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +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.Producers; +using Nethermind.Consensus.Validators; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Db; +using Nethermind.Init.Modules; +using Nethermind.Specs.ChainSpecStyle; +using Nethermind.Xdc.Contracts; +using Nethermind.Xdc.Spec; +using Nethermind.TxPool; +using Nethermind.Logging; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Xdc.TxPool; +using Nethermind.Api.Steps; +using Nethermind.Synchronization; +using Nethermind.Synchronization.FastSync; +using Nethermind.Synchronization.ParallelSync; + +namespace Nethermind.Xdc; + +public class XdcModule : Module +{ + private const string SnapshotDbName = "Snapshots"; + + protected override void Load(ContainerBuilder builder) + { + base.Load(builder); + + builder + .AddStep(typeof(InitializeBlockchainXdc)) + .Intercept(XdcChainSpecLoader.ProcessChainSpec) + .AddSingleton() + .Map(chainSpec => + chainSpec.EngineChainSpecParametersProvider.GetChainSpecParameters()) + + .AddDecorator() + .AddScoped() + + // stores + .AddSingleton() + .AddSingleton() + .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 + + // Validators + .AddSingleton() + .AddSingleton() + .AddSingleton() + + // managers + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddDatabase(SnapshotDbName) + .AddSingleton(CreateSnapshotManager) + .AddSingleton(CreateSignTransactionManager) + .AddSingleton() + .AddSingleton() + .AddSingleton() + + // sync + .AddSingleton() + .AddSingleton, XdcStateSyncAllocationStrategyFactory>() + + .AddSingleton() + + // block processing + .AddScoped() + ; + } + + private ISnapshotManager CreateSnapshotManager([KeyFilter(SnapshotDbName)] IDb db, IBlockTree blockTree, IPenaltyHandler penaltyHandler, IMasternodeVotingContract votingContract, ISpecProvider specProvider) + { + return new SnapshotManager(db, blockTree, penaltyHandler, votingContract, specProvider); + } + private ISignTransactionManager CreateSignTransactionManager(ISigner signer, ITxPool txPool, ILogManager logManager) + { + return new SignTransactionManager(signer, txPool, logManager.GetClassLogger()); + } + + 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/XdcPlugin.cs b/src/Nethermind/Nethermind.Xdc/XdcPlugin.cs index 0d16b4e009ff..6e5781fe8eb6 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcPlugin.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcPlugin.cs @@ -1,16 +1,11 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; +using Autofac.Core; using Nethermind.Api.Extensions; using Nethermind.Consensus; -using Nethermind.Consensus.Validators; -using Nethermind.Core; -using Nethermind.Core.Specs; using Nethermind.Specs.ChainSpecStyle; -using Autofac; -using Autofac.Core; -using Nethermind.Xdc.Spec; +using System; namespace Nethermind.Xdc; @@ -21,7 +16,6 @@ public class XdcPlugin(ChainSpec chainSpec) : IConsensusPlugin public string Name => Xdc; public string Description => "Xdc support for Nethermind"; public bool Enabled => chainSpec.SealEngineType == SealEngineType; - public bool MustInitialize => true; public string SealEngineType => Core.SealEngineType.XDPoS; public IModule Module => new XdcModule(); @@ -35,22 +29,3 @@ public IBlockProducer InitBlockProducer() throw new NotSupportedException(); } } - -public class XdcModule : Module -{ - protected override void Load(ContainerBuilder builder) - { - base.Load(builder); - - builder - .AddSingleton() - .Map(chainSpec => - chainSpec.EngineChainSpecParametersProvider.GetChainSpecParameters()) - - // Validators - .AddSingleton() - - ; - } - -} diff --git a/src/Nethermind/Nethermind.Xdc/XdcPool.cs b/src/Nethermind/Nethermind.Xdc/XdcPool.cs new file mode 100644 index 000000000000..a58495fc31cd --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcPool.cs @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Core.Threading; +using System.Collections.Generic; +using System.Linq; + +namespace Nethermind.Xdc; + +public class XdcPool where T : IXdcPoolItem +{ + private readonly Dictionary<(ulong Round, Hash256 Hash), ArrayPoolList> _items = new(); + private readonly McsLock _lock = new(); + + public long Add(T item) + { + using var lockRelease = _lock.Acquire(); + { + var key = item.PoolKey(); + if (!_items.TryGetValue(key, out var list)) + { + //128 should be enough to cover all master nodes and some extras + list = new ArrayPoolList(128); + _items[key] = list; + } + if (!list.Contains(item)) + list.Add(item); + return list.Count; + } + } + + public void EndRound(ulong round) + { + using var lockRelease = _lock.Acquire(); + { + foreach (var key in _items.Keys) + { + if (key.Round <= round && _items.Remove(key, out ArrayPoolList list)) + { + list?.Dispose(); + } + } + } + } + + public IReadOnlyCollection GetItems(T item) + { + using var lockRelease = _lock.Acquire(); + { + var key = item.PoolKey(); + if (_items.TryGetValue(key, out ArrayPoolList list)) + { + //Allocating a new array since it goes outside the lock + return list.ToArray(); + } + return []; + } + } + + public long GetCount(T item) + { + using var lockRelease = _lock.Acquire(); + { + var key = item.PoolKey(); + if (_items.TryGetValue(key, out ArrayPoolList list)) + { + return list.Count; + } + return 0; + } + } +} + +public interface IXdcPoolItem +{ + (ulong Round, Hash256 hash) PoolKey(); +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcRewardCalculator.cs b/src/Nethermind/Nethermind.Xdc/XdcRewardCalculator.cs new file mode 100644 index 000000000000..db8754349841 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcRewardCalculator.cs @@ -0,0 +1,246 @@ +// 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 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 == default || 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
(); + var mergeSignRange = spec.MergeSignRange; + + 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: (totalReward / totalSignatures) * signatureCount + /// + internal 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; + + + UInt256 portion = totalReward / total; + UInt256 reward = portion * signatures; + + return reward; + } + + internal (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.Xdc/XdcSealValidator.cs b/src/Nethermind/Nethermind.Xdc/XdcSealValidator.cs index f39a2f880460..297a3b165d89 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcSealValidator.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcSealValidator.cs @@ -10,19 +10,14 @@ using Nethermind.Xdc.Spec; using Nethermind.Xdc.Types; using System; -using System.Collections.Frozen; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Collections.ObjectModel; using System.Linq; -using static Nethermind.Xdc.XdcExtensions; - namespace Nethermind.Xdc; + internal class XdcSealValidator(ISnapshotManager snapshotManager, IEpochSwitchManager epochSwitchManager, ISpecProvider specProvider) : ISealValidator { - private EthereumEcdsa _ethereumEcdsa = new(0); //Ignore chainId since we don't sign transactions here - private XdcHeaderDecoder _headerDecoder = new(); + private readonly EthereumEcdsa _ethereumEcdsa = new(0); //Ignore chainId since we don't sign transactions here + private readonly XdcHeaderDecoder _headerDecoder = new(); public bool ValidateParams(BlockHeader parent, BlockHeader header, bool isUncle = false) { @@ -41,19 +36,17 @@ public bool ValidateParams(BlockHeader parent, BlockHeader header, out string er ExtraFieldsV2 extraFieldsV2 = xdcHeader.ExtraConsensusData!; - if (extraFieldsV2.CurrentRound <= extraFieldsV2.QuorumCert.ProposedBlockInfo.Round) + if (extraFieldsV2.BlockRound <= extraFieldsV2.QuorumCert.ProposedBlockInfo.Round) { error = "Round number is not greater than the round in the QC."; return false; } - //TODO verify QC - - IXdcReleaseSpec xdcSpec = specProvider.GetXdcSpec(xdcHeader); // will throw if no spec found + IXdcReleaseSpec xdcSpec = specProvider.GetXdcSpec(xdcHeader); // will throw if no spec found Address[] masternodes; - if (xdcHeader.IsEpochSwitch(xdcSpec)) + if (epochSwitchManager.IsEpochSwitchAtBlock(xdcHeader)) { if (xdcHeader.Nonce != XdcConstants.NonceDropVoteValue) { @@ -72,7 +65,7 @@ public bool ValidateParams(BlockHeader parent, BlockHeader header, out string er } //TODO init masternodes by reading from most recent checkpoint - (masternodes, var penaltiesAddresses) = snapshotManager.CalculateNextEpochMasternodes(xdcHeader, xdcSpec); + (masternodes, var penaltiesAddresses) = snapshotManager.CalculateNextEpochMasternodes(xdcHeader.Number, xdcHeader.ParentHash, xdcSpec); if (!xdcHeader.ValidatorsAddress.SequenceEqual(masternodes)) { error = "Validators does not match what's stored in snapshot minus its penalty."; @@ -87,24 +80,26 @@ public bool ValidateParams(BlockHeader parent, BlockHeader header, out string er } else { - if (xdcHeader.Validators?.Length != 0) + if (xdcHeader.Validators is not null && + xdcHeader.Validators.Length != 0) { error = "Validators are not empty in non-epoch switch header."; return false; } - if (xdcHeader.Penalties?.Length != 0) + if (xdcHeader.Penalties is not null && + xdcHeader.Penalties?.Length != 0) { error = "Penalties are not empty in non-epoch switch header."; return false; } //TODO get masternodes from snapshot - EpochSwitchInfo epochSwitchInfo = epochSwitchManager.GetEpochSwitchInfo(xdcHeader, xdcHeader.ParentHash); + EpochSwitchInfo epochSwitchInfo = epochSwitchManager.GetEpochSwitchInfo(xdcHeader); masternodes = epochSwitchInfo.Masternodes; if (masternodes is null || masternodes.Length == 0) throw new InvalidOperationException($"Snap shot returned no master nodes for header \n{xdcHeader.ToString()}"); } - ulong currentLeaderIndex = (xdcHeader.ExtraConsensusData.CurrentRound % (ulong)xdcSpec.EpochLength % (ulong)masternodes.Length); + ulong currentLeaderIndex = (xdcHeader.ExtraConsensusData.BlockRound % (ulong)xdcSpec.EpochLength % (ulong)masternodes.Length); if (masternodes[(int)currentLeaderIndex] != header.Author) { error = $"Block proposer {header.Author} is not the current leader."; diff --git a/src/Nethermind/Nethermind.Xdc/XdcSealer.cs b/src/Nethermind/Nethermind.Xdc/XdcSealer.cs new file mode 100644 index 000000000000..35c0f60714dd --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcSealer.cs @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Consensus; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Crypto; +using Nethermind.Serialization.Rlp; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Nethermind.Xdc; + +internal class XdcSealer(ISigner signer) : ISealer +{ + private static readonly XdcHeaderDecoder _xdcHeaderDecoder = new XdcHeaderDecoder(); + public Address Address => signer.Address; + + public bool CanSeal(long blockNumber, Hash256 parentHash) + { + //We might want to add more logic here in the future + return true; + } + + public Task SealBlock(Block block, CancellationToken cancellationToken) + { + if (block.Header is not XdcBlockHeader xdcBlockHeader) + throw new ArgumentException("Only XDC headers are supported."); + if (block.IsGenesis) throw new InvalidOperationException("Can't sign genesis block"); + + KeccakRlpStream hashStream = new KeccakRlpStream(); + _xdcHeaderDecoder.Encode(hashStream, xdcBlockHeader, RlpBehaviors.ForSealing); + xdcBlockHeader.Validator = signer.Sign(hashStream.GetValueHash()).BytesWithRecovery; + + xdcBlockHeader.Hash = xdcBlockHeader.CalculateHash().ToHash256(); + return Task.FromResult(block); + } +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcSort.cs b/src/Nethermind/Nethermind.Xdc/XdcSort.cs new file mode 100644 index 000000000000..c8a9ac342c9d --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcSort.cs @@ -0,0 +1,250 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Text; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace Nethermind.Xdc; + +/// +/// Port of XDC's Go's sort.Slice function and its underlying implementation. +/// https://vscode.dev/github/XinFinOrg/XDPoSChain/blob/dev-upgrade/common/sort/slice.go#L29 +/// This implementation is required since the order of validators is decided by it in XDC. +/// +public static class XdcSort +{ + private struct LessSwap + { + public Func Less; + public IList Data; + + public LessSwap(IList data, Func less) + { + Data = data; + Less = less; + } + + public void Swap(int i, int j) + { + T temp = Data[i]; + Data[i] = Data[j]; + Data[j] = temp; + } + } + + /// + /// Sorts the slice x given the provided less function. + /// The sort is not guaranteed to be stable: equal elements may be reversed from their original order. + /// + public static void Slice(IList x, Func less) + { + if (x == null) + throw new ArgumentNullException(nameof(x)); + if (less == null) + throw new ArgumentNullException(nameof(less)); + + int length = x.Count; + var data = new LessSwap(x, less); + QuickSort_func(data, 0, length, MaxDepth(length)); + } + + private static int MaxDepth(int n) + { + int depth = 0; + for (int i = n; i > 0; i >>= 1) + { + depth++; + } + return depth * 2; + } + + private static void QuickSort_func(LessSwap data, int a, int b, int maxDepth) + { + while (b - a > 12) + { + if (maxDepth == 0) + { + HeapSort_func(data, a, b); + return; + } + maxDepth--; + int mlo, mhi; + DoPivot_func(data, a, b, out mlo, out mhi); + if (mlo - a < b - mhi) + { + QuickSort_func(data, a, mlo, maxDepth); + a = mhi; + } + else + { + QuickSort_func(data, mhi, b, maxDepth); + b = mlo; + } + } + if (b - a > 1) + { + for (int i = a + 6; i < b; i++) + { + if (data.Less(data.Data[i], data.Data[i - 6])) + { + data.Swap(i, i - 6); + } + } + InsertionSort_func(data, a, b); + } + } + + private static void HeapSort_func(LessSwap data, int a, int b) + { + int first = a; + int lo = 0; + int hi = b - a; + for (int i = (hi - 1) / 2; i >= 0; i--) + { + SiftDown_func(data, i, hi, first); + } + for (int i = hi - 1; i >= 0; i--) + { + data.Swap(first, first + i); + SiftDown_func(data, lo, i, first); + } + } + + private static void DoPivot_func(LessSwap data, int low, int high, out int middleLow, out int middleHigh) + { + int m = (int)((uint)(low + high) >> 1); + if (high - low > 40) + { + int s = (high - low) / 8; + MedianOfThree_func(data, low, low + s, low + 2 * s); + MedianOfThree_func(data, m, m - s, m + s); + MedianOfThree_func(data, high - 1, high - 1 - s, high - 1 - 2 * s); + } + MedianOfThree_func(data, low, m, high - 1); + int pivot = low; + int a = low + 1; + int c = high - 1; + while (a < c && data.Less(data.Data[a], data.Data[pivot])) + { + a++; + } + int b = a; + while (true) + { + while (b < c && !data.Less(data.Data[pivot], data.Data[b])) + { + b++; + } + while (b < c && data.Less(data.Data[pivot], data.Data[c - 1])) + { + c--; + } + if (b >= c) + { + break; + } + data.Swap(b, c - 1); + b++; + c--; + } + bool protect = high - c < 5; + if (!protect && high - c < (high - low) / 4) + { + int d = 0; + if (!data.Less(data.Data[pivot], data.Data[high - 1])) + { + data.Swap(c, high - 1); + c++; + d++; + } + if (!data.Less(data.Data[b - 1], data.Data[pivot])) + { + b--; + d++; + } + if (!data.Less(data.Data[m], data.Data[pivot])) + { + data.Swap(m, b - 1); + b--; + d++; + } + protect = d > 1; + } + if (protect) + { + while (true) + { + while (a < b && !data.Less(data.Data[b - 1], data.Data[pivot])) + { + b--; + } + while (a < b && data.Less(data.Data[a], data.Data[pivot])) + { + a++; + } + if (a >= b) + { + break; + } + data.Swap(a, b - 1); + a++; + b--; + } + } + data.Swap(pivot, b - 1); + middleLow = b - 1; + middleHigh = c; + } + + private static void InsertionSort_func(LessSwap data, int a, int b) + { + for (int i = a + 1; i < b; i++) + { + for (int j = i; j > a && data.Less(data.Data[j], data.Data[j - 1]); j--) + { + data.Swap(j, j - 1); + } + } + } + + private static void SiftDown_func(LessSwap data, int low, int high, int first) + { + int root = low; + while (true) + { + int child = 2 * root + 1; + if (child >= high) + { + break; + } + if (child + 1 < high && data.Less(data.Data[first + child], data.Data[first + child + 1])) + { + child++; + } + if (!data.Less(data.Data[first + root], data.Data[first + child])) + { + return; + } + data.Swap(first + root, first + child); + root = child; + } + } + + private static void MedianOfThree_func(LessSwap data, int m1, int m0, int m2) + { + if (data.Less(data.Data[m1], data.Data[m0])) + { + data.Swap(m1, m0); + } + if (data.Less(data.Data[m2], data.Data[m1])) + { + data.Swap(m2, m1); + if (data.Less(data.Data[m1], data.Data[m0])) + { + data.Swap(m1, m0); + } + } + } +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcStateSyncAllocationStrategyFactory.cs b/src/Nethermind/Nethermind.Xdc/XdcStateSyncAllocationStrategyFactory.cs new file mode 100644 index 000000000000..74277a46fbbf --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcStateSyncAllocationStrategyFactory.cs @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Stats; +using Nethermind.Synchronization.FastSync; +using Nethermind.Synchronization.ParallelSync; +using Nethermind.Synchronization.Peers; +using Nethermind.Synchronization.Peers.AllocationStrategies; +using Nethermind.Synchronization.StateSync; + +namespace Nethermind.Xdc; + +public class XdcStateSyncAllocationStrategyFactory : StaticPeerAllocationStrategyFactory +{ + private static readonly IPeerAllocationStrategy DefaultStrategy = + new AllocationStrategy(new BySpeedStrategy(TransferSpeedType.NodeData, true)); + + public XdcStateSyncAllocationStrategyFactory() : base(DefaultStrategy) + { + } + + internal class AllocationStrategy : FilterPeerAllocationStrategy + { + public AllocationStrategy(IPeerAllocationStrategy strategy) : base(strategy) + { + } + + protected override bool Filter(PeerInfo peerInfo) + { + return peerInfo.CanGetSnapData() || peerInfo.SyncPeer.ProtocolVersion == 100; + } + } +} + diff --git a/src/Nethermind/Nethermind.Xdc/XdcSubnetBlockHeader.cs b/src/Nethermind/Nethermind.Xdc/XdcSubnetBlockHeader.cs new file mode 100644 index 000000000000..768dc9f382df --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcSubnetBlockHeader.cs @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Crypto; +using Nethermind.Int256; +using System.Collections.Immutable; + +namespace Nethermind.Xdc; + +public class XdcSubnetBlockHeader : XdcBlockHeader +{ + private static readonly XdcSubnetHeaderDecoder _headerDecoder = new(); + + public XdcSubnetBlockHeader( + Hash256 parentHash, + Hash256 unclesHash, + Address beneficiary, + in UInt256 difficulty, + long number, + long gasLimit, + ulong timestamp, + byte[] extraData) + : base(parentHash, unclesHash, beneficiary, difficulty, number, gasLimit, timestamp, extraData) + { + } + + public byte[]? NextValidators { get; set; } + + private ImmutableArray
? _nextValidatorsAddress; + public ImmutableArray
? NextValidatorsAddress + { + get + { + if (_nextValidatorsAddress is not null) + return _nextValidatorsAddress; + _nextValidatorsAddress = XdcExtensions.ExtractAddresses(NextValidators); + return _nextValidatorsAddress; + } + set { _nextValidatorsAddress = value; } + } + + public override ValueHash256 CalculateHash() + { + KeccakRlpStream rlpStream = new KeccakRlpStream(); + _headerDecoder.Encode(rlpStream, this); + return rlpStream.GetHash(); + } +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcTransactionExtensions.cs b/src/Nethermind/Nethermind.Xdc/XdcTransactionExtensions.cs new file mode 100644 index 000000000000..917590e5f203 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcTransactionExtensions.cs @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Xdc.Spec; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Nethermind.Xdc; + +internal static partial class XdcExtensions +{ + public static bool IsSpecialTransaction(this Transaction currentTx, IXdcReleaseSpec spec) + => currentTx.To is not null && ((currentTx.To == spec.BlockSignerContract) || (currentTx.To == spec.RandomizeSMCBinary)); + public static bool RequiresSpecialHandling(this Transaction currentTx, IXdcReleaseSpec spec) + => IsSignTransaction(currentTx, spec) + || IsLendingTransaction(currentTx, spec) + || IsTradingTransaction(currentTx, spec) + || IsLendingFinalizedTradeTransaction(currentTx, spec) + || IsTradingStateTransaction(currentTx, spec); + public static bool IsSignTransaction(this Transaction currentTx, IXdcReleaseSpec spec) => currentTx.To is not null && currentTx.To == spec.BlockSignerContract; + public static bool IsTradingTransaction(this Transaction currentTx, IXdcReleaseSpec spec) => currentTx.To is not null && currentTx.To == spec.XDCXAddressBinary && spec.IsTIPXDCXMiner; + public static bool IsLendingTransaction(this Transaction currentTx, IXdcReleaseSpec spec) => currentTx.To is not null && currentTx.To == spec.XDCXLendingAddressBinary && spec.IsTIPXDCXMiner; + public static bool IsLendingFinalizedTradeTransaction(this Transaction currentTx, IXdcReleaseSpec spec) => currentTx.To is not null && currentTx.To == spec.XDCXLendingFinalizedTradeAddressBinary && spec.IsTIPXDCXMiner; + public static bool IsTradingStateTransaction(this Transaction currentTx, IXdcReleaseSpec spec) => currentTx.To is not null && currentTx.To == spec.TradingStateAddressBinary && spec.IsTIPXDCXMiner; + + public static bool IsSkipNonceTransaction(this Transaction currentTx, IXdcReleaseSpec spec) => + currentTx.To is not null + && (IsTradingStateTransaction(currentTx, spec) + || IsTradingTransaction(currentTx, spec) + || IsLendingTransaction(currentTx, spec) + || IsLendingFinalizedTradeTransaction(currentTx, spec)); + +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcTransactionProcessor.cs b/src/Nethermind/Nethermind.Xdc/XdcTransactionProcessor.cs new file mode 100644 index 000000000000..db8bc0214fa1 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcTransactionProcessor.cs @@ -0,0 +1,233 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.Evm.GasPolicy; +using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.Tracing.State; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Xdc.Contracts; +using Nethermind.Xdc.Spec; + +namespace Nethermind.Xdc; + +internal class XdcTransactionProcessor : EthereumTransactionProcessorBase +{ + private readonly IMasternodeVotingContract _masternodeVotingContract; + + public XdcTransactionProcessor( + ITransactionProcessor.IBlobBaseFeeCalculator blobBaseFeeCalculator, + ISpecProvider? specProvider, + IWorldState? worldState, + IVirtualMachine? virtualMachine, + ICodeInfoRepository? codeInfoRepository, + ILogManager? logManager, + IMasternodeVotingContract masternodeVotingContract) + : base( + blobBaseFeeCalculator, + specProvider, + worldState, + virtualMachine, + codeInfoRepository, + logManager) + { + _masternodeVotingContract = masternodeVotingContract; + } + + 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) + { + IXdcReleaseSpec xdcSpec = (IXdcReleaseSpec)spec; + + if (tx.IsSpecialTransaction(xdcSpec)) return; + + if (!xdcSpec.IsTipTrc21FeeEnabled) + { + base.PayFees(tx, header, spec, tracer, substate, spentGas, premiumPerGas, blobBaseFee, statusCode); + return; + } + + Address coinbase = header.GasBeneficiary!; + Address owner = _masternodeVotingContract.GetCandidateOwnerDuringProcessing(this, header, coinbase); + + if (owner is null || owner == Address.Zero) + return; + + UInt256 fee = tx.GasPrice * (ulong)spentGas; + WorldState.AddToBalanceAndCreateIfNotExists(owner, fee, spec); + + if (tracer.IsTracingFees) + tracer.ReportFees(fee, UInt256.Zero); + } + + protected override TransactionResult BuyGas(Transaction tx, IReleaseSpec spec, ITxTracer tracer, ExecutionOptions opts, + in UInt256 effectiveGasPrice, out UInt256 premiumPerGas, out UInt256 senderReservedGasPayment, + out UInt256 blobBaseFee) + { + if (tx.RequiresSpecialHandling((XdcReleaseSpec)spec)) + { + premiumPerGas = 0; + senderReservedGasPayment = 0; + blobBaseFee = 0; + return TransactionResult.Ok; + } + return base.BuyGas(tx, spec, tracer, opts, effectiveGasPrice, out premiumPerGas, out senderReservedGasPayment, out blobBaseFee); + } + + protected override TransactionResult ValidateSender(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTracer tracer, ExecutionOptions opts) + { + var xdcSpec = spec as XdcReleaseSpec; + Address target = tx.To; + Address sender = tx.SenderAddress; + + if (xdcSpec.IsBlackListingEnabled) + { + if (IsBlackListed(xdcSpec, sender) || IsBlackListed(xdcSpec, target)) + { + // Skip processing special transactions if either sender or recipient is blacklisted + return XdcTransactionResult.ContainsBlacklistedAddress; + } + } + + return base.ValidateSender(tx, header, spec, tracer, opts); + } + + private bool IsBlackListed(IXdcReleaseSpec spec, Address sender) + { + return spec.BlackListedAddresses.Contains(sender); + } + + protected override TransactionResult Execute(Transaction tx, ITxTracer tracer, ExecutionOptions opts) + { + BlockHeader header = VirtualMachine.BlockExecutionContext.Header; + IXdcReleaseSpec spec = GetSpec(header) as IXdcReleaseSpec; + + if (tx.RequiresSpecialHandling(spec)) + { + return ExecuteSpecialTransaction(tx, tracer, opts); + } + + return base.Execute(tx, tracer, opts); + } + + protected override TransactionResult IncrementNonce(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTracer tracer, ExecutionOptions opts) + { + var xdcSpec = (IXdcReleaseSpec)spec; + if (tx.RequiresSpecialHandling(xdcSpec)) + { + if (tx.IsSignTransaction(xdcSpec)) + { + var nonce = WorldState.GetNonce(tx.SenderAddress); + + if (nonce < tx.Nonce) + { + return XdcTransactionResult.NonceTooHigh; + } + else if (nonce > tx.Nonce) + { + return XdcTransactionResult.NonceTooLow; + } + + WorldState.IncrementNonce(tx.SenderAddress); + } + + return TransactionResult.Ok; + } + + return base.IncrementNonce(tx, header, spec, tracer, opts); + } + + protected override TransactionResult ValidateGas(Transaction tx, BlockHeader header, long minGasRequired) + { + var spec = SpecProvider.GetXdcSpec((XdcBlockHeader)header); + if (tx.RequiresSpecialHandling(spec)) + { + return TransactionResult.Ok; + } + return base.ValidateGas(tx, header, minGasRequired); + } + + protected override UInt256 CalculateEffectiveGasPrice(Transaction tx, bool eip1559Enabled, in UInt256 baseFee, out UInt256 opcodeGasPrice) + { + // IMPORTANT: if we override the effective gas price to 0, we must also set opcodeGasPrice to 0. + // TxExecutionContext is created with opcodeGasPrice and is later used for refunding, tracing, etc. + // + // Also: IsSpecialTransaction requires the IXdcReleaseSpec to decide Randomize vs BlockSigner, so + // we need the current block spec here. + IXdcReleaseSpec xdcSpec = (IXdcReleaseSpec)VirtualMachine.BlockExecutionContext.Spec; + + if (tx.IsSpecialTransaction(xdcSpec)) + { + opcodeGasPrice = UInt256.Zero; + return UInt256.Zero; + } + + return base.CalculateEffectiveGasPrice(tx, eip1559Enabled, in baseFee, out opcodeGasPrice); + } + + protected override IntrinsicGas CalculateIntrinsicGas(Transaction tx, IReleaseSpec spec) => + tx.RequiresSpecialHandling((IXdcReleaseSpec)spec) + ? new IntrinsicGas() + : base.CalculateIntrinsicGas(tx, spec); + + private TransactionResult ExecuteSpecialTransaction(Transaction tx, ITxTracer tracer, ExecutionOptions opts) + { + BlockHeader header = VirtualMachine.BlockExecutionContext.Header; + IXdcReleaseSpec spec = GetSpec(header) as IXdcReleaseSpec; + + bool restore = opts.HasFlag(ExecutionOptions.Restore); + + // maybe a better approach would be adding an XdcGasPolicy + TransactionResult result; + _ = RecoverSenderIfNeeded(tx, spec, opts, UInt256.Zero); + IntrinsicGas intrinsicGas = CalculateIntrinsicGas(tx, spec); + + if (!(result = ValidateSender(tx, header, spec, tracer, opts)) + || !(result = IncrementNonce(tx, header, spec, tracer, opts)) + || !(result = ValidateStatic(tx, header, spec, opts, intrinsicGas))) + { + if (restore) + { + WorldState.Reset(resetBlockChanges: false); + } + return result; + } + + // SignTx special stuff has already been handled above + return ProcessEmptyTransaction(tx, tracer, spec); + } + + private TransactionResult ProcessEmptyTransaction(Transaction tx, ITxTracer tracer, IReleaseSpec spec) + { + WorldState.Commit(spec, tracer.IsTracingState ? tracer : NullStateTracer.Instance, commitRoots: !spec.IsEip658Enabled); + + if (tracer.IsTracingReceipt) + { + Hash256 stateRoot = null; + if (!spec.IsEip658Enabled) + { + WorldState.RecalculateStateRoot(); + stateRoot = WorldState.StateRoot; + } + + var log = new LogEntry(tx.To, [], []); + tracer.MarkAsSuccess(tx.To, 0, [], [log], stateRoot); + } + + return TransactionResult.Ok; + } +} diff --git a/src/Nethermind/Nethermind.slnx b/src/Nethermind/Nethermind.slnx index 9e52cb2b87e8..6298a9f62d2f 100644 --- a/src/Nethermind/Nethermind.slnx +++ b/src/Nethermind/Nethermind.slnx @@ -7,9 +7,6 @@ - - - @@ -34,8 +31,10 @@ + + @@ -49,7 +48,7 @@ - + @@ -66,7 +65,6 @@ - diff --git a/src/Nethermind/tests.props b/src/Nethermind/tests.props index 2b0420c425ab..67b4251fde21 100644 --- a/src/Nethermind/tests.props +++ b/src/Nethermind/tests.props @@ -6,11 +6,6 @@ false - - true - true - - @@ -21,9 +16,9 @@ - + - + diff --git a/tools/DocGen/ConfigGenerator.cs b/tools/DocGen/ConfigGenerator.cs index 8810540e3da7..414b3d4c2bae 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 f78ce87ae201..6987321701e9 100644 --- a/tools/DocGen/DBSizeGenerator.cs +++ b/tools/DocGen/DBSizeGenerator.cs @@ -28,7 +28,6 @@ internal static void Generate(string docsPath, string? dbSizeSourcePath) [ "mainnet", "sepolia", - "holesky", "gnosis", "chiado", "energyweb", @@ -98,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 697b03817723..72b192535855 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/Evm.slnx b/tools/Evm/Evm.slnx index 957bc390ccc4..46e72a9b9238 100644 --- a/tools/Evm/Evm.slnx +++ b/tools/Evm/Evm.slnx @@ -1,3 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/Evm/T8n/JsonTypes/InputData.cs b/tools/Evm/T8n/JsonTypes/InputData.cs index ba9fc250ea13..7d6582518239 100644 --- a/tools/Evm/T8n/JsonTypes/InputData.cs +++ b/tools/Evm/T8n/JsonTypes/InputData.cs @@ -33,7 +33,7 @@ public Transaction[] GetTransactions(TxDecoder decoder, ulong chainId) for (int i = 0; i < Txs.Length; i++) { - var transaction = Txs[i].ToTransaction(); + var transaction = (Transaction)Txs[i].ToTransaction(); transaction.SenderAddress = null; // t8n does not accept SenderAddress from input, so need to reset senderAddress SignTransaction(transaction, TransactionMetaDataList[i], (LegacyTransactionForRpc)Txs[i]); diff --git a/tools/Evm/T8n/JsonTypes/T8nExecutionResult.cs b/tools/Evm/T8n/JsonTypes/T8nExecutionResult.cs index be55139916e1..73aeeebd50c0 100644 --- a/tools/Evm/T8n/JsonTypes/T8nExecutionResult.cs +++ b/tools/Evm/T8n/JsonTypes/T8nExecutionResult.cs @@ -39,7 +39,7 @@ public static T8nExecutionResult ConstructT8nExecutionResult(IWorldState statePr .SelectMany(receipt => receipt.Logs ?? Enumerable.Empty()) .ToArray(); var bloom = new Bloom(logEntries); - var gasUsed = blockReceiptsTracer.TxReceipts.Count == 0 ? 0 : (ulong)blockReceiptsTracer.LastReceipt.GasUsedTotal; + var gasUsed = blockReceiptsTracer.TxReceipts.Length == 0 ? 0 : (ulong)blockReceiptsTracer.LastReceipt.GasUsedTotal; ulong? blobGasUsed = test.Spec.IsEip4844Enabled ? BlobGasCalculator.CalculateBlobGas(txReport.ValidTransactions.ToArray()) : null; var postState = new PostState diff --git a/tools/Evm/T8n/T8nBlockHashProvider.cs b/tools/Evm/T8n/T8nBlockHashProvider.cs index 7ba0195653ee..41befa0d10b4 100644 --- a/tools/Evm/T8n/T8nBlockHashProvider.cs +++ b/tools/Evm/T8n/T8nBlockHashProvider.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using Evm.T8n.Errors; +using Nethermind.Blockchain; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; @@ -9,29 +10,19 @@ namespace Evm.T8n; -public class T8nBlockHashProvider : IBlockhashProvider +public class T8nBlockHashProvider(Dictionary blockHashes) : IBlockhashProvider { - private readonly Dictionary _blockHashes = new(); - private static readonly int _maxDepth = 256; - - public Hash256? GetBlockhash(BlockHeader currentBlock, long number) - => GetBlockhash(currentBlock, number, null); - public Hash256? GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec? spec) { long current = currentBlock.Number; - if (number >= current || number < current - Math.Min(current, _maxDepth)) - { + if (number >= current || number < current - Math.Min(current, BlockhashProvider.MaxDepth)) return null; - } - return _blockHashes.GetValueOrDefault(number, null) ?? - throw new T8nException($"BlockHash for block {number} not provided", - T8nErrorCodes.ErrorMissingBlockhash); + return blockHashes.TryGetValue(number, out Hash256? hash) + ? hash + : throw new T8nException($"BlockHash for block {number} not provided", + T8nErrorCodes.ErrorMissingBlockhash); } - public void Insert(Hash256 blockHash, long number) - { - _blockHashes[number] = blockHash; - } + public Task Prefetch(BlockHeader currentBlock, CancellationToken token) => Task.CompletedTask; } diff --git a/tools/Evm/T8n/T8nExecutor.cs b/tools/Evm/T8n/T8nExecutor.cs index fb27cbe1ca21..15081c34a078 100644 --- a/tools/Evm/T8n/T8nExecutor.cs +++ b/tools/Evm/T8n/T8nExecutor.cs @@ -14,39 +14,36 @@ using Nethermind.Core.Specs; using Nethermind.Core.Test; using Nethermind.Crypto; -using Nethermind.Db; using Nethermind.Evm; using Nethermind.Evm.State; -using Nethermind.Evm.Tracing; using Nethermind.Blockchain.Tracing.GethStyle; using Nethermind.Blockchain.Tracing; using Nethermind.Evm.TransactionProcessing; using Nethermind.Logging; using Nethermind.State; -using Nethermind.Trie.Pruning; using Nethermind.Blockchain; namespace Evm.T8n; public static class T8nExecutor { - private static ILogManager _logManager = LimboLogs.Instance; + private static readonly ILogManager _logManager = LimboLogs.Instance; public static T8nExecutionResult Execute(T8nCommandArguments arguments) { T8nTest test = T8nInputProcessor.ProcessInputAndConvertToT8nTest(arguments); - KzgPolynomialCommitments.InitializeAsync(); + KzgPolynomialCommitments.InitializeAsync().Wait(); IWorldState stateProvider = TestWorldStateFactory.CreateForTest(); 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, @@ -116,9 +113,9 @@ public static T8nExecutionResult Execute(T8nCommandArguments arguments) blockReceiptsTracer.LastReceipt.BlockNumber = 0; transactionExecutionReport.SuccessfulTransactionReceipts.Add(blockReceiptsTracer.LastReceipt); } - else if (transactionResult.Error is not null && transaction.SenderAddress is not null) + else if (!transactionResult.TransactionExecuted && transaction.SenderAddress is not null) { - var error = GethErrorMappings.GetErrorMapping(transactionResult.Error, + var error = GethErrorMappings.GetErrorMapping(transactionResult.ErrorDescription, transaction.SenderAddress.ToString(true), transaction.Nonce, stateProvider.GetNonce(transaction.SenderAddress)); @@ -138,17 +135,8 @@ public static T8nExecutionResult Execute(T8nCommandArguments arguments) blockReceiptsTracer, test.SpecProvider, transactionExecutionReport); } - private static IBlockhashProvider ConstructBlockHashProvider(T8nTest test) - { - var t8NBlockHashProvider = new T8nBlockHashProvider(); - - foreach (KeyValuePair blockHash in test.BlockHashes) - { - t8NBlockHashProvider.Insert(blockHash.Value, long.Parse(blockHash.Key)); - } - - return t8NBlockHashProvider; - } + private static IBlockhashProvider ConstructBlockHashProvider(T8nTest test) => + 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 bf89abf19635..33634083dd20 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 f302bff3c5bc..b66d85fe9a22 100644 --- a/tools/Evm/T8n/T8nValidator.cs +++ b/tools/Evm/T8n/T8nValidator.cs @@ -7,7 +7,6 @@ using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; -using Nethermind.Specs.Forks; namespace Evm.T8n; @@ -23,7 +22,7 @@ public static void ApplyChecks(EnvJson env, ISpecProvider specProvider, IRelease private static void ApplyLondonChecks(EnvJson env, IReleaseSpec spec) { - if (spec is not London) return; + if (!spec.IsEip1559Enabled) return; if (env.CurrentBaseFee is not null) return; if (!env.ParentBaseFee.HasValue || env.CurrentNumber == 0) @@ -39,7 +38,7 @@ private static void ApplyLondonChecks(EnvJson env, IReleaseSpec spec) private static void ApplyShanghaiChecks(EnvJson env, IReleaseSpec spec) { - if (spec is not Shanghai) return; + if (!spec.IsEip4895Enabled) return; if (env.Withdrawals is null) { throw new T8nException("Shanghai config but missing 'withdrawals' in env section", @@ -49,7 +48,7 @@ private static void ApplyShanghaiChecks(EnvJson env, IReleaseSpec spec) private static void ApplyCancunChecks(EnvJson env, IReleaseSpec spec) { - if (spec is not Cancun) + if (!spec.IsEip4844Enabled) { env.ParentBeaconBlockRoot = null; return; @@ -69,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 1a13e2c1c26f..5ed026fc7284 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/JitAsm/DisassemblyParser.cs b/tools/JitAsm/DisassemblyParser.cs new file mode 100644 index 000000000000..dcbb9687af8f --- /dev/null +++ b/tools/JitAsm/DisassemblyParser.cs @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text; +using System.Text.RegularExpressions; + +namespace JitAsm; + +internal static partial class DisassemblyParser +{ + // Pattern to detect the start of a method's disassembly + // Example: ; Assembly listing for method Namespace.Type:Method(args) + [GeneratedRegex(@"^; Assembly listing for method (?.+)$", RegexOptions.Compiled | RegexOptions.Multiline)] + private static partial Regex MethodHeaderPattern(); + + // Pattern to detect end of method disassembly (next method or end) + [GeneratedRegex(@"^; Total bytes of code", RegexOptions.Compiled | RegexOptions.Multiline)] + private static partial Regex MethodEndPattern(); + + public static string Parse(string jitOutput, bool lastOnly = false) + { + if (string.IsNullOrWhiteSpace(jitOutput)) + { + return string.Empty; + } + + var result = new StringBuilder(); + var matches = MethodHeaderPattern().Matches(jitOutput); + + if (matches.Count == 0) + { + // No method headers found, return raw output if it looks like assembly + if (jitOutput.Contains("mov") || jitOutput.Contains("call") || jitOutput.Contains("ret")) + { + return jitOutput.Trim(); + } + return string.Empty; + } + + // In tier1 mode, JitDisasm captures both Tier-0 and Tier-1 compilations. + // We want the LAST compilation (Tier-1 with full optimizations). + int startIdx = lastOnly ? matches.Count - 1 : 0; + + for (int i = startIdx; i < matches.Count; i++) + { + var match = matches[i]; + var startIndex = match.Index; + + // Find the end of this method's disassembly + int endIndex = (i + 1 < matches.Count) ? matches[i + 1].Index : jitOutput.Length; + + // Extract this method's disassembly + var methodAsm = jitOutput[startIndex..endIndex].TrimEnd(); + + // Find "Total bytes of code" line and include it + var totalBytesMatch = MethodEndPattern().Match(methodAsm); + if (totalBytesMatch.Success) + { + // Find end of line after "Total bytes of code" + var lineEnd = methodAsm.IndexOf('\n', totalBytesMatch.Index); + if (lineEnd > 0) + { + methodAsm = methodAsm[..(lineEnd + 1)].TrimEnd(); + } + } + + if (result.Length > 0) + { + result.AppendLine(); + result.AppendLine(new string('-', 80)); + result.AppendLine(); + } + + result.AppendLine(methodAsm); + } + + return result.ToString().Trim(); + } + + public static IEnumerable ParseMethods(string jitOutput) + { + if (string.IsNullOrWhiteSpace(jitOutput)) + { + yield break; + } + + var matches = MethodHeaderPattern().Matches(jitOutput); + + for (int i = 0; i < matches.Count; i++) + { + var match = matches[i]; + var methodName = match.Groups["method"].Value; + var startIndex = match.Index; + + int endIndex = (i + 1 < matches.Count) ? matches[i + 1].Index : jitOutput.Length; + + var methodAsm = jitOutput[startIndex..endIndex].TrimEnd(); + + yield return new MethodDisassembly + { + MethodName = methodName, + Assembly = methodAsm + }; + } + } +} + +internal sealed class MethodDisassembly +{ + public required string MethodName { get; init; } + public required string Assembly { get; init; } +} diff --git a/tools/JitAsm/InstructionAnnotator.cs b/tools/JitAsm/InstructionAnnotator.cs new file mode 100644 index 000000000000..068d5ab34853 --- /dev/null +++ b/tools/JitAsm/InstructionAnnotator.cs @@ -0,0 +1,441 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text; +using System.Text.RegularExpressions; + +namespace JitAsm; + +internal static partial class InstructionAnnotator +{ + // Matches JIT assembly instruction lines: + // " add rax, rcx" + // " mov dword ptr [rbp+0x10], eax" + [GeneratedRegex(@"^\s+(?[a-z]\w*)\s+(?.+)$", RegexOptions.IgnoreCase)] + private static partial Regex InstructionLineRegex(); + + // Matches zero-operand instructions: " ret" or " nop" + [GeneratedRegex(@"^\s+(?[a-z]\w*)\s*$", RegexOptions.IgnoreCase)] + private static partial Regex ZeroOperandRegex(); + + // 64-bit registers + private static readonly HashSet Regs64 = new(StringComparer.OrdinalIgnoreCase) + { + "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rsp", "rbp", + "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15" + }; + + // 32-bit registers + private static readonly HashSet Regs32 = new(StringComparer.OrdinalIgnoreCase) + { + "eax", "ebx", "ecx", "edx", "esi", "edi", "esp", "ebp", + "r8d", "r9d", "r10d", "r11d", "r12d", "r13d", "r14d", "r15d" + }; + + // 16-bit registers + private static readonly HashSet Regs16 = new(StringComparer.OrdinalIgnoreCase) + { + "ax", "bx", "cx", "dx", "si", "di", "sp", "bp", + "r8w", "r9w", "r10w", "r11w", "r12w", "r13w", "r14w", "r15w" + }; + + // 8-bit registers + private static readonly HashSet Regs8 = new(StringComparer.OrdinalIgnoreCase) + { + "al", "bl", "cl", "dl", "sil", "dil", "spl", "bpl", "ah", "bh", "ch", "dh", + "r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b" + }; + + // JIT mnemonic → uops.info mnemonic mapping for conditional jumps + // JIT uses Intel-style aliases (je, jne, ja, etc.) but uops.info uses the + // canonical forms (jz, jnz, jnbe, etc.) + private static readonly Dictionary MnemonicAliases = new(StringComparer.OrdinalIgnoreCase) + { + ["je"] = "jz", + ["jne"] = "jnz", + ["ja"] = "jnbe", + ["jae"] = "jnb", + ["jb"] = "jb", // canonical + ["jbe"] = "jna", + ["jg"] = "jnle", + ["jge"] = "jnl", + ["jl"] = "jnge", + ["jle"] = "jng", + ["jc"] = "jb", + ["jnc"] = "jnb", + ["jp"] = "jp", // canonical + ["jnp"] = "jnp", // canonical + ["js"] = "js", // canonical + ["jns"] = "jns", // canonical + ["jo"] = "jo", // canonical + ["jno"] = "jno", // canonical + ["cmove"] = "cmovz", + ["cmovne"] = "cmovnz", + ["cmova"] = "cmovnbe", + ["cmovae"] = "cmovnb", + ["cmovb"] = "cmovb", // canonical + ["cmovbe"] = "cmovna", + ["cmovg"] = "cmovnle", + ["cmovge"] = "cmovnl", + ["cmovl"] = "cmovnge", + ["cmovle"] = "cmovng", + ["sete"] = "setz", + ["setne"] = "setnz", + ["seta"] = "setnbe", + ["setae"] = "setnb", + ["setb"] = "setb", // canonical + ["setbe"] = "setna", + ["setg"] = "setnle", + ["setge"] = "setnl", + ["setl"] = "setnge", + ["setle"] = "setng", + }; + + public static string Annotate(string disassembly, InstructionDb db) + { + var sb = new StringBuilder(); + var lines = JoinContinuationLines(disassembly.Split('\n')); + + foreach (string rawLine in lines) + { + string line = rawLine.TrimEnd('\r'); + + // Skip comment lines, labels, directives + if (IsNonInstructionLine(line)) + { + sb.AppendLine(line); + continue; + } + + var match = InstructionLineRegex().Match(line); + if (match.Success) + { + string mnemonic = match.Groups["mnemonic"].Value.ToLowerInvariant(); + string operandsRaw = match.Groups["operands"].Value.Trim(); + + // Skip annotations for calls and jumps to labels + if (ShouldSkipAnnotation(mnemonic, operandsRaw)) + { + sb.AppendLine(line); + continue; + } + + string pattern = ClassifyOperands(operandsRaw, mnemonic); + + // Try the original mnemonic first, then any alias + string lookupMnemonic = MnemonicAliases.TryGetValue(mnemonic, out var alias) + ? alias : mnemonic; + var info = db.Lookup(mnemonic, pattern) + ?? (lookupMnemonic != mnemonic ? db.Lookup(lookupMnemonic, pattern) : null); + + if (info is not null) + { + string annotation = FormatAnnotation(info); + // Pad the line to align annotations + int padTo = Math.Max(line.Length + 1, 55); + sb.Append(line.PadRight(padTo)); + sb.AppendLine(annotation); + } + else + { + sb.AppendLine(line); + } + continue; + } + + // Try zero-operand match + var zeroMatch = ZeroOperandRegex().Match(line); + if (zeroMatch.Success) + { + string mnemonic = zeroMatch.Groups["mnemonic"].Value.ToLowerInvariant(); + if (!ShouldSkipAnnotation(mnemonic, "")) + { + string zeroLookup = MnemonicAliases.TryGetValue(mnemonic, out var zAlias) + ? zAlias : mnemonic; + var info = db.Lookup(mnemonic, "") + ?? (zeroLookup != mnemonic ? db.Lookup(zeroLookup, "") : null); + if (info is not null) + { + string annotation = FormatAnnotation(info); + int padTo = Math.Max(line.Length + 1, 55); + sb.Append(line.PadRight(padTo)); + sb.AppendLine(annotation); + continue; + } + } + } + + sb.AppendLine(line); + } + + return sb.ToString().TrimEnd(); + } + + /// + /// Joins JIT output continuation lines. The JIT wraps long lines at ~80 chars: + /// " call \n[System.Threading.ThreadLocal`1[...]:get_Value():...]\n" + /// "; Assembly listing for method \nNamespace.Type:Method(...)\n" + /// This joins them so each logical line is a single string. + /// + private static List JoinContinuationLines(string[] rawLines) + { + var result = new List(rawLines.Length); + for (int i = 0; i < rawLines.Length; i++) + { + string line = rawLines[i].TrimEnd('\r'); + + // Keep joining while the next line looks like a continuation + while (i + 1 < rawLines.Length) + { + string next = rawLines[i + 1].TrimEnd('\r'); + if (IsContinuationLine(next)) + { + // Preserve a single space between joined parts so "call \n[Type:Method]" + // becomes "call [Type:Method]" rather than "call[Type:Method]" + line = line.TrimEnd() + " " + next.TrimStart(); + i++; + } + else + { + break; + } + } + + result.Add(line); + } + return result; + } + + /// + /// A line is a continuation if it doesn't match any known "primary" line type: + /// blank, comment (;), label (G_M...:), instruction (leading whitespace), data (RWD), or alignment. + /// + private static bool IsContinuationLine(string line) + { + if (line.Length == 0) return false; + + ReadOnlySpan trimmed = line.AsSpan().TrimEnd('\r'); + if (trimmed.Length == 0) return false; + + // Instructions start with whitespace + if (char.IsWhiteSpace(trimmed[0])) return false; + + // Comments start with ';' + if (trimmed[0] == ';') return false; + + // Labels: "G_M000_IG01:" or similar identifiers ending with ':' + // Check if line contains ':' and starts with a label-like pattern + if (trimmed.StartsWith("G_M", StringComparison.Ordinal) && trimmed.Contains(":", StringComparison.Ordinal)) + return false; + + // Read-only data table entries: "RWD00 dd ..." + if (trimmed.StartsWith("RWD", StringComparison.Ordinal)) return false; + + // Alignment directives: "align [N bytes for IG...]" + if (trimmed.StartsWith("align", StringComparison.OrdinalIgnoreCase)) return false; + + // Everything else is a continuation of the previous line + return true; + } + + private static bool IsNonInstructionLine(string line) + { + if (line.Length == 0) return true; + + // Instructions always start with whitespace (indented). + // Labels, data tables, directives, and other non-instruction lines start at column 0. + if (line[0] == ';') return true; // Comment at column 0 + if (!char.IsWhiteSpace(line[0])) return true; // Labels (G_M000_IG01:), data (RWD00), directives, etc. + + // Indented comments: " ; comment" + ReadOnlySpan trimmed = line.AsSpan().TrimStart(); + if (trimmed.Length > 0 && trimmed[0] == ';') return true; + + return false; + } + + private static bool ShouldSkipAnnotation(string mnemonic, string operands) + { + // Skip calls (to runtime helpers, methods, etc.) + if (mnemonic == "call") return true; + + // Skip ret - uops.info TP_unrolled is a microbenchmark artifact (return stack buffer + // mispredictions make the measurement meaningless for real code) + if (mnemonic == "ret") return true; + + // Skip int3/nop - not meaningful for performance analysis + if (mnemonic is "int3" or "nop" or "int") return true; + + return false; + } + + internal static string ClassifyOperands(string operandsRaw, string? mnemonic = null) + { + // Handle trailing comments after operands: "rax, rcx ; some comment" + int commentIdx = operandsRaw.IndexOf(';'); + if (commentIdx >= 0) + operandsRaw = operandsRaw[..commentIdx].TrimEnd(); + + if (string.IsNullOrWhiteSpace(operandsRaw)) + return ""; + + // Split operands by comma, but respect brackets for memory operands + var operands = SplitOperands(operandsRaw); + var parts = new List(); + + for (int i = 0; i < operands.Count; i++) + { + string op = operands[i].Trim(); + + // LEA's second operand is an address expression, not a memory load + // uops.info classifies it as "agen" (address generation) + if (mnemonic is "lea" && i == 1 && op.Contains('[')) + { + parts.Add("agen"); + continue; + } + + string classified = ClassifySingleOperand(op); + if (classified.Length > 0) + parts.Add(classified); + } + + return string.Join(",", parts); + } + + private static List SplitOperands(string operands) + { + var result = new List(); + int depth = 0; + int start = 0; + + for (int i = 0; i < operands.Length; i++) + { + char c = operands[i]; + if (c == '[') depth++; + else if (c == ']') depth--; + else if (c == ',' && depth == 0) + { + result.Add(operands[start..i]); + start = i + 1; + } + } + + result.Add(operands[start..]); + return result; + } + + private static string ClassifySingleOperand(string op) + { + // Memory operand: "dword ptr [rbp+10h]", "qword ptr [rsp+20h]", "[rax]" + if (op.Contains('[')) + { + if (op.Contains("zmmword ptr", StringComparison.OrdinalIgnoreCase)) return "m512"; + if (op.Contains("ymmword ptr", StringComparison.OrdinalIgnoreCase)) return "m256"; + if (op.Contains("xmmword ptr", StringComparison.OrdinalIgnoreCase)) return "m128"; + if (op.Contains("qword ptr", StringComparison.OrdinalIgnoreCase)) return "m64"; + // gword ptr = GC-tracked pointer-width memory (.NET JIT specific, equivalent to qword on x64) + if (op.Contains("gword ptr", StringComparison.OrdinalIgnoreCase)) return "m64"; + // bword ptr = pointer-width memory without GC tracking (.NET JIT specific, equivalent to qword on x64) + if (op.Contains("bword ptr", StringComparison.OrdinalIgnoreCase)) return "m64"; + if (op.Contains("dword ptr", StringComparison.OrdinalIgnoreCase)) return "m32"; + if (op.Contains("word ptr", StringComparison.OrdinalIgnoreCase) && + !op.Contains("dword", StringComparison.OrdinalIgnoreCase) && + !op.Contains("qword", StringComparison.OrdinalIgnoreCase) && + !op.Contains("gword", StringComparison.OrdinalIgnoreCase) && + !op.Contains("bword", StringComparison.OrdinalIgnoreCase) && + !op.Contains("xmmword", StringComparison.OrdinalIgnoreCase) && + !op.Contains("ymmword", StringComparison.OrdinalIgnoreCase) && + !op.Contains("zmmword", StringComparison.OrdinalIgnoreCase)) + return "m16"; + if (op.Contains("byte ptr", StringComparison.OrdinalIgnoreCase)) return "m8"; + return "m"; + } + + // Register operands + string regName = op.Trim(); + + // ZMM registers + if (regName.StartsWith("zmm", StringComparison.OrdinalIgnoreCase)) return "zmm"; + // YMM registers + if (regName.StartsWith("ymm", StringComparison.OrdinalIgnoreCase)) return "ymm"; + // XMM registers + if (regName.StartsWith("xmm", StringComparison.OrdinalIgnoreCase)) return "xmm"; + // K mask registers + if (regName.StartsWith("k", StringComparison.OrdinalIgnoreCase) && regName.Length <= 2 && + regName.Length > 1 && char.IsDigit(regName[1])) return "k"; + + if (Regs64.Contains(regName)) return "r64"; + if (Regs32.Contains(regName)) return "r32"; + if (Regs16.Contains(regName)) return "r16"; + if (Regs8.Contains(regName)) return "r8"; + + // Immediate: hex (0x1A, 1Ah), decimal, or negative + if (IsImmediate(regName)) + { + // Try to determine imm8 vs imm32 from value range + if (TryParseImmediate(regName, out long value)) + { + return value is >= -128 and <= 255 ? "imm8" : "imm32"; + } + return "imm"; + } + + // Label reference (for jumps) - strip SHORT/NEAR prefix added by JIT + if (regName.StartsWith("SHORT ", StringComparison.OrdinalIgnoreCase)) + regName = regName[6..].TrimStart(); + if (regName.StartsWith("NEAR ", StringComparison.OrdinalIgnoreCase)) + regName = regName[5..].TrimStart(); + + if (regName.StartsWith("G_M", StringComparison.OrdinalIgnoreCase)) + return "rel"; + + // Unknown + return ""; + } + + private static bool IsImmediate(string op) + { + if (op.Length == 0) return false; + + // Hex: 0x prefix or trailing h + if (op.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) return true; + if (op.EndsWith('h') || op.EndsWith('H')) + { + return op[..^1].All(c => char.IsAsciiHexDigit(c) || c == '-'); + } + + // Decimal (possibly negative) + return op.All(c => char.IsDigit(c) || c == '-'); + } + + private static bool TryParseImmediate(string op, out long value) + { + value = 0; + + if (op.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + return long.TryParse(op.AsSpan(2), System.Globalization.NumberStyles.HexNumber, + System.Globalization.CultureInfo.InvariantCulture, out value); + + if (op.EndsWith('h') || op.EndsWith('H')) + return long.TryParse(op.AsSpan(0, op.Length - 1), System.Globalization.NumberStyles.HexNumber, + System.Globalization.CultureInfo.InvariantCulture, out value); + + return long.TryParse(op, out value); + } + + private static string FormatAnnotation(InstructionInfo info) + { + var sb = new StringBuilder(); + sb.Append("; ["); + sb.Append($"TP:{info.Throughput:F2}"); + sb.Append($" | Lat:{info.Latency,2}"); + sb.Append($" | Uops:{info.Uops}"); + if (info.Ports is not null) + { + sb.Append($" | {info.Ports}"); + } + sb.Append(']'); + return sb.ToString(); + } +} diff --git a/tools/JitAsm/InstructionDb.cs b/tools/JitAsm/InstructionDb.cs new file mode 100644 index 000000000000..df39ca110944 --- /dev/null +++ b/tools/JitAsm/InstructionDb.cs @@ -0,0 +1,156 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace JitAsm; + +internal sealed class InstructionInfo +{ + public required string Mnemonic { get; init; } + public required string OperandPattern { get; init; } + public float Throughput { get; init; } + public int Latency { get; init; } + public int Uops { get; init; } + public string? Ports { get; init; } +} + +internal sealed class InstructionDb +{ + private const string Magic = "UOPS"; + private const ushort Version = 2; + + private readonly Dictionary> _instructions = new(StringComparer.OrdinalIgnoreCase); + + public string ArchName { get; } + + public InstructionDb(string archName) + { + ArchName = archName; + } + + public void Add(InstructionInfo info) + { + string key = info.Mnemonic.ToLowerInvariant(); + if (!_instructions.TryGetValue(key, out var list)) + { + list = []; + _instructions[key] = list; + } + list.Add(info); + } + + public int Count => _instructions.Sum(kv => kv.Value.Count); + + public InstructionInfo? Lookup(string mnemonic, string operandPattern) + { + if (!_instructions.TryGetValue(mnemonic, out var forms)) + return null; + + // Exact match + foreach (var form in forms) + { + if (string.Equals(form.OperandPattern, operandPattern, StringComparison.OrdinalIgnoreCase)) + return form; + } + + // Relaxed match: ignore register width differences (r32 ≈ r64 for same instruction class) + string relaxed = RelaxPattern(operandPattern); + foreach (var form in forms) + { + if (string.Equals(RelaxPattern(form.OperandPattern), relaxed, StringComparison.OrdinalIgnoreCase)) + return form; + } + + // Mnemonic-only match for zero-operand instructions (ret, nop, etc.) + if (operandPattern.Length == 0) + { + foreach (var form in forms) + { + if (form.OperandPattern.Length == 0) + return form; + } + } + + return null; + } + + private static string RelaxPattern(string pattern) + { + // Normalize register widths: r8/r16/r32/r64 → r, m8/m16/m32/m64/m128/m256/m512 → m, imm8/imm32 → imm + return pattern + .Replace("r64", "r").Replace("r32", "r").Replace("r16", "r").Replace("r8", "r") + .Replace("m512", "m").Replace("m256", "m").Replace("m128", "m") + .Replace("m64", "m").Replace("m32", "m").Replace("m16", "m").Replace("m8", "m") + .Replace("imm32", "imm").Replace("imm8", "imm"); + } + + public void Save(string path) + { + using var stream = File.Create(path); + using var writer = new BinaryWriter(stream); + + // Header + writer.Write(Magic.ToCharArray()); + writer.Write(Version); + writer.Write(ArchName); + + // Count all entries + int entryCount = Count; + writer.Write(entryCount); + + // Entries + foreach (var (_, forms) in _instructions) + { + foreach (var info in forms) + { + writer.Write(info.Mnemonic); + writer.Write(info.OperandPattern); + writer.Write(info.Throughput); + writer.Write((short)info.Latency); + writer.Write((short)info.Uops); + writer.Write(info.Ports ?? string.Empty); + } + } + } + + public static InstructionDb Load(string path) + { + using var stream = File.OpenRead(path); + using var reader = new BinaryReader(stream); + + // Header + char[] magic = reader.ReadChars(4); + if (new string(magic) != Magic) + throw new InvalidDataException($"Invalid instruction database file: bad magic"); + + ushort version = reader.ReadUInt16(); + if (version != Version) + throw new InvalidDataException($"Unsupported instruction database version: {version}"); + + string archName = reader.ReadString(); + int entryCount = reader.ReadInt32(); + + var db = new InstructionDb(archName); + + for (int i = 0; i < entryCount; i++) + { + string mnemonic = reader.ReadString(); + string operandPattern = reader.ReadString(); + float throughput = reader.ReadSingle(); + short latency = reader.ReadInt16(); + short uops = reader.ReadInt16(); + string ports = reader.ReadString(); + + db.Add(new InstructionInfo + { + Mnemonic = mnemonic, + OperandPattern = operandPattern, + Throughput = throughput, + Latency = latency, + Uops = uops, + Ports = ports.Length > 0 ? ports : null + }); + } + + return db; + } +} diff --git a/tools/JitAsm/InstructionDbBuilder.cs b/tools/JitAsm/InstructionDbBuilder.cs new file mode 100644 index 000000000000..7daf9cfa517b --- /dev/null +++ b/tools/JitAsm/InstructionDbBuilder.cs @@ -0,0 +1,265 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Globalization; +using System.Xml; + +namespace JitAsm; + +internal static class InstructionDbBuilder +{ + // Map from CLI flag value to uops.info architecture name + private static readonly Dictionary ArchMap = new(StringComparer.OrdinalIgnoreCase) + { + ["alder-lake"] = "ADL-P", + ["rocket-lake"] = "RKL", + ["ice-lake"] = "ICL", + ["tiger-lake"] = "TGL", + ["skylake"] = "SKL", + ["zen4"] = "ZEN4", + ["zen3"] = "ZEN3", + ["zen2"] = "ZEN2", + }; + + // Fallback chain: if the target arch has no data, try these in order + private static readonly Dictionary FallbackChain = new(StringComparer.OrdinalIgnoreCase) + { + ["ADL-P"] = ["RKL", "TGL", "ICL", "SKL"], + ["RKL"] = ["TGL", "ICL", "SKL"], + ["TGL"] = ["ICL", "SKL"], + ["ICL"] = ["SKL", "SKX", "HSW"], + ["SKL"] = ["SKX", "HSW"], + ["ZEN4"] = ["ZEN3", "ZEN2", "ZEN+"], + ["ZEN3"] = ["ZEN2", "ZEN+"], + ["ZEN2"] = ["ZEN+"], + }; + + public static string ResolveArchName(string cliValue) + { + return ArchMap.TryGetValue(cliValue, out var name) ? name : cliValue.ToUpperInvariant(); + } + + public static IReadOnlyCollection SupportedArchitectures => ArchMap.Keys; + + public static InstructionDb Build(string xmlPath, string archCliValue) + { + string targetArch = ResolveArchName(archCliValue); + string[] fallbacks = FallbackChain.TryGetValue(targetArch, out var fb) ? fb : []; + + var db = new InstructionDb(targetArch); + var seen = new HashSet(StringComparer.OrdinalIgnoreCase); + + using var stream = File.OpenRead(xmlPath); + using var reader = XmlReader.Create(stream, new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore }); + + while (reader.Read()) + { + if (reader.NodeType != XmlNodeType.Element || reader.Name != "instruction") + continue; + + string? asm = reader.GetAttribute("asm"); + if (asm is null) + continue; + + // Clean the asm mnemonic: remove prefixes like "{load} " or "{store} " + string mnemonic = CleanMnemonic(asm); + if (mnemonic.Length == 0) + continue; + + // Read the instruction subtree + string instructionXml = reader.ReadOuterXml(); + var instrDoc = new XmlDocument(); + instrDoc.LoadXml(instructionXml); + var instrNode = instrDoc.DocumentElement!; + + // Parse operands + string operandPattern = BuildOperandPattern(instrNode); + + // Dedup key + string key = $"{mnemonic.ToLowerInvariant()}|{operandPattern}"; + if (seen.Contains(key)) + continue; + + // Find measurement for target architecture (with fallback) + var measurement = FindMeasurement(instrNode, targetArch, fallbacks); + if (measurement is null) + continue; + + float throughput = ParseFloat(measurement.GetAttribute("TP_unrolled")) + ?? ParseFloat(measurement.GetAttribute("TP_loop")) + ?? 0; + + int uops = ParseInt(measurement.GetAttribute("uops")) ?? 0; + string? ports = measurement.GetAttribute("ports"); + if (string.IsNullOrEmpty(ports)) ports = null; + + // Get max latency from child elements + int latency = 0; + foreach (XmlNode child in measurement.ChildNodes) + { + if (child is XmlElement latencyEl && latencyEl.Name == "latency") + { + int? cycles = ParseInt(latencyEl.GetAttribute("cycles")) + ?? ParseInt(latencyEl.GetAttribute("cycles_mem")) + ?? ParseInt(latencyEl.GetAttribute("cycles_addr")); + if (cycles.HasValue && cycles.Value > latency) + latency = cycles.Value; + } + } + + seen.Add(key); + db.Add(new InstructionInfo + { + Mnemonic = mnemonic.ToLowerInvariant(), + OperandPattern = operandPattern, + Throughput = throughput, + Latency = latency, + Uops = uops, + Ports = ports + }); + } + + return db; + } + + private static string CleanMnemonic(string asm) + { + // Remove assembler hints like "{load} ", "{store} ", "{vex} ", "{evex} " + ReadOnlySpan span = asm.AsSpan().Trim(); + while (span.Length > 0 && span[0] == '{') + { + int end = span.IndexOf('}'); + if (end < 0) break; + span = span[(end + 1)..].TrimStart(); + } + + // Take only the first word (mnemonic), skip any operand hints + int space = span.IndexOf(' '); + if (space > 0) + span = span[..space]; + + return span.ToString(); + } + + private static string BuildOperandPattern(XmlElement instrNode) + { + var parts = new List(); + foreach (XmlNode child in instrNode.ChildNodes) + { + if (child is not XmlElement operandEl || operandEl.Name != "operand") + continue; + + // Skip suppressed operands (flags, implicit registers) + if (operandEl.GetAttribute("suppressed") == "1") + continue; + + string? type = operandEl.GetAttribute("type"); + string? width = operandEl.GetAttribute("width"); + + string part = type switch + { + "reg" => ClassifyReg(width, operandEl.InnerText), + "mem" => ClassifyMem(width), + "agen" => "agen", + "imm" => ClassifyImm(width), + "relbr" => "rel", + _ => "" + }; + + if (part.Length > 0) + parts.Add(part); + } + + return string.Join(",", parts); + } + + private static string ClassifyReg(string? width, string? regNames) + { + // Check register names for xmm/ymm/zmm/mm/k + if (regNames is not null) + { + string firstReg = regNames.Split(',')[0].Trim().ToUpperInvariant(); + if (firstReg.StartsWith("ZMM")) return "zmm"; + if (firstReg.StartsWith("YMM")) return "ymm"; + if (firstReg.StartsWith("XMM")) return "xmm"; + if (firstReg.StartsWith("MM")) return "mm"; + if (firstReg.StartsWith("K")) return "k"; + } + + return width switch + { + "8" => "r8", + "16" => "r16", + "32" => "r32", + "64" => "r64", + "128" => "xmm", + "256" => "ymm", + "512" => "zmm", + _ => "r" + }; + } + + private static string ClassifyMem(string? width) + { + return width switch + { + "8" => "m8", + "16" => "m16", + "32" => "m32", + "64" => "m64", + "128" => "m128", + "256" => "m256", + "512" => "m512", + _ => "m" + }; + } + + private static string ClassifyImm(string? width) + { + return width switch + { + "8" => "imm8", + "16" => "imm16", + "32" => "imm32", + _ => "imm" + }; + } + + private static XmlElement? FindMeasurement(XmlElement instrNode, string targetArch, string[] fallbacks) + { + // Try target arch first, then fallbacks + var archsToTry = new List { targetArch }; + archsToTry.AddRange(fallbacks); + + foreach (string archName in archsToTry) + { + foreach (XmlNode child in instrNode.ChildNodes) + { + if (child is not XmlElement archEl || archEl.Name != "architecture") + continue; + if (archEl.GetAttribute("name") != archName) + continue; + + foreach (XmlNode archChild in archEl.ChildNodes) + { + if (archChild is XmlElement measureEl && measureEl.Name == "measurement") + return measureEl; + } + } + } + + return null; + } + + private static float? ParseFloat(string? value) + { + if (string.IsNullOrEmpty(value)) return null; + return float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out float result) ? result : null; + } + + private static int? ParseInt(string? value) + { + if (string.IsNullOrEmpty(value)) return null; + return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int result) ? result : null; + } +} diff --git a/tools/JitAsm/JitAsm.csproj b/tools/JitAsm/JitAsm.csproj new file mode 100644 index 000000000000..a505e193dda2 --- /dev/null +++ b/tools/JitAsm/JitAsm.csproj @@ -0,0 +1,12 @@ + + + Exe + + + + + + + + + diff --git a/tools/JitAsm/JitAsm.slnx b/tools/JitAsm/JitAsm.slnx new file mode 100644 index 000000000000..b30012a5e817 --- /dev/null +++ b/tools/JitAsm/JitAsm.slnx @@ -0,0 +1,3 @@ + + + diff --git a/tools/JitAsm/JitRunner.cs b/tools/JitAsm/JitRunner.cs new file mode 100644 index 000000000000..b348446161f9 --- /dev/null +++ b/tools/JitAsm/JitRunner.cs @@ -0,0 +1,269 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Diagnostics; +using System.Text; + +namespace JitAsm; + +internal sealed class JitRunner(string assemblyPath, string? typeName, string methodName, string? typeParams, string? classTypeParams, bool verbose, bool tier1 = false) +{ + public async Task RunSinglePassAsync(IReadOnlyList? cctorsToInit = null) + { + var result = await RunJitProcessAsync(cctorsToInit); + return result; + } + + public async Task RunTwoPassAsync() + { + // Pass 1: Run without cctor initialization to detect static constructor calls + var pass1Result = await RunJitProcessAsync(null); + + if (!pass1Result.Success) + { + return pass1Result; + } + + // Detect static constructors in the output + var detectedCctors = StaticCtorDetector.DetectStaticCtors(pass1Result.Output ?? string.Empty); + + if (detectedCctors.Count == 0) + { + // No cctors detected, return pass 1 result + return pass1Result; + } + + // Pass 2: Run with cctor initialization + var pass2Result = await RunJitProcessAsync(detectedCctors); + + return new JitResult + { + Success = pass2Result.Success, + Output = pass2Result.Output, + Error = pass2Result.Error, + Pass1Output = verbose ? pass1Result.Output : null, + DetectedCctors = detectedCctors + }; + } + + private async Task RunJitProcessAsync(IReadOnlyList? cctorsToInit) + { + // Get the path to the JitAsm executable + var (executablePath, argumentPrefix) = GetExecutablePath(); + + // Build the method pattern for JitDisasm + var methodPattern = BuildMethodPattern(); + + var startInfo = new ProcessStartInfo + { + FileName = executablePath, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + + // Set JIT environment variables - these must be set before the process starts + if (tier1) + { + // Tier-1 simulation: enable tiered compilation so the method compiles at Tier-0 first, + // then gets recompiled at Tier-1 with full optimizations after the cctors have run. + startInfo.EnvironmentVariables["DOTNET_TieredCompilation"] = "1"; + startInfo.EnvironmentVariables["DOTNET_TieredPGO"] = "1"; + startInfo.EnvironmentVariables["DOTNET_TC_CallCountThreshold"] = "1"; + startInfo.EnvironmentVariables["DOTNET_TC_CallCountingDelayMs"] = "0"; + } + else + { + startInfo.EnvironmentVariables["DOTNET_TieredCompilation"] = "0"; + startInfo.EnvironmentVariables["DOTNET_TC_QuickJit"] = "0"; + } + startInfo.EnvironmentVariables["DOTNET_JitDisasm"] = methodPattern; + startInfo.EnvironmentVariables["DOTNET_JitDiffableDasm"] = "1"; + if (verbose) + startInfo.EnvironmentVariables["JITASM_VERBOSE"] = "1"; + + // Build arguments for internal runner + var args = new StringBuilder(); + if (argumentPrefix is not null) + { + args.Append(EscapeArg(argumentPrefix)); + args.Append(' '); + } + args.Append("--internal-runner "); + args.Append(EscapeArg(assemblyPath)); + args.Append(' '); + args.Append(EscapeArg(methodName)); + + if (typeName is not null) + { + args.Append(" --type "); + args.Append(EscapeArg(typeName)); + } + + if (typeParams is not null) + { + args.Append(" --type-params "); + args.Append(EscapeArg(typeParams)); + } + + if (classTypeParams is not null) + { + args.Append(" --class-type-params "); + args.Append(EscapeArg(classTypeParams)); + } + + if (cctorsToInit is not null && cctorsToInit.Count > 0) + { + args.Append(" --init-cctors "); + args.Append(EscapeArg(string.Join(";", cctorsToInit))); + } + + if (tier1) + { + args.Append(" --tier1"); + } + + startInfo.Arguments = args.ToString(); + + if (verbose) + { + Spectre.Console.AnsiConsole.MarkupLine($"[grey]Running: {Spectre.Console.Markup.Escape(executablePath)} {Spectre.Console.Markup.Escape(startInfo.Arguments)}[/]"); + Spectre.Console.AnsiConsole.MarkupLine($"[grey]DOTNET_JitDisasm={Spectre.Console.Markup.Escape(methodPattern)}[/]"); + Spectre.Console.AnsiConsole.WriteLine(); + } + + try + { + using var process = Process.Start(startInfo); + if (process is null) + { + return new JitResult { Success = false, Error = "Failed to start process" }; + } + + var stdoutTask = process.StandardOutput.ReadToEndAsync(); + var stderrTask = process.StandardError.ReadToEndAsync(); + + await process.WaitForExitAsync(); + + var stdout = await stdoutTask; + var stderr = await stderrTask; + + // Parse the JIT output from stdout (JIT diagnostics can go to either stream) + // In tier1 mode, multiple compilations are captured; take only the last (Tier-1) + var disassembly = DisassemblyParser.Parse(stdout, lastOnly: tier1); + + if (string.IsNullOrWhiteSpace(disassembly)) + { + // Try stderr + disassembly = DisassemblyParser.Parse(stderr, lastOnly: tier1); + } + + if (string.IsNullOrWhiteSpace(disassembly)) + { + // Check if there's an error + if (!string.IsNullOrWhiteSpace(stderr) && stderr.Contains("Error")) + { + return new JitResult { Success = false, Error = stderr.Trim() }; + } + + // No disassembly found + return new JitResult + { + Success = false, + Error = "No disassembly output found. Method may not exist or JIT output was not captured.", + Output = $"stdout:\n{stdout}\n\nstderr:\n{stderr}" + }; + } + + return new JitResult + { + Success = true, + Output = disassembly + }; + } + catch (Exception ex) + { + return new JitResult { Success = false, Error = ex.Message }; + } + } + + private string BuildMethodPattern() + { + // Build pattern for DOTNET_JitDisasm + // Just use the method name - the JIT will match any method with this name + // Type filtering is done post-hoc by parsing the output + return methodName; + } + + /// + /// Returns the executable path and any prefix arguments needed (e.g., DLL path for dotnet host). + /// + private static (string FileName, string? ArgumentPrefix) GetExecutablePath() + { + // Get the path to the current executable + var currentExe = Environment.ProcessPath; + if (currentExe is not null && File.Exists(currentExe)) + { + // When running via "dotnet run", ProcessPath is the dotnet host, not our tool. + // Detect this by checking if it ends with "dotnet" (or "dotnet.exe"). + var exeName = Path.GetFileNameWithoutExtension(currentExe); + if (exeName.Equals("dotnet", StringComparison.OrdinalIgnoreCase)) + { + var assemblyLocation = typeof(JitRunner).Assembly.Location; + if (!string.IsNullOrEmpty(assemblyLocation)) + { + return ("dotnet", assemblyLocation); + } + } + + return (currentExe, null); + } + + // Fallback to assembly location + var location = typeof(JitRunner).Assembly.Location; + if (!string.IsNullOrEmpty(location)) + { + // For .dll, try to find the corresponding .exe + var directory = Path.GetDirectoryName(location)!; + var baseName = Path.GetFileNameWithoutExtension(location); + + // Try .exe first (Windows) + var exePath = Path.Combine(directory, baseName + ".exe"); + if (File.Exists(exePath)) + { + return (exePath, null); + } + + // Try without extension (Linux/macOS) + exePath = Path.Combine(directory, baseName); + if (File.Exists(exePath)) + { + return (exePath, null); + } + + // Use dotnet to run the dll + return ("dotnet", location); + } + + throw new InvalidOperationException("Could not determine executable path"); + } + + private static string EscapeArg(string arg) + { + if (arg.Contains(' ') || arg.Contains('"')) + { + return $"\"{arg.Replace("\"", "\\\"")}\""; + } + return arg; + } +} + +internal sealed class JitResult +{ + public bool Success { get; init; } + public string? Output { get; init; } + public string? Error { get; init; } + public string? Pass1Output { get; init; } + public IReadOnlyList DetectedCctors { get; init; } = []; +} diff --git a/tools/JitAsm/MethodResolver.cs b/tools/JitAsm/MethodResolver.cs new file mode 100644 index 000000000000..334d7fb14b5e --- /dev/null +++ b/tools/JitAsm/MethodResolver.cs @@ -0,0 +1,393 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Reflection; + +namespace JitAsm; + +internal sealed class MethodResolver(Assembly assembly) +{ + private static readonly Dictionary TypeAliases = new(StringComparer.OrdinalIgnoreCase) + { + ["bool"] = typeof(bool), + ["byte"] = typeof(byte), + ["sbyte"] = typeof(sbyte), + ["char"] = typeof(char), + ["short"] = typeof(short), + ["ushort"] = typeof(ushort), + ["int"] = typeof(int), + ["uint"] = typeof(uint), + ["long"] = typeof(long), + ["ulong"] = typeof(ulong), + ["float"] = typeof(float), + ["double"] = typeof(double), + ["decimal"] = typeof(decimal), + ["string"] = typeof(string), + ["object"] = typeof(object), + ["void"] = typeof(void), + ["nint"] = typeof(nint), + ["nuint"] = typeof(nuint), + }; + + public MethodInfo? ResolveMethod(string? typeName, string methodName, string? typeParams, string? classTypeParams = null) + { + var candidates = new List<(Type Type, MethodInfo Method)>(); + + if (typeName is not null) + { + // Search specific type + var type = ResolveType(typeName); + if (type is null) + { + return null; + } + + // If the type is a generic type definition and we have class type params, construct the concrete type + if (type.IsGenericTypeDefinition && classTypeParams is not null) + { + type = MakeGenericType(type, classTypeParams); + if (type is null) + { + return null; + } + } + + var methods = FindMethods(type, methodName); + candidates.AddRange(methods.Select(m => (type, m))); + + // Also search base types if no methods found (for inherited methods) + if (candidates.Count == 0) + { + var baseType = type.BaseType; + while (baseType is not null && candidates.Count == 0) + { + methods = FindMethods(baseType, methodName, includeInherited: true); + candidates.AddRange(methods.Select(m => (baseType, m))); + baseType = baseType.BaseType; + } + } + } + else + { + // Search all types + foreach (var type in assembly.GetTypes()) + { + var searchType = type; + + // If the type is a generic type definition and we have class type params, try to construct it + if (type.IsGenericTypeDefinition && classTypeParams is not null) + { + var typeParamCount = classTypeParams.Split(',').Length; + if (type.GetGenericArguments().Length == typeParamCount) + { + var constructed = MakeGenericType(type, classTypeParams); + if (constructed is not null) + { + searchType = constructed; + } + } + } + + var methods = FindMethods(searchType, methodName); + candidates.AddRange(methods.Select(m => (searchType, m))); + } + } + + if (candidates.Count == 0) + { + return null; + } + + bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; + if (verbose) + { + Console.Error.WriteLine($"[DEBUG] Candidates before filtering: {candidates.Count}"); + foreach (var c in candidates) + { + Console.Error.WriteLine($"[DEBUG] Method: {c.Method.Name}, IsGenericMethodDefinition: {c.Method.IsGenericMethodDefinition}, GenericArgCount: {c.Method.GetGenericArguments().Length}"); + } + } + + // If type params are specified, filter to only methods with matching generic param count + if (typeParams is not null) + { + var genericParamCount = typeParams.Split(',').Length; + if (verbose) + Console.Error.WriteLine($"[DEBUG] Looking for generic methods with {genericParamCount} type params"); + + var genericMatches = candidates.Where(c => c.Method.IsGenericMethodDefinition && + c.Method.GetGenericArguments().Length == genericParamCount).ToList(); + + if (verbose) + Console.Error.WriteLine($"[DEBUG] Generic matches: {genericMatches.Count}"); + + if (genericMatches.Count > 0) + { + candidates = genericMatches; + } + } + else + { + // Prefer non-generic methods if no type params specified + var nonGeneric = candidates.Where(c => !c.Method.IsGenericMethodDefinition).ToList(); + if (nonGeneric.Count > 0) + { + candidates = nonGeneric; + } + } + + if (candidates.Count == 0) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] No candidates after filtering"); + return null; + } + + if (verbose) + Console.Error.WriteLine($"[DEBUG] Final candidates: {candidates.Count}, calling MakeGenericIfNeeded"); + + // If there's only one candidate, use it + if (candidates.Count == 1) + { + return MakeGenericIfNeeded(candidates[0].Method, typeParams); + } + + // Return first match + return MakeGenericIfNeeded(candidates[0].Method, typeParams); + } + + private Type? MakeGenericType(Type genericTypeDefinition, string classTypeParams) + { + var typeNames = classTypeParams.Split(',', StringSplitOptions.TrimEntries); + var types = new Type[typeNames.Length]; + + bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; + + for (int i = 0; i < typeNames.Length; i++) + { + var resolved = ResolveTypeParam(typeNames[i]); + if (resolved is null) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] Failed to resolve type param: {typeNames[i]}"); + return null; + } + types[i] = resolved; + if (verbose) + Console.Error.WriteLine($"[DEBUG] Resolved type param {typeNames[i]} to {resolved.FullName}"); + } + + try + { + var result = genericTypeDefinition.MakeGenericType(types); + if (verbose) + Console.Error.WriteLine($"[DEBUG] Constructed generic type: {result.FullName}"); + return result; + } + catch (Exception ex) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericType failed: {ex.Message}"); + return null; + } + } + + private Type? ResolveType(string typeName) + { + // Try direct lookup + var type = assembly.GetType(typeName); + if (type is not null) return type; + + // Try with assembly name prefix removed if present + var types = assembly.GetTypes(); + + // Try exact match on FullName + type = types.FirstOrDefault(t => t.FullName == typeName); + if (type is not null) return type; + + // Try match on Name only + type = types.FirstOrDefault(t => t.Name == typeName); + if (type is not null) return type; + + // Try matching generic types by base name (without the `N suffix) + // e.g., "TransactionProcessorBase" should match "TransactionProcessorBase`1" + type = types.FirstOrDefault(t => t.IsGenericTypeDefinition && + (t.FullName?.StartsWith(typeName + "`") == true || + t.Name.StartsWith(typeName + "`"))); + if (type is not null) return type; + + // Also try matching with the ` syntax - the type might be specified as TypeName`1 + if (typeName.Contains('`')) + { + type = types.FirstOrDefault(t => t.FullName == typeName || t.Name == typeName); + if (type is not null) return type; + } + + // Try nested types + foreach (var t in types) + { + if (t.FullName is not null && typeName.StartsWith(t.FullName + "+")) + { + var nestedName = typeName[(t.FullName.Length + 1)..]; + var nested = t.GetNestedType(nestedName, BindingFlags.Public | BindingFlags.NonPublic); + if (nested is not null) return nested; + } + } + + // Search referenced assemblies for cross-assembly types + foreach (var refName in assembly.GetReferencedAssemblies()) + { + try + { + var refAssembly = Assembly.Load(refName); + type = refAssembly.GetType(typeName); + if (type is not null) return type; + + // Try matching generic types by base name (e.g., "ClockCache" matches "ClockCache`2") + Type[] refTypes; + try { refTypes = refAssembly.GetTypes(); } + catch (ReflectionTypeLoadException ex) { refTypes = ex.Types.Where(t => t is not null).ToArray()!; } + + type = refTypes.FirstOrDefault(t => + t.FullName == typeName || t.Name == typeName || + (t.IsGenericTypeDefinition && + (t.FullName?.StartsWith(typeName + "`") == true || + t.Name.StartsWith(typeName + "`")))); + if (type is not null) return type; + } + catch + { + // Skip assemblies that can't be loaded + } + } + + return null; + } + + private static IEnumerable FindMethods(Type type, string methodName, bool includeInherited = false) + { + var flags = BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Instance | BindingFlags.Static; + + if (!includeInherited) + { + flags |= BindingFlags.DeclaredOnly; + } + + bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; + var methods = type.GetMethods(flags).Where(m => m.Name == methodName).ToList(); + if (verbose) + { + Console.Error.WriteLine($"[DEBUG] FindMethods on {type.FullName} for '{methodName}': found {methods.Count} methods"); + if (methods.Count == 0) + { + // List some methods to help debug + var allMethods = type.GetMethods(flags).Where(m => m.Name.Contains("Evm") || m.Name.Contains("Execute")).Take(10); + Console.Error.WriteLine($"[DEBUG] Sample methods containing 'Evm' or 'Execute': {string.Join(", ", allMethods.Select(m => m.Name))}"); + } + } + + return methods; + } + + private MethodInfo? MakeGenericIfNeeded(MethodInfo method, string? typeParams) + { + bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; + + if (typeParams is null || !method.IsGenericMethodDefinition) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: returning method as-is (typeParams={typeParams}, IsGenericMethodDefinition={method.IsGenericMethodDefinition})"); + return method; + } + + var typeNames = typeParams.Split(',', StringSplitOptions.TrimEntries); + var types = new Type[typeNames.Length]; + + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: resolving {typeNames.Length} type params: {string.Join(", ", typeNames)}"); + + for (int i = 0; i < typeNames.Length; i++) + { + var resolved = ResolveTypeParam(typeNames[i]); + if (resolved is null) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: failed to resolve type param '{typeNames[i]}'"); + return null; + } + types[i] = resolved; + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: resolved '{typeNames[i]}' to {resolved.FullName}"); + } + + try + { + var result = method.MakeGenericMethod(types); + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: success, created {result}"); + return result; + } + catch (Exception ex) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: MakeGenericMethod failed: {ex.Message}"); + return null; + } + } + + private Type? ResolveTypeParam(string typeName) + { + // Check aliases first + if (TypeAliases.TryGetValue(typeName, out var aliasType)) + { + return aliasType; + } + + // Try the target assembly + var type = ResolveType(typeName); + if (type is not null) return type; + + // Try referenced assemblies + foreach (var refName in assembly.GetReferencedAssemblies()) + { + try + { + var refAssembly = Assembly.Load(refName); + type = refAssembly.GetType(typeName); + if (type is not null) return type; + + // Try by short name + type = refAssembly.GetTypes().FirstOrDefault(t => t.Name == typeName || t.FullName == typeName); + if (type is not null) return type; + } + catch + { + // Skip assemblies that can't be loaded + } + } + + // Try Type.GetType as last resort + return Type.GetType(typeName); + } + + public IEnumerable FindAllMethods(string? typeName, string methodName) + { + if (typeName is not null) + { + var type = ResolveType(typeName); + if (type is not null) + { + return FindMethods(type, methodName); + } + return []; + } + + var results = new List(); + foreach (var type in assembly.GetTypes()) + { + results.AddRange(FindMethods(type, methodName)); + } + return results; + } +} diff --git a/tools/JitAsm/Program.cs b/tools/JitAsm/Program.cs new file mode 100644 index 000000000000..deb928163f70 --- /dev/null +++ b/tools/JitAsm/Program.cs @@ -0,0 +1,581 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.CommandLine; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Loader; +using System.Text.Json; +using Spectre.Console; + +namespace JitAsm; + +internal static class Program +{ + private static readonly Option AssemblyOption = new("-a", "--assembly") + { + Description = "Path to the assembly containing the method", + Required = true + }; + + private static readonly Option TypeOption = new("-t", "--type") + { + Description = "Fully qualified type name (optional, will search all types if not specified)" + }; + + private static readonly Option MethodOption = new("-m", "--method") + { + Description = "Method name to disassemble", + Required = true + }; + + private static readonly Option TypeParamsOption = new("--type-params") + { + Description = "Method generic type parameters (comma-separated type names)" + }; + + private static readonly Option ClassTypeParamsOption = new("--class-type-params") + { + Description = "Class generic type parameters (comma-separated type names, e.g., for TransactionProcessorBase`1)" + }; + + private static readonly Option SkipCctorOption = new("--skip-cctor-detection") + { + Description = "Skip automatic static constructor detection (single pass only)" + }; + + private static readonly Option FullOptsOption = new("--fullopts") + { + Description = "Use single-pass FullOpts compilation (DOTNET_TieredCompilation=0) instead of the default Tier-1 + PGO simulation" + }; + + private static readonly Option NoAnnotateOption = new("--no-annotate") + { + Description = "Disable per-instruction annotations (throughput, latency, uops, ports from uops.info)" + }; + + private static readonly Option ArchOption = new("--arch") + { + Description = "Target microarchitecture for annotations (default: zen4). Options: zen4, zen3, zen2, alder-lake, rocket-lake, ice-lake, tiger-lake, skylake", + DefaultValueFactory = _ => "zen4" + }; + + private static readonly Option VerboseOption = new("-v", "--verbose") + { + Description = "Show resolution details and both passes" + }; + + private static int Main(string[] args) + { + // Check if running in internal runner mode + if (args.Length > 0 && args[0] == "--internal-runner") + { + return RunInternalRunner(args.Skip(1).ToArray()); + } + + return RunCli(args); + } + + private static int RunCli(string[] args) + { + var rootCommand = new RootCommand("JIT Assembly Disassembler - Generate JIT assembly output for .NET methods") + { + AssemblyOption, + TypeOption, + MethodOption, + TypeParamsOption, + ClassTypeParamsOption, + SkipCctorOption, + FullOptsOption, + NoAnnotateOption, + ArchOption, + VerboseOption + }; + + int exitCode = 0; + rootCommand.SetAction(parseResult => + { + var assembly = parseResult.GetValue(AssemblyOption)!; + var typeName = parseResult.GetValue(TypeOption); + var methodName = parseResult.GetValue(MethodOption)!; + var typeParams = parseResult.GetValue(TypeParamsOption); + var classTypeParams = parseResult.GetValue(ClassTypeParamsOption); + var skipCctor = parseResult.GetValue(SkipCctorOption); + var fullOpts = parseResult.GetValue(FullOptsOption); + var annotate = !parseResult.GetValue(NoAnnotateOption); + var arch = parseResult.GetValue(ArchOption)!; + var verbose = parseResult.GetValue(VerboseOption); + + exitCode = Execute(assembly, typeName, methodName, typeParams, classTypeParams, skipCctor, fullOpts, annotate, arch, verbose); + }); + + int parseExitCode = rootCommand.Parse(args).Invoke(); + return parseExitCode != 0 ? parseExitCode : exitCode; + } + + private static int Execute(FileInfo assembly, string? typeName, string methodName, string? typeParams, string? classTypeParams, bool skipCctor, bool fullOpts, bool annotate, string arch, bool verbose) + { + if (!assembly.Exists) + { + AnsiConsole.MarkupLine($"[red]Error:[/] Assembly not found: {assembly.FullName}"); + return 1; + } + + bool tier1 = !fullOpts; + + if (verbose) + { + AnsiConsole.MarkupLine($"[blue]Assembly:[/] {assembly.FullName}"); + AnsiConsole.MarkupLine($"[blue]Type:[/] {typeName ?? "(search all)"}"); + AnsiConsole.MarkupLine($"[blue]Method:[/] {methodName}"); + if (classTypeParams is not null) + { + AnsiConsole.MarkupLine($"[blue]Class Type Parameters:[/] {classTypeParams}"); + } + if (typeParams is not null) + { + AnsiConsole.MarkupLine($"[blue]Method Type Parameters:[/] {typeParams}"); + } + AnsiConsole.MarkupLine(tier1 + ? "[blue]Mode:[/] Tier-1 + Dynamic PGO (default)" + : "[blue]Mode:[/] FullOpts (TieredCompilation=0)"); + AnsiConsole.WriteLine(); + } + + var runner = new JitRunner(assembly.FullName, typeName, methodName, typeParams, classTypeParams, verbose, tier1); + + InstructionDb? instructionDb = annotate ? LoadOrBuildInstructionDb(arch, verbose) : null; + + JitResult result = (skipCctor ? + runner.RunSinglePassAsync() : + runner.RunTwoPassAsync()) + .GetAwaiter().GetResult(); + + return OutputResult(result, verbose, instructionDb); + } + + private static int OutputResult(JitResult result, bool verbose, InstructionDb? instructionDb = null) + { + if (!result.Success) + { + AnsiConsole.MarkupLine($"[red]Error:[/] {Markup.Escape(result.Error ?? "Unknown error")}"); + if (!string.IsNullOrEmpty(result.Output)) + { + AnsiConsole.WriteLine(); + AnsiConsole.MarkupLine("[yellow]Output:[/]"); + AnsiConsole.WriteLine(result.Output); + } + return 1; + } + + if (verbose && result.DetectedCctors.Count > 0) + { + AnsiConsole.MarkupLine("[blue]Detected static constructors:[/]"); + foreach (var cctor in result.DetectedCctors) + { + AnsiConsole.MarkupLine($" [grey]- {Markup.Escape(cctor)}[/]"); + } + AnsiConsole.WriteLine(); + } + + if (verbose && result.Pass1Output is not null && result.DetectedCctors.Count > 0) + { + AnsiConsole.MarkupLine("[blue]Pass 1 Output (before cctor initialization):[/]"); + AnsiConsole.WriteLine(result.Pass1Output); + AnsiConsole.WriteLine(); + AnsiConsole.MarkupLine("[blue]Pass 2 Output (after cctor initialization):[/]"); + } + + string output = result.Output ?? string.Empty; + if (instructionDb is not null) + { + output = InstructionAnnotator.Annotate(output, instructionDb); + } + + Console.WriteLine(output); + return 0; + } + + private static InstructionDb? LoadOrBuildInstructionDb(string arch, bool verbose) + { + string toolDir = AppContext.BaseDirectory; + // Look for files relative to the project source directory first, then the binary directory + string projectDir = Path.GetFullPath(Path.Combine(toolDir, "..", "..", "..", "..")); + if (!File.Exists(Path.Combine(projectDir, "JitAsm.csproj"))) + { + // Fallback: try to find the project directory from the current working directory + string cwd = Directory.GetCurrentDirectory(); + if (File.Exists(Path.Combine(cwd, "tools", "JitAsm", "JitAsm.csproj"))) + projectDir = Path.Combine(cwd, "tools", "JitAsm"); + else if (File.Exists(Path.Combine(cwd, "JitAsm.csproj"))) + projectDir = cwd; + else + projectDir = toolDir; + } + + string dbPath = Path.Combine(projectDir, "instructions.db"); + string xmlPath = Path.Combine(projectDir, "instructions.xml"); + + // Check if we have a cached .db for this architecture + if (File.Exists(dbPath)) + { + try + { + var db = InstructionDb.Load(dbPath); + string targetArch = InstructionDbBuilder.ResolveArchName(arch); + if (db.ArchName == targetArch) + { + if (verbose) + AnsiConsole.MarkupLine($"[blue]Loaded instruction database:[/] {dbPath} ({db.Count} entries for {db.ArchName})"); + return db; + } + + if (verbose) + AnsiConsole.MarkupLine($"[yellow]Cached DB is for {db.ArchName}, need {targetArch}. Rebuilding...[/]"); + } + catch (Exception ex) + { + if (verbose) + AnsiConsole.MarkupLine($"[yellow]Failed to load cached DB: {Markup.Escape(ex.Message)}. Rebuilding...[/]"); + } + } + + // Build from XML + if (!File.Exists(xmlPath)) + { + AnsiConsole.MarkupLine("[yellow]Instruction database not found. Annotations disabled.[/]"); + AnsiConsole.MarkupLine("[yellow]Download it with:[/]"); + AnsiConsole.MarkupLine($" curl -o {Markup.Escape(xmlPath)} https://uops.info/instructions.xml"); + AnsiConsole.MarkupLine("[yellow]Or use --no-annotate to suppress this warning.[/]"); + return null; + } + + AnsiConsole.MarkupLine($"[blue]Building instruction database for {arch}...[/]"); + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + var builtDb = InstructionDbBuilder.Build(xmlPath, arch); + stopwatch.Stop(); + + AnsiConsole.MarkupLine($"[blue]Built {builtDb.Count} instruction entries in {stopwatch.Elapsed.TotalSeconds:F1}s[/]"); + + try + { + builtDb.Save(dbPath); + if (verbose) + AnsiConsole.MarkupLine($"[blue]Saved to:[/] {dbPath}"); + } + catch (Exception ex) + { + if (verbose) + AnsiConsole.MarkupLine($"[yellow]Warning: Could not save DB cache: {Markup.Escape(ex.Message)}[/]"); + } + + return builtDb; + } + + private static int RunInternalRunner(string[] args) + { + // Parse internal runner arguments + // Format: [--type ] [--type-params ] [--class-type-params ] [--init-cctors ] + if (args.Length < 2) + { + Console.Error.WriteLine("Internal runner requires assembly and method arguments"); + return 1; + } + + var assemblyPath = args[0]; + var methodName = args[1]; + string? typeName = null; + string? typeParams = null; + string? classTypeParams = null; + List cctorsToInit = []; + bool tier1 = false; + + for (int i = 2; i < args.Length; i++) + { + switch (args[i]) + { + case "--type" when i + 1 < args.Length: + typeName = args[++i]; + break; + case "--type-params" when i + 1 < args.Length: + typeParams = args[++i]; + break; + case "--class-type-params" when i + 1 < args.Length: + classTypeParams = args[++i]; + break; + case "--init-cctors" when i + 1 < args.Length: + cctorsToInit = args[++i].Split(';', StringSplitOptions.RemoveEmptyEntries).ToList(); + break; + case "--tier1": + tier1 = true; + break; + } + } + + // Set up dependency resolution for the target assembly. + // Without this, RuntimeHelpers.PrepareMethod silently fails when the + // JIT can't resolve types from transitive NuGet packages (e.g. Nethermind.Int256). + bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; + + // Build assembly name → file path mapping from the deps.json file. + // This resolves both project references (in the same directory) and + // NuGet packages (from the global packages cache). + var assemblyDir = Path.GetDirectoryName(Path.GetFullPath(assemblyPath))!; + var assemblyMap = BuildAssemblyMap(assemblyPath, assemblyDir, verbose); + + AssemblyLoadContext.Default.Resolving += (context, name) => + { + // First check the target assembly's directory + var localPath = Path.Combine(assemblyDir, name.Name + ".dll"); + if (File.Exists(localPath)) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] AssemblyResolve: {name.Name} → {localPath} (local)"); + return context.LoadFromAssemblyPath(localPath); + } + + // Then check deps.json mapped paths + if (assemblyMap.TryGetValue(name.Name!, out var mappedPath) && File.Exists(mappedPath)) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] AssemblyResolve: {name.Name} → {mappedPath} (deps.json)"); + return context.LoadFromAssemblyPath(mappedPath); + } + + if (verbose) + Console.Error.WriteLine($"[DEBUG] AssemblyResolve: {name.Name} → NOT FOUND"); + return null; + }; + + try + { + var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(assemblyPath)); + + if (verbose) + { + Console.Error.WriteLine($"[DEBUG] Loaded assembly: {assembly.FullName}"); + Console.Error.WriteLine($"[DEBUG] Type name: {typeName}"); + Console.Error.WriteLine($"[DEBUG] Method name: {methodName}"); + Console.Error.WriteLine($"[DEBUG] Type params: {typeParams}"); + Console.Error.WriteLine($"[DEBUG] Class type params: {classTypeParams}"); + } + + // Initialize static constructors if requested + foreach (var cctorTypeName in cctorsToInit) + { + var cctorType = ResolveType(assembly, cctorTypeName, verbose); + if (cctorType is not null) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] Running cctor for: {cctorType.FullName}"); + RuntimeHelpers.RunClassConstructor(cctorType.TypeHandle); + } + else if (verbose) + { + Console.Error.WriteLine($"[DEBUG] WARNING: Could not resolve cctor type: {cctorTypeName}"); + } + } + + // Resolve the method + var resolver = new MethodResolver(assembly); + var method = resolver.ResolveMethod(typeName, methodName, typeParams, classTypeParams); + + if (method is null) + { + if (verbose) + { + var types = assembly.GetTypes().Where(t => t.Name.Contains(typeName ?? "")).Take(5); + Console.Error.WriteLine($"[DEBUG] Possible types: {string.Join(", ", types.Select(t => t.FullName))}"); + } + Console.Error.WriteLine($"Could not resolve method: {methodName}"); + return 1; + } + + if (tier1) + { + var parameters = method.GetParameters(); + var invokeArgs = new object?[parameters.Length]; + object? target = method.IsStatic ? null : TryCreateInstance(method.DeclaringType!); + + void InvokeN(int count) + { + for (int i = 0; i < count; i++) + { + try { method.Invoke(target, invokeArgs); } + catch { /* Expected - args are null/default */ } + } + } + + if (verbose) + Console.Error.WriteLine("[DEBUG] Phase 1: Invoking to trigger Tier-0 → Instrumented Tier-0..."); + InvokeN(50); + Thread.Sleep(1000); + + if (verbose) + Console.Error.WriteLine("[DEBUG] Phase 2: Invoking to trigger Instrumented Tier-0 → Tier-1..."); + InvokeN(50); + Thread.Sleep(2000); + + if (verbose) + Console.Error.WriteLine("[DEBUG] Phase 3: Final invocations to ensure Tier-1 is installed..."); + InvokeN(50); + Thread.Sleep(1000); + } + else + { + RuntimeHelpers.PrepareMethod(method.MethodHandle); + if (verbose) + Console.Error.WriteLine($"[DEBUG] PrepareMethod completed for {method.DeclaringType?.FullName}:{method.Name}"); + + // Also invoke the method to ensure all code paths are JIT-compiled. + // PrepareMethod alone may not trigger DOTNET_JitDisasm output for + // methods from dynamically loaded assemblies. + var parameters = method.GetParameters(); + var invokeArgs = new object?[parameters.Length]; + object? target = method.IsStatic ? null : TryCreateInstance(method.DeclaringType!); + try { method.Invoke(target, invokeArgs); } + catch { /* Expected - args are null/default */ } + if (verbose) + Console.Error.WriteLine("[DEBUG] Method invocation completed"); + } + + return 0; + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error: {ex.Message}"); + return 1; + } + } + + private static object? TryCreateInstance(Type type) + { + try + { + return RuntimeHelpers.GetUninitializedObject(type); + } + catch + { + return null; + } + } + + private static Type? ResolveType(Assembly assembly, string typeName, bool verbose = false) + { + // Try direct lookup in target assembly first + var type = assembly.GetType(typeName); + if (type is not null) return type; + + // Try searching all types in target assembly + foreach (var t in assembly.GetTypes()) + { + if (t.FullName == typeName || t.Name == typeName) + { + return t; + } + } + + // Search referenced assemblies (types often come from other assemblies, + // e.g., Nethermind.Core types referenced from Nethermind.Evm) + foreach (var refName in assembly.GetReferencedAssemblies()) + { + try + { + var refAssembly = Assembly.Load(refName); + type = refAssembly.GetType(typeName); + if (type is not null) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] Resolved cctor type '{typeName}' from referenced assembly {refName.Name}"); + return type; + } + + // Try by short name + foreach (var t in refAssembly.GetTypes()) + { + if (t.FullName == typeName || t.Name == typeName) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] Resolved cctor type '{typeName}' from referenced assembly {refName.Name} (by scan)"); + return t; + } + } + } + catch + { + // Skip assemblies that can't be loaded + } + } + + // Try Type.GetType as last resort (requires assembly-qualified names for cross-assembly) + return Type.GetType(typeName); + } + + /// + /// Build a mapping from assembly name to file path using the deps.json file. + /// Resolves NuGet package references via the global packages cache. + /// + private static Dictionary BuildAssemblyMap(string assemblyPath, string assemblyDir, bool verbose) + { + var map = new Dictionary(StringComparer.OrdinalIgnoreCase); + string depsPath = Path.ChangeExtension(assemblyPath, ".deps.json"); + if (!File.Exists(depsPath)) + return map; + + string nugetPackages = Environment.GetEnvironmentVariable("NUGET_PACKAGES") + ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); + + try + { + using var stream = File.OpenRead(depsPath); + using var doc = JsonDocument.Parse(stream); + + var root = doc.RootElement; + if (!root.TryGetProperty("targets", out var targets)) + return map; + + // Get the first (and usually only) target + foreach (var target in targets.EnumerateObject()) + { + foreach (var package in target.Value.EnumerateObject()) + { + if (!package.Value.TryGetProperty("runtime", out var runtime)) + continue; + + foreach (var dll in runtime.EnumerateObject()) + { + string dllRelativePath = dll.Name; // e.g. "lib/net10.0/Nethermind.Int256.dll" + string asmName = Path.GetFileNameWithoutExtension(dllRelativePath); + + // Skip if already in the local directory + if (File.Exists(Path.Combine(assemblyDir, asmName + ".dll"))) + continue; + + // Resolve from NuGet cache: packages/{id}/{version}/{path} + string packageId = package.Name; // e.g. "Nethermind.Numerics.Int256/1.4.0" + string[] parts = packageId.Split('/'); + if (parts.Length == 2) + { + string fullPath = Path.Combine(nugetPackages, parts[0].ToLowerInvariant(), parts[1], dllRelativePath); + if (File.Exists(fullPath)) + { + map[asmName] = fullPath; + if (verbose) + Console.Error.WriteLine($"[DEBUG] Deps map: {asmName} → {fullPath}"); + } + } + } + } + break; // Only process the first target + } + } + catch (Exception ex) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] Failed to parse deps.json: {ex.Message}"); + } + + return map; + } +} + diff --git a/tools/JitAsm/README.md b/tools/JitAsm/README.md new file mode 100644 index 000000000000..705821f64be3 --- /dev/null +++ b/tools/JitAsm/README.md @@ -0,0 +1,494 @@ +# JitAsm + +A tool for viewing JIT-compiled assembly output for .NET methods. Useful for analyzing code generation, verifying optimizations, and comparing different implementations. + +## How It Works + +JitAsm spawns a child process with JIT diagnostic environment variables (`DOTNET_JitDisasm`) to capture disassembly output. + +By default, the tool simulates **Tier-1 recompilation with Dynamic PGO** — the same compilation tier that runs in production after warm-up. This produces the most representative assembly: smaller code, eliminated static base helpers, and PGO-guided branch layout. + +### Compilation Tiers + +The .NET runtime compiles methods through multiple stages: + +| Stage | Description | Typical Size | +|-------|-------------|-------------| +| **Tier-0** | Quick JIT, minimal optimization (`minopt`) | Largest | +| **Instrumented Tier-0** | Tier-0 + PGO probes for profiling | Larger still | +| **Tier-1 + PGO** (default) | Full optimization with profile data | **Smallest** | +| **FullOpts** (`--fullopts`) | Full optimization, no PGO, no tiering | Middle | + +Key difference between Tier-1 and FullOpts: +- **FullOpts** (`DOTNET_TieredCompilation=0`) compiles in a single pass. Cross-module static base helpers (`CORINFO_HELP_GET_NONGCSTATIC_BASE`) persist because the JIT hasn't resolved the static base address yet. +- **Tier-1 + PGO** compiles after Tier-0 has executed. By then, static bases are resolved, and the JIT can embed addresses directly — eliminating the helper entirely. PGO data also improves branch layout and inlining decisions. + +### Two-Pass Static Constructor Handling + +When a method references static fields, the JIT may include static constructor initialization checks (`CORINFO_HELP_GET_NONGCSTATIC_BASE`, `CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE`). JitAsm automatically detects these and runs a second pass with static constructors pre-initialized, showing the steady-state optimized code path. + +In Tier-1 mode (default), many of these helpers are eliminated naturally since the static bases are already resolved by Tier-0 execution. + +### Tier-1 Simulation + +The tool drives through all PGO compilation stages: + +1. **Invoke** method via reflection (triggers Tier-0 JIT + installs call counting stubs) +2. **Wait** for Instrumented Tier-0 recompilation (PGO profiling version) +3. **Invoke** again (triggers call counter through instrumented code) +4. **Wait** for Tier-1 recompilation (fully optimized with PGO data) +5. **Capture** the final Tier-1 assembly output + +Critical env var: `DOTNET_TC_CallCountingDelayMs=0` — without this, counting stubs aren't installed in time and Tier-1 never triggers. + +## Usage + +```bash +dotnet run --project tools/JitAsm -c Release -- [options] +``` + +### Options + +| Option | Description | +|--------|-------------| +| `-a, --assembly ` | Path to the assembly containing the method (required) | +| `-t, --type ` | Fully qualified type name (optional, searches all types if not specified) | +| `-m, --method ` | Method name to disassemble (required) | +| `--type-params ` | Method generic type parameters (comma-separated) | +| `--class-type-params ` | Class generic type parameters (comma-separated, for generic containing types) | +| `--fullopts` | Use single-pass FullOpts compilation (`TieredCompilation=0`) instead of Tier-1 + PGO | +| `--no-annotate` | Disable per-instruction annotations (throughput, latency, uops, ports). Annotations are **on by default** | +| `--arch ` | Target microarchitecture for annotations (default: `zen4`). See [Supported Architectures](#supported-architectures) | +| `--skip-cctor-detection` | Skip automatic static constructor detection (single pass only) | +| `-v, --verbose` | Show resolution details and both passes | + +### Default Mode: Tier-1 + PGO + +By default, the tool simulates Tier-1 recompilation with Dynamic PGO. This is the compilation tier code runs at in production and produces the most optimized output. + +```bash +# Default: Tier-1 + PGO (production-representative) +dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName +``` + +### FullOpts Mode + +Use `--fullopts` when you need the older single-pass compilation. This is faster (no invocation/sleep cycle) but may show static base helpers and lack PGO-guided optimizations. + +```bash +# FullOpts: single compilation, no PGO +dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName --fullopts +``` + +## Prerequisites + +Build the target project in Release configuration before disassembling: + +```bash +dotnet build src/Nethermind/Nethermind.Evm -c Release +``` + +Assemblies are output to `src/Nethermind/artifacts/bin/{ProjectName}/release/{ProjectName}.dll`. For example: +- `src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll` +- `src/Nethermind/artifacts/bin/Nethermind.Core/release/Nethermind.Core.dll` + +## Platform Notes (Windows) + +On Windows, follow these rules to avoid argument parsing errors: + +- **Avoid quoted strings** around paths and type parameters — they can cause `'' was not matched` errors +- **Use forward slashes** in paths (works on both Windows and Linux) +- **Put the entire command on a single line** — avoid line continuations (`\`) +- Use absolute paths when possible for clarity + +```bash +# Good (Windows) +dotnet run --project tools/JitAsm -c Release -- -a D:/GitHub/nethermind/src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmStack -m PushBytesRef + +# Bad (may fail on Windows) +dotnet run --project tools/JitAsm -c Release -- \ + -a "src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll" \ + -t "Nethermind.Evm.EvmStack" \ + -m PushBytesRef +``` + +On Linux/macOS, both styles work fine. + +## Examples + +### Basic Usage + +```bash +# Disassemble a simple method (searches all types, default Tier-1 + PGO) +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m PushBytesRef +``` + +### With Type Specification + +```bash +# Specify the containing type to narrow the search +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmStack -m PushBytesRef +``` + +### Generic Methods (Method-Level Type Parameters) + +Use `--type-params` for type parameters on the method itself: + +```bash +# PushBytes(...) where TTracingInst = OffFlag +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmStack -m PushBytes --type-params Nethermind.Core.OffFlag +``` + +### Generic Classes (Class-Level Type Parameters) + +Use `--class-type-params` when the containing class is generic. This is separate from `--type-params` which is for method-level generics. Generic type names can omit the backtick arity suffix (e.g., `TransactionProcessorBase` matches `TransactionProcessorBase`1`). + +```bash +# TransactionProcessorBase.Execute(...) +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.TransactionProcessing.TransactionProcessorBase -m Execute --class-type-params Nethermind.Evm.GasPolicy.EthereumGasPolicy --type-params Nethermind.Core.OffFlag +``` + +### Multiple Generic Parameters + +Use comma-separated type names (no spaces after commas): + +```bash +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m SomeGenericMethod --type-params System.Int32,System.String +``` + +### EVM Instruction Example + +Disassemble the MUL opcode implementation with specific gas policy and tracing flags: + +```bash +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmInstructions -m InstructionMath2Param --type-params Nethermind.Evm.GasPolicy.EthereumGasPolicy,Nethermind.Evm.EvmInstructions+OpMul,Nethermind.Core.OffFlag +``` + +This shows the JIT output for `InstructionMath2Param`: +- `EthereumGasPolicy` - Standard Ethereum gas accounting +- `OpMul` - The multiplication operation (nested type, use `+` syntax) +- `OffFlag` - Tracing disabled (allows dead code elimination of tracing paths) + +Other math operations can be viewed by replacing `OpMul` with: `OpAdd`, `OpSub`, `OpDiv`, `OpMod`, `OpSDiv`, `OpSMod`, `OpLt`, `OpGt`, `OpSLt`, `OpSGt`, `OpEq`, `OpAnd`, `OpOr`, `OpXor`. + +### Type Aliases + +Common C# type aliases are supported: + +```bash +# These are equivalent +--type-params int +--type-params System.Int32 +``` + +Supported aliases: `bool`, `byte`, `sbyte`, `char`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double`, `decimal`, `string`, `object`, `nint`, `nuint` + +### Verbose Mode + +```bash +# Show detailed output including cctor detection and compilation tier info +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m PushBytesRef -v +``` + +### Skip Static Constructor Detection + +```bash +# Single pass only (faster, but may show cctor overhead) +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m SomeMethod --skip-cctor-detection +``` + +### FullOpts vs Tier-1 Comparison + +Compare the same method under different compilation modes to see what Tier-1 + PGO eliminates: + +```bash +# Tier-1 + PGO (default) — production-representative +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.BlockExecutionContext -m GetBlobBaseFee > tier1.asm 2>&1 + +# FullOpts — single-pass, no PGO +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.BlockExecutionContext -m GetBlobBaseFee --fullopts > fullopts.asm 2>&1 + +# Compare +diff tier1.asm fullopts.asm +``` + +Example result for `GetBlobBaseFee`: +- **FullOpts**: 356 bytes, has `CORINFO_HELP_GET_NONGCSTATIC_BASE` +- **Tier-1 + PGO**: 236 bytes (34% smaller), helper eliminated entirely + +## Example Output + +Default Tier-1 + PGO output: + +``` +; Assembly listing for method Namespace.Type:Method() (Tier1) +; Emitting BLENDED_CODE for generic X64 + VEX + EVEX on Windows +; Tier1 code ← Tier-1 recompilation (production tier) +; optimized code +; optimized using Dynamic PGO +; rsp based frame +; partially interruptible +; with Dynamic PGO: fgCalledCount is 50 + +G_M000_IG01: ;; offset=0x0000 + sub rsp, 40 + ... + +; Total bytes of code 236 +``` + +FullOpts output (with `--fullopts`): + +``` +; Assembly listing for method Namespace.Type:Method() (FullOpts) +; FullOpts code ← Single-pass FullOpts +; optimized code +; No PGO data ← No profile data available + ... + +; Total bytes of code 356 +``` + +## Instruction Annotations + +Per-instruction performance data from [uops.info](https://uops.info) is included **by default**, showing throughput, latency, micro-op count, and execution port usage inline with the assembly output. Use `--no-annotate` to disable. + +### Setup + +Download the uops.info instruction database (110MB, one-time): + +```bash +curl -o tools/JitAsm/instructions.xml https://uops.info/instructions.xml +``` + +On first use, the XML is preprocessed into a compact binary cache (`instructions.db`, ~1-2MB). Subsequent runs load from the cache. + +### Usage + +```bash +# Annotations are on by default (zen4) +dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName + +# Use a different architecture +dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName --arch alder-lake + +# Disable annotations +dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName --no-annotate +``` + +### Annotation Format + +Each instruction line gets an end-of-line annotation: + +``` + add rax, rcx ; [TP:0.25 | Lat: 1 | Uops:1] + mov qword ptr [rbp+10h], rax ; [TP:0.50 | Lat:11 | Uops:1] + vmovdqu ymm0, ymmword ptr [rsi] ; [TP:0.50 | Lat: 8 | Uops:1 | 1*FP_LD] +``` + +| Field | Meaning | +|-------|---------| +| `TP` | Reciprocal throughput (cycles per instruction). Lower = faster. | +| `Lat` | Latency (cycles from input ready to output ready). Matters for dependency chains. | +| `Uops` | Micro-op count. Fewer = less pressure on the execution engine. | +| Ports | Execution port usage (e.g., `1*p0156`). Shows which functional units are used. | + +### Supported Architectures + +| `--arch` value | CPU | Description | +|----------------|-----|-------------| +| `zen4` (default) | AMD Zen 4 | Ryzen 7000 / EPYC 9004 | +| `zen3` | AMD Zen 3 | Ryzen 5000 / EPYC 7003 | +| `zen2` | AMD Zen 2 | Ryzen 3000 / EPYC 7002 | +| `alder-lake` | Intel ADL-P | 12th Gen Core (P-cores) | +| `rocket-lake` | Intel RKL | 11th Gen Core | +| `ice-lake` | Intel ICL | 10th Gen Core | +| `tiger-lake` | Intel TGL | 11th Gen Mobile | +| `skylake` | Intel SKL | 6th Gen Core | + +When data for the selected architecture is unavailable for a specific instruction, the tool falls back to a nearby architecture in the same family. + +### Annotation Details + +**Mnemonic mapping:** The .NET JIT uses Intel-style mnemonic aliases (e.g., `je`, `jne`, `ja`) while uops.info uses canonical forms (`jz`, `jnz`, `jnbe`). The annotator automatically maps between them for conditional jumps, conditional moves (`cmove`→`cmovz`), and set-byte instructions (`sete`→`setz`). + +**LEA handling:** The `lea` instruction computes an address but doesn't access memory. Its second operand is classified as `agen` (address generation) rather than a memory load, matching the uops.info encoding. + +**.NET JIT-specific prefixes:** +- `gword ptr` — GC-tracked pointer-width memory reference. Treated as `m64` on x64. +- `bword ptr` — Pointer-width memory reference without GC tracking. Treated as `m64` on x64. +- `SHORT` / `NEAR` — Jump distance hints from the JIT. Stripped before operand classification. + +**Skipped instructions:** `call`, `ret`, `int3`, `nop`, and `int` are not annotated. `ret` is skipped because uops.info's `TP_unrolled` for `ret` reflects return stack buffer mispredictions in microbenchmarks, not real-world performance. + +## Iterative Workflow + +JitAsm is designed for rapid iteration when optimizing code: + +1. **Modify source code** +2. **Build the target assembly:** + ```bash + dotnet build src/Nethermind/Nethermind.Evm -c Release + ``` +3. **View the generated assembly:** + ```bash + dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m YourMethod + ``` +4. **Compare output between iterations** + +For fast iteration during optimization, `--fullopts` skips the invocation/sleep cycle (~4s faster) at the cost of less representative output. Use default Tier-1 for final verification. + +## Environment Variables + +The tool sets these JIT diagnostic variables for the child process: + +### Default Mode (Tier-1 + PGO) + +| Variable | Value | Purpose | +|----------|-------|---------| +| `DOTNET_TieredCompilation` | `1` | Enable tiered compilation | +| `DOTNET_TieredPGO` | `1` | Enable Dynamic PGO | +| `DOTNET_TC_CallCountThreshold` | `1` | Minimal call count before recompilation | +| `DOTNET_TC_CallCountingDelayMs` | `0` | Install counting stubs immediately (critical) | +| `DOTNET_JitDisasm` | `` | Output disassembly for matching methods | +| `DOTNET_JitDiffableDasm` | `1` | Consistent, diffable output format | + +### FullOpts Mode (`--fullopts`) + +| Variable | Value | Purpose | +|----------|-------|---------| +| `DOTNET_TieredCompilation` | `0` | Disable tiered compilation | +| `DOTNET_TC_QuickJit` | `0` | Disable quick JIT | +| `DOTNET_JitDisasm` | `` | Output disassembly for matching methods | +| `DOTNET_JitDiffableDasm` | `1` | Consistent, diffable output format | + +## Comparing Generic Specializations (OffFlag vs OnFlag) + +A common workflow is comparing two generic instantiations to verify dead code elimination. Nethermind uses the `IFlag` pattern (`OnFlag`/`OffFlag`) for compile-time branch elimination. + +```bash +# Generate ASM for tracing disabled (common case) +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmInstructions -m InstructionMath2Param --type-params Nethermind.Evm.GasPolicy.EthereumGasPolicy,Nethermind.Evm.EvmInstructions+OpAdd,Nethermind.Core.OffFlag > off.asm 2>&1 + +# Generate ASM for tracing enabled +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmInstructions -m InstructionMath2Param --type-params Nethermind.Evm.GasPolicy.EthereumGasPolicy,Nethermind.Evm.EvmInstructions+OpAdd,Nethermind.Core.OnFlag > on.asm 2>&1 + +# Compare: the OnFlag version should have tracing code that is absent from OffFlag +diff off.asm on.asm +``` + +**Red flag:** If both versions have identical code size, the JIT may not be eliminating dead code as expected. + +## Extracting Metrics from ASM Output + +Use these patterns to extract key metrics from the disassembly output for tracking optimization progress: + +```bash +# Code size (from the last line, e.g., "; Total bytes of code 40") +tail -1 output.asm + +# Basic block count (fewer = simpler control flow = better branch prediction) +grep -c "G_M000_IG[0-9]*:" output.asm + +# Branch count (conditional jumps) +grep -cE "\bj(e|ne|g|l|ge|le|a|b|ae|be|nz|z)\b" output.asm + +# Call count (should be minimal in hot paths) +grep -c "call" output.asm + +# Register saves in prologue (>4 push = register pressure issue) +grep -c "push" output.asm + +# Stack frame size (from prologue, e.g., "sub rsp, 40") +grep "sub.*rsp" output.asm +``` + +## Reading Assembly Output + +### Output Structure + +The disassembly header contains useful metadata: + +``` +; Assembly listing for method Namespace.Type:Method() (Tier1) +; Emitting BLENDED_CODE for generic X64 + VEX + EVEX on Windows +; Tier1 code ← Tier-1 optimized (production tier) +; optimized code +; optimized using Dynamic PGO ← PGO-guided optimizations applied +; rsp based frame +; partially interruptible +; with Dynamic PGO: fgCalledCount is 50 ← How many times method was called during profiling +``` + +The code is organized into **basic blocks** labeled `G_M000_IG01`, `G_M000_IG02`, etc. Each block is a straight-line sequence of instructions ending with a branch or fall-through. + +### Common Red Flags + +| Pattern in ASM | Meaning | Severity | +|----------------|---------|----------| +| `call CORINFO_HELP_BOX` | Boxing allocation in hot path | High | +| `call CORINFO_HELP_VIRTUAL_FUNC_PTR` | Virtual dispatch not devirtualized (~25-50 cycles) | High | +| `call CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE` | Static constructor check | High | +| `call CORINFO_HELP_GET_NONGCSTATIC_BASE` | Cross-module static base resolution (eliminated in Tier-1) | Medium* | +| `call CORINFO_HELP_ASSIGN_REF` | GC write barrier (bad in loops) | High | +| `callvirt [Interface:Method()]` | Interface dispatch not devirtualized | High | +| `call [SmallType:SmallMethod()]` | Failed inlining of small method | Medium | +| `cmp ...; jae THROW_LABEL` | Bounds check (may be eliminable) | Medium | +| `idiv` | Division by non-constant (~20-80 cycles) | Medium | +| `>4 push` instructions in prologue | Register pressure | Medium | +| `vmovdqu32 zmmword ptr` in prologue | Large stack zeroing (cold locals bloating hot path) | Low | + +*`CORINFO_HELP_GET_NONGCSTATIC_BASE` appears in FullOpts mode for cross-module static field access but is naturally eliminated in Tier-1 + PGO. If you see it in default (Tier-1) output, it indicates the static base couldn't be resolved at runtime — a real issue. + +### What Good ASM Looks Like + +- Few basic blocks (simple control flow) +- No `call` instructions in the hot path (everything inlined) +- Compact code size (better I-cache utilization) +- No redundant loads of the same memory location +- SIMD instructions (`vpaddd`, `vpxor`, etc.) for bulk data operations +- `cmov*` (conditional moves) instead of branches where appropriate +- Fall-through on the hot path, forward jumps to cold/error paths +- `; optimized using Dynamic PGO` in the header (confirms PGO applied) + +## Troubleshooting + +### No disassembly output + +- Verify the assembly path is correct and the DLL exists +- Check if the method name is spelled correctly (case-sensitive) +- Try without the `-t` option to search all types +- Use `-v` for verbose output to see what's happening +- Ensure you built with `-c Release` (Debug builds produce different code) + +### Tier-1 produces no output / shows Tier-0 instead + +- The Tier-1 simulation invokes the method via reflection with default arguments. Methods taking **ref struct** parameters (`Span`, `ReadOnlySpan`, `Memory`) cannot be invoked this way — the simulation silently fails and produces no output. +- **Workaround:** Use `--fullopts` for methods with ref struct parameters. It compiles in a single pass without invocation. +- For other methods: this means the Tier-1 recompilation didn't fire. The method may not be invocable with null/default arguments. +- Check `-v` output for error messages during invocation phases. + +### Method not found + +- Ensure the assembly is built (Release configuration recommended) +- For generic methods, provide all required type parameters via `--type-params` +- For methods in generic classes, provide class type parameters via `--class-type-params` +- Use fully qualified type names for type parameters from other assemblies +- Generic type names can omit the backtick arity (e.g., `TransactionProcessorBase` matches `TransactionProcessorBase`1`) + +### Multiple methods with same name + +- Specify the type with `-t` to narrow the search +- The tool will use the first matching overload if multiple exist + +### On Windows: `'' was not matched` error + +- Remove all quotes from arguments — pass paths and type names unquoted +- Replace backslashes with forward slashes in paths +- Put the entire command on a single line + +## Building + +```bash +dotnet build tools/JitAsm -c Release +``` diff --git a/tools/JitAsm/StaticCtorDetector.cs b/tools/JitAsm/StaticCtorDetector.cs new file mode 100644 index 000000000000..1b512dceb957 --- /dev/null +++ b/tools/JitAsm/StaticCtorDetector.cs @@ -0,0 +1,175 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.RegularExpressions; + +namespace JitAsm; + +internal static partial class StaticCtorDetector +{ + // Patterns for detecting static constructor calls in JIT disassembly + // Example: call Namespace.Type:.cctor() + [GeneratedRegex(@"call\s+(?[\w.+`\[\],]+):\.cctor\(\)", RegexOptions.Compiled)] + private static partial Regex CctorCallPattern(); + + // JIT helpers for static field initialization + // Matches both shared and non-shared variants, with optional brackets: + // call CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE + // call [CORINFO_HELP_GET_NONGCSTATIC_BASE] + // call CORINFO_HELP_CLASSINIT_SHARED_DYNAMICCLASS + [GeneratedRegex(@"call\s+\[?CORINFO_HELP_(?:(?:GETSHARED_|GET_)(?:NON)?GCSTATIC_BASE|CLASSINIT_SHARED_DYNAMICCLASS)\]?", RegexOptions.Compiled)] + private static partial Regex StaticHelperPattern(); + + // Pattern for type references in static helper context + // The JIT output often shows the type being initialized nearby + // Example: ; Namespace.Type + [GeneratedRegex(@";\s*(?[\w.+`\[\],]+)\s*$", RegexOptions.Compiled | RegexOptions.Multiline)] + private static partial Regex TypeCommentPattern(); + + // Pattern for detecting lazy initialization checks + // Example: cmp dword ptr [Namespace.Type:initialized], 0 + [GeneratedRegex(@"\[(?[\w.+`\[\],]+):", RegexOptions.Compiled)] + private static partial Regex StaticFieldAccessPattern(); + + // Pattern for extracting types from call targets in JIT output. + // In diffable mode, long call targets wrap to the next line, e.g.: + // call + // [Nethermind.Core.Eip4844Constants:get_MinBlobGasPrice():...] + // In non-diffable mode, they appear on one line: + // call [Nethermind.Core.Eip4844Constants:get_MinBlobGasPrice():...] + // This pattern matches the "[Type:Method" portion (possibly with [r11] prefix for callvirt). + [GeneratedRegex(@"(?:\[(?:r\d+\])?)?\[?(?[\w.+`\[\],]+):(?!:)", RegexOptions.Compiled)] + private static partial Regex CallTargetTypePattern(); + + public static IReadOnlyList DetectStaticCtors(string disassemblyOutput) + { + var detectedTypes = new HashSet(StringComparer.Ordinal); + + // Detect direct .cctor calls + foreach (Match match in CctorCallPattern().Matches(disassemblyOutput)) + { + var typeName = NormalizeTypeName(match.Groups["type"].Value); + if (!string.IsNullOrEmpty(typeName)) + { + detectedTypes.Add(typeName); + } + } + + // Check for static helper calls and try to find associated types + if (StaticHelperPattern().IsMatch(disassemblyOutput)) + { + int typesBeforeHelperScan = detectedTypes.Count; + + // Look for type references near the helper calls (±3 lines) + var lines = disassemblyOutput.Split('\n'); + for (int i = 0; i < lines.Length; i++) + { + if (StaticHelperPattern().IsMatch(lines[i])) + { + // Check surrounding lines for type context + for (int j = Math.Max(0, i - 3); j <= Math.Min(lines.Length - 1, i + 3); j++) + { + var typeMatch = TypeCommentPattern().Match(lines[j]); + if (typeMatch.Success) + { + var typeName = NormalizeTypeName(typeMatch.Groups["type"].Value); + if (!string.IsNullOrEmpty(typeName) && IsValidTypeName(typeName)) + { + detectedTypes.Add(typeName); + } + } + + var fieldMatch = StaticFieldAccessPattern().Match(lines[j]); + if (fieldMatch.Success) + { + var typeName = NormalizeTypeName(fieldMatch.Groups["type"].Value); + if (!string.IsNullOrEmpty(typeName) && IsValidTypeName(typeName)) + { + detectedTypes.Add(typeName); + } + } + } + } + } + + // Fallback: if helpers were found but no types extracted from nearby context + // (common when JitDiffableDasm strips annotations or helper has no nearby type comment), + // scan the entire method for types referenced in call instructions. + // In diffable mode, long call targets wrap to the next line: + // call + // [Nethermind.Core.Eip4844Constants:get_MinBlobGasPrice():...] + // Pre-running extra cctors is cheap; missing the right one means stale output. + if (detectedTypes.Count == typesBeforeHelperScan) + { + for (int i = 0; i < lines.Length; i++) + { + var line = lines[i]; + // Check current line and continuation lines after bare "call" instructions + if (line.TrimStart().StartsWith("call") || (i > 0 && lines[i - 1].TrimEnd().EndsWith("call"))) + { + var callMatch = CallTargetTypePattern().Match(line); + if (callMatch.Success) + { + var typeName = NormalizeTypeName(callMatch.Groups["type"].Value); + if (!string.IsNullOrEmpty(typeName) && IsValidTypeName(typeName)) + { + detectedTypes.Add(typeName); + } + } + } + } + } + } + + var result = new List(detectedTypes); + result.Sort(StringComparer.Ordinal); + return result; + } + + private static string NormalizeTypeName(string typeName) + { + // Remove generic arity suffix if present (e.g., `1, `2) + var tickIndex = typeName.IndexOf('`'); + if (tickIndex > 0) + { + // Keep up to and including the backtick and number for proper type resolution + var endIndex = tickIndex + 1; + while (endIndex < typeName.Length && char.IsDigit(typeName[endIndex])) + { + endIndex++; + } + + // If there's more after the generic arity, check for nested types + if (endIndex < typeName.Length && typeName[endIndex] == '+') + { + // Keep nested type info + return typeName; + } + + return typeName[..endIndex]; + } + + return typeName.Trim(); + } + + private static bool IsValidTypeName(string typeName) + { + // Filter out obvious non-type names + if (string.IsNullOrWhiteSpace(typeName)) + return false; + + // Must contain at least one letter + if (!typeName.Any(char.IsLetter)) + return false; + + // Should not be just a keyword or register name + var lowered = typeName.ToLowerInvariant(); + string[] invalidNames = ["rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rsp", "rbp", + "eax", "ebx", "ecx", "edx", "esi", "edi", "esp", "ebp", + "ptr", "dword", "qword", "byte", "word"]; + if (invalidNames.Contains(lowered)) + return false; + + return true; + } +} diff --git a/tools/Kute/Dockerfile b/tools/Kute/Dockerfile index 6d147b2338ed..4cf8b4497086 100644 --- a/tools/Kute/Dockerfile +++ b/tools/Kute/Dockerfile @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited # SPDX-License-Identifier: LGPL-3.0-only -FROM mcr.microsoft.com/dotnet/sdk:9.0-noble AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0-noble AS build ARG BUILD_CONFIG=release ARG TARGETARCH @@ -11,7 +11,7 @@ COPY . . RUN arch=$([ "$TARGETARCH" = "amd64" ] && echo "x64" || echo "$TARGETARCH") && \ dotnet publish tools/Kute/Nethermind.Tools.Kute -c $BUILD_CONFIG -a $arch -o out --sc -FROM mcr.microsoft.com/dotnet/runtime-deps:9.0-noble +FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-noble WORKDIR /nethermind diff --git a/tools/Kute/Nethermind.Tools.Kute/Application.cs b/tools/Kute/Nethermind.Tools.Kute/Application.cs index 5c63ec1dce15..c18db234e223 100644 --- a/tools/Kute/Nethermind.Tools.Kute/Application.cs +++ b/tools/Kute/Nethermind.Tools.Kute/Application.cs @@ -45,7 +45,7 @@ public async Task Run(CancellationToken token = default) var totalTimer = new Timer(); using (totalTimer.Time()) { - await _processor.Process(_msgProvider.Messages(token), async (jsonRpc) => + await _processor.Process(_msgProvider.Messages(token), async jsonRpc => { await ProcessRpc(jsonRpc, token); }, token); diff --git a/tools/Kute/Nethermind.Tools.Kute/Config.cs b/tools/Kute/Nethermind.Tools.Kute/Config.cs index c73bd1475490..7298564cd932 100644 --- a/tools/Kute/Nethermind.Tools.Kute/Config.cs +++ b/tools/Kute/Nethermind.Tools.Kute/Config.cs @@ -15,39 +15,37 @@ public static class Config Required = true, }; - public static Option HostAddress { get; } = new("--address", "-a") + public static Option JwtSecretFilePath { get; } = new("--secret", "-s") { - DefaultValueFactory = r => "http://localhost:8551", - Description = "Address where to send JSON RPC requests", - HelpName = "url", + Description = "Path to file with hex-encoded secret for JWT authentication", + HelpName = "path", Required = true, }; - public static Option JwtSecretFilePath { get; } = new("--secret", "-s") + public static Option HostAddress { get; } = new("--address", "-a") { - Description = "Path to file with hex-encoded secret for JWT authentication", - HelpName = "path", - Required = true + Description = "Address where to send JSON RPC requests", + HelpName = "url", + DefaultValueFactory = _ => "http://localhost:8551", }; public static Option AuthTtl { get; } = new("--ttl", "-t") { - DefaultValueFactory = r => 60, Description = "Authentication time to live (TTL), in seconds", - HelpName = "number" + HelpName = "number", + DefaultValueFactory = _ => 60, }; public static Option ShowProgress { get; } = new("--progress", "-p") { Description = "Show progress", - DefaultValueFactory = r => false, + DefaultValueFactory = _ => false, }; public static Option MetricsReportFormatter { get; } = new("--output", "-o") { - DefaultValueFactory = r => MetricsReportFormat.Pretty, Description = "Strategy to report metrics", - HelpName = "path", + DefaultValueFactory = _ => MetricsReportFormat.Pretty, }; public static Option> MethodFilters { get; } = new("--filters", "-f") @@ -55,20 +53,20 @@ public static class Config Description = "A comma separated List of regexes of methods to be executed with optional limits", HelpName = "regexes", CustomParser = r => r.Tokens.Count == 1 ? r.Tokens[0].Value.Split(',') : null, - DefaultValueFactory = r => [], + DefaultValueFactory = _ => [], }; public static Option ResponsesTraceFile { get; } = new("--responses", "-r") { Description = "Path to file to store JSON-RPC responses", - HelpName = "path" + HelpName = "path", }; public static Option ConcurrentRequests { get; } = new("--concurrency", "-c") { Description = "Process at most request concurrently", HelpName = "number", - DefaultValueFactory = r => 1 + DefaultValueFactory = _ => 1, }; public static Option PrometheusPushGateway { get; } = new("--gateway", "-g") @@ -91,12 +89,12 @@ 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") { - DefaultValueFactory = r => new Dictionary(), + DefaultValueFactory = _ => new Dictionary(), CustomParser = r => { var labels = new Dictionary(); diff --git a/tools/Kute/Nethermind.Tools.Kute/JsonRpc.cs b/tools/Kute/Nethermind.Tools.Kute/JsonRpc.cs index b2ebf92f60be..783d8e6ee9a2 100644 --- a/tools/Kute/Nethermind.Tools.Kute/JsonRpc.cs +++ b/tools/Kute/Nethermind.Tools.Kute/JsonRpc.cs @@ -7,7 +7,6 @@ namespace Nethermind.Tools.Kute; public abstract class JsonRpc { - public abstract string? Id { get; } private readonly JsonNode _node; private JsonRpc(JsonNode node) @@ -28,14 +27,14 @@ public class Single : Request private readonly Lazy _id; private readonly Lazy _methodName; - public override string? Id { get => _id.Value; } + public string? Id { get => _id.Value; } public string? MethodName { get => _methodName.Value; } public Single(JsonNode node) : base(node) { _id = new(() => { - if (_node["id"] is JsonNode id) + if (_node["id"] is { } id) { return ((Int64)id).ToString(); } @@ -44,7 +43,7 @@ public Single(JsonNode node) : base(node) }); _methodName = new(() => { - if (_node["method"] is JsonNode method) + if (_node["method"] is { } method) { return (string?)method; } @@ -60,7 +59,7 @@ public class Batch : Request { private readonly Lazy _id; - public override string? Id { get => _id.Value; } + public string? Id { get => _id.Value; } public Batch(JsonNode node) : base(node) { @@ -68,8 +67,8 @@ public Batch(JsonNode node) : base(node) { if (Items().Any()) { - var first = Items().First()?.Id?.ToString(); - var last = Items().Last()?.Id?.ToString(); + var first = Items().First()?.Id; + var last = Items().Last()?.Id; if (first is not null && last is not null) { @@ -97,13 +96,13 @@ public class Response : JsonRpc { private readonly Lazy _id; - public override string? Id { get => _id.Value; } + public string? Id { get => _id.Value; } public Response(JsonNode node) : base(node) { _id = new(() => { - if (_node["id"] is JsonNode id) + if (_node["id"] is { } id) { return ((Int64)id).ToString(); } diff --git a/tools/Kute/Nethermind.Tools.Kute/JsonRpcSubmitter/HttpJsonRpcSubmitter.cs b/tools/Kute/Nethermind.Tools.Kute/JsonRpcSubmitter/HttpJsonRpcSubmitter.cs index f11fe59eecb3..c0e8872dc6e7 100644 --- a/tools/Kute/Nethermind.Tools.Kute/JsonRpcSubmitter/HttpJsonRpcSubmitter.cs +++ b/tools/Kute/Nethermind.Tools.Kute/JsonRpcSubmitter/HttpJsonRpcSubmitter.cs @@ -29,7 +29,7 @@ public HttpJsonRpcSubmitter(HttpClient httpClient, IAuth auth, string hostAddres Content = new StringContent(rpc.ToJsonString(), Encoding.UTF8, MediaTypeNames.Application.Json), }; - var httpResponse = await _httpClient.SendAsync(request, token); + using HttpResponseMessage httpResponse = await _httpClient.SendAsync(request, token); return await JsonRpc.Response.FromHttpResponseAsync(httpResponse, token); } } diff --git a/tools/Kute/Nethermind.Tools.Kute/JsonRpcValidator/BatchJsonRpcValidator.cs b/tools/Kute/Nethermind.Tools.Kute/JsonRpcValidator/BatchJsonRpcValidator.cs index f71558aff938..31e534f0d5aa 100644 --- a/tools/Kute/Nethermind.Tools.Kute/JsonRpcValidator/BatchJsonRpcValidator.cs +++ b/tools/Kute/Nethermind.Tools.Kute/JsonRpcValidator/BatchJsonRpcValidator.cs @@ -46,7 +46,7 @@ public bool IsValid(JsonRpc.Request request, JsonRpc.Response response) foreach (var (req, res) in requests.Zip(responses)) { if (req.Id != res.Id) return false; - if (_singleValidator.IsInvalid(req!, res)) return false; + if (_singleValidator.IsInvalid(req, res)) return false; } return true; diff --git a/tools/Kute/Nethermind.Tools.Kute/JsonRpcValidator/Eth/NewPayloadJsonRpcValidator.cs b/tools/Kute/Nethermind.Tools.Kute/JsonRpcValidator/Eth/NewPayloadJsonRpcValidator.cs index b069f55ff219..1a58b08c2c35 100644 --- a/tools/Kute/Nethermind.Tools.Kute/JsonRpcValidator/Eth/NewPayloadJsonRpcValidator.cs +++ b/tools/Kute/Nethermind.Tools.Kute/JsonRpcValidator/Eth/NewPayloadJsonRpcValidator.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Text.Json.Nodes; using System.Text.RegularExpressions; namespace Nethermind.Tools.Kute.JsonRpcValidator.Eth; @@ -13,12 +12,12 @@ public sealed class NewPayloadJsonRpcValidator : IJsonRpcValidator public bool IsValid(JsonRpc.Request request, JsonRpc.Response response) { // If preconditions are not met, then mark it as Valid. - if (!ShouldValidateRequest(request) || response is null) + if (!ShouldValidateRequest(request)) { return true; } - if (response.Json["result"]?["status"] is JsonNode status) + if (response.Json["result"]?["status"] is { } status) { return (string?)status == "VALID"; } diff --git a/tools/Kute/Nethermind.Tools.Kute/Metrics/ComposedMetricsReporter.cs b/tools/Kute/Nethermind.Tools.Kute/Metrics/ComposedMetricsReporter.cs index 06ce10f1ad9c..7bf904fa8644 100644 --- a/tools/Kute/Nethermind.Tools.Kute/Metrics/ComposedMetricsReporter.cs +++ b/tools/Kute/Nethermind.Tools.Kute/Metrics/ComposedMetricsReporter.cs @@ -7,7 +7,6 @@ public sealed class ComposedMetricsReporter : IMetricsReporter { private readonly IMetricsReporter[] _reporters; - public ComposedMetricsReporter(params IMetricsReporter[] reporters) { _reporters = reporters; diff --git a/tools/Kute/Nethermind.Tools.Kute/Metrics/ConsoleTotalReporter.cs b/tools/Kute/Nethermind.Tools.Kute/Metrics/ConsoleTotalReporter.cs index 2108356318a1..604b7e17d2d0 100644 --- a/tools/Kute/Nethermind.Tools.Kute/Metrics/ConsoleTotalReporter.cs +++ b/tools/Kute/Nethermind.Tools.Kute/Metrics/ConsoleTotalReporter.cs @@ -17,9 +17,7 @@ public ConsoleTotalReporter(IMetricsReportProvider provider, IMetricsReportForma public async Task Total(TimeSpan elapsed, CancellationToken token = default) { var report = _provider.Report(); - using (var stream = Console.OpenStandardOutput()) - { - await _formatter.WriteAsync(stream, report, token); - } + await using var stream = Console.OpenStandardOutput(); + await _formatter.WriteAsync(stream, report, token); } } diff --git a/tools/Kute/Nethermind.Tools.Kute/Metrics/HtmlMetricsReportFormatter.cs b/tools/Kute/Nethermind.Tools.Kute/Metrics/HtmlMetricsReportFormatter.cs index aa545233ae88..f0cf0e816047 100644 --- a/tools/Kute/Nethermind.Tools.Kute/Metrics/HtmlMetricsReportFormatter.cs +++ b/tools/Kute/Nethermind.Tools.Kute/Metrics/HtmlMetricsReportFormatter.cs @@ -26,7 +26,7 @@ public async Task WriteAsync(Stream stream, MetricsReport report, CancellationTo await JsonSerializer.SerializeAsync(stream, report, cancellationToken: token); await stream.WriteAsync(_encoding.GetBytes("\n\n"), token); - using var resourceStream = _assembly.GetManifestResourceStream(_reportTemplate)!; + await using var resourceStream = _assembly.GetManifestResourceStream(_reportTemplate)!; await resourceStream.CopyToAsync(stream, token); } } diff --git a/tools/Kute/Nethermind.Tools.Kute/Metrics/MemoryMetricsReporter.cs b/tools/Kute/Nethermind.Tools.Kute/Metrics/MemoryMetricsReporter.cs index 268e08e18ff1..98fa4ce8b87d 100644 --- a/tools/Kute/Nethermind.Tools.Kute/Metrics/MemoryMetricsReporter.cs +++ b/tools/Kute/Nethermind.Tools.Kute/Metrics/MemoryMetricsReporter.cs @@ -24,9 +24,10 @@ public sealed class MemoryMetricsReporter public Task Succeeded(CancellationToken token = default) => Task.FromResult(Interlocked.Increment(ref _succeeded)); public Task Failed(CancellationToken token = default) => Task.FromResult(Interlocked.Increment(ref _failed)); public Task Ignored(CancellationToken token = default) => Task.FromResult(Interlocked.Increment(ref _ignored)); + public Task Batch(JsonRpc.Request.Batch batch, TimeSpan elapsed, CancellationToken token = default) { - var id = batch.Id?.ToString(); + var id = batch.Id; if (id is not null) { _batches[id] = elapsed; @@ -34,21 +35,25 @@ public Task Batch(JsonRpc.Request.Batch batch, TimeSpan elapsed, CancellationTok return Task.CompletedTask; } + public Task Single(JsonRpc.Request.Single single, TimeSpan elapsed, CancellationToken token = default) { var id = single.Id; var methodName = single.MethodName; if (id is not null && methodName is not null) { - var newMethodDict = new ConcurrentDictionary(); - newMethodDict[id] = elapsed; - _singles.AddOrUpdate(methodName, - newMethodDict, - (_, dict) => + var newMethodDict = new ConcurrentDictionary { - dict[id] = elapsed; - return dict; - }); + [id] = elapsed, + }; + _singles.AddOrUpdate( + methodName, + newMethodDict, + (_, dict) => + { + dict[id] = elapsed; + return dict; + }); } return Task.CompletedTask; @@ -69,7 +74,11 @@ public MetricsReport Report() Ignored = _ignored, Responses = _responses, TotalTime = _totalRunningTime, - Singles = _singles.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyDictionary)kvp.Value.ToDictionary(ikvp => ikvp.Key, ikvp => ikvp.Value)), + Singles = _singles.ToDictionary( + kvp => kvp.Key, + IReadOnlyDictionary (kvp) => kvp.Value.ToDictionary( + ikvp => ikvp.Key, + ikvp => ikvp.Value)), Batches = _batches.ToDictionary(kvp => kvp.Key, kvp => kvp.Value), }; } diff --git a/tools/Kute/Nethermind.Tools.Kute/Metrics/MetricsReport.cs b/tools/Kute/Nethermind.Tools.Kute/Metrics/MetricsReport.cs index bf0fbd177f1f..b3e36d869e65 100644 --- a/tools/Kute/Nethermind.Tools.Kute/Metrics/MetricsReport.cs +++ b/tools/Kute/Nethermind.Tools.Kute/Metrics/MetricsReport.cs @@ -57,10 +57,10 @@ public sealed record MetricsReport public required IReadOnlyDictionary> Singles { get; init; } public required IReadOnlyDictionary Batches { get; init; } - // Computed properties private Dictionary? _singlesMetrics; - private TimeMetrics? _batchesMetrics; public Dictionary SinglesMetrics => _singlesMetrics ??= Singles.ToDictionary(kvp => kvp.Key, kvp => TimeMetrics.From(kvp.Value.Values.ToList())); + + private TimeMetrics? _batchesMetrics; public TimeMetrics BatchesMetrics => _batchesMetrics ??= TimeMetrics.From(Batches.Values.ToList()); } diff --git a/tools/Kute/Nethermind.Tools.Kute/Metrics/PrettyMetricsReportFormatter.cs b/tools/Kute/Nethermind.Tools.Kute/Metrics/PrettyMetricsReportFormatter.cs index 3fb592549506..516b3f388c3a 100644 --- a/tools/Kute/Nethermind.Tools.Kute/Metrics/PrettyMetricsReportFormatter.cs +++ b/tools/Kute/Nethermind.Tools.Kute/Metrics/PrettyMetricsReportFormatter.cs @@ -7,7 +7,7 @@ public sealed class PrettyMetricsReportFormatter : IMetricsReportFormatter { public async Task WriteAsync(Stream stream, MetricsReport report, CancellationToken token = default) { - using var writer = new StreamWriter(stream); + await using var writer = new StreamWriter(stream); await writer.WriteLineAsync("=== Report ===", token); await writer.WriteLineAsync($"Total Time: {report.TotalTime.TotalSeconds} s", token); await writer.WriteLineAsync($"Total Messages: {report.TotalMessages}\n", token); @@ -16,10 +16,8 @@ public async Task WriteAsync(Stream stream, MetricsReport report, CancellationTo await writer.WriteLineAsync($"Ignored: {report.Ignored}", token); await writer.WriteLineAsync($"Responses: {report.Responses}\n", token); await writer.WriteLineAsync("Singles:", token); - foreach (var single in report.SinglesMetrics) + foreach ((var methodName, var metrics) in report.SinglesMetrics) { - var methodName = single.Key; - var metrics = single.Value; await writer.WriteLineAsync($" {methodName}:", token); await writer.WriteLineAsync($" Count: {report.Singles[methodName].Count}", token); await writer.WriteLineAsync($" Max: {metrics.Max.TotalMilliseconds} ms", token); @@ -41,6 +39,6 @@ internal static class StreamWriterExt { public static async Task WriteLineAsync(this StreamWriter writer, string value, CancellationToken token) { - await writer.WriteLineAsync(MemoryExtensions.AsMemory(value), token); + await writer.WriteLineAsync(value.AsMemory(), token); } } diff --git a/tools/Kute/Nethermind.Tools.Kute/Metrics/PrometheusPushGatewayMetricsReporter.cs b/tools/Kute/Nethermind.Tools.Kute/Metrics/PrometheusPushGatewayMetricsReporter.cs index 1837a443892d..d5c9985bdddd 100644 --- a/tools/Kute/Nethermind.Tools.Kute/Metrics/PrometheusPushGatewayMetricsReporter.cs +++ b/tools/Kute/Nethermind.Tools.Kute/Metrics/PrometheusPushGatewayMetricsReporter.cs @@ -11,7 +11,8 @@ namespace Nethermind.Tools.Kute.Metrics; public sealed class PrometheusPushGatewayMetricsReporter : IMetricsReporter { - private readonly string _endpoint; + private const string JobName = "kute"; + private readonly MetricPusher _pusher; private readonly MetricPushServer _server; @@ -27,8 +28,7 @@ public PrometheusPushGatewayMetricsReporter( string endpoint, Dictionary labels, string? user, - string? password - ) + string? password) { var registry = new CollectorRegistry(); var factory = new MetricFactory(registry); @@ -38,21 +38,23 @@ public PrometheusPushGatewayMetricsReporter( _failedCounter = factory.CreateCounter(GetMetricName("messages_failed"), ""); _ignoredCounter = factory.CreateCounter(GetMetricName("messages_ignored"), ""); _responseCounter = factory.CreateCounter(GetMetricName("responses_total"), ""); - _singleDuration = factory.CreateHistogram(GetMetricName("single_duration_seconds"), "", labelNames: new[] { "jsonrpc_id", "method" }); - _batchDuration = factory.CreateHistogram(GetMetricName("batch_duration_seconds"), "", labelNames: new[] { "jsonrpc_id" }); + _singleDuration = factory.CreateHistogram(GetMetricName("single_duration_seconds"), "", labelNames: ["jsonrpc_id", "method"]); + _batchDuration = factory.CreateHistogram(GetMetricName("batch_duration_seconds"), "", labelNames: ["jsonrpc_id"]); - _endpoint = endpoint; string instanceLabel = labels.TryGetValue("instance", out var instance) ? instance : Guid.NewGuid().ToString(); labels.Remove("instance"); + var httpClient = new HttpClient(); if (user is not null && password is not null) { - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{user}:{password}"))); + var authParameter = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{user}:{password}")); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authParameter); } + _pusher = new MetricPusher(new MetricPusherOptions { CollectorRegistry = registry, - Endpoint = _endpoint, + Endpoint = endpoint, Job = JobName, Instance = instanceLabel, AdditionalLabels = labels, @@ -95,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; } @@ -115,10 +123,9 @@ public async Task Total(TimeSpan elapsed, CancellationToken token = default) _server.Stop(); } - public static string JobName => "kute"; - public static string GetMetricName(string name) + private static string GetMetricName(string name) { - var lowerName = name.ToLower(); + var lowerName = name.ToLowerInvariant(); var sanitizedName = lowerName.Replace(" ", "_").Replace("-", "_"); return $"{JobName}_{sanitizedName}"; } diff --git a/tools/Kute/Nethermind.Tools.Kute/Nethermind.Tools.Kute.csproj b/tools/Kute/Nethermind.Tools.Kute/Nethermind.Tools.Kute.csproj index 1a0e06c329ab..531343b30960 100644 --- a/tools/Kute/Nethermind.Tools.Kute/Nethermind.Tools.Kute.csproj +++ b/tools/Kute/Nethermind.Tools.Kute/Nethermind.Tools.Kute.csproj @@ -8,7 +8,6 @@ - diff --git a/tools/Kute/Nethermind.Tools.Kute/Program.cs b/tools/Kute/Nethermind.Tools.Kute/Program.cs index 609bb9c17354..e4bd6d9bcfa1 100644 --- a/tools/Kute/Nethermind.Tools.Kute/Program.cs +++ b/tools/Kute/Nethermind.Tools.Kute/Program.cs @@ -14,18 +14,19 @@ using Nethermind.Tools.Kute.SecretProvider; using Nethermind.Tools.Kute.SystemClock; using System.CommandLine; +using System.Net; namespace Nethermind.Tools.Kute; -static class Program +public static class Program { public static async Task Main(string[] args) { RootCommand rootCommand = [ - Config.HostAddress, Config.JwtSecretFilePath, Config.MessagesFilePath, + Config.HostAddress, Config.ResponsesTraceFile, Config.MethodFilters, Config.MetricsReportFormatter, @@ -56,7 +57,18 @@ private static IServiceProvider BuildServiceProvider(ParseResult parseResult) collection.AddSingleton(); collection.AddSingleton(); - collection.AddSingleton(); + collection.AddSingleton(_ => + { + // Disable proxy auto-detection to avoid timeout (~2s) on Windows. + // Each Kute invocation is a separate process, so the proxy lookup + // would otherwise be paid on every single measured request. + var handler = new SocketsHttpHandler + { + UseProxy = false, + AutomaticDecompression = DecompressionMethods.None, + }; + return new HttpClient(handler); + }); collection.AddSingleton(new FileSecretProvider(parseResult.GetValue(Config.JwtSecretFilePath)!)); collection.AddSingleton(provider => new TtlAuth( @@ -68,7 +80,7 @@ private static IServiceProvider BuildServiceProvider(ParseResult parseResult) TimeSpan.FromSeconds(parseResult.GetValue(Config.AuthTtl)) ) ); - collection.AddSingleton>(serviceProvider => + collection.AddSingleton>(_ => { FileMessageProvider ofStrings = new FileMessageProvider(parseResult.GetValue(Config.MessagesFilePath)!); JsonRpcMessageProvider ofJsonRpc = new JsonRpcMessageProvider(ofStrings); @@ -87,7 +99,7 @@ private static IServiceProvider BuildServiceProvider(ParseResult parseResult) new ComposedJsonRpcMethodFilter( [..parseResult .GetValue(Config.MethodFilters)! - .Select(pattern => new PatternJsonRpcMethodFilter(pattern) as IJsonRpcMethodFilter)] + .Select(IJsonRpcMethodFilter (pattern) => new PatternJsonRpcMethodFilter(pattern))] ) ); collection.AddSingleton(provider => @@ -118,7 +130,7 @@ private static IServiceProvider BuildServiceProvider(ParseResult parseResult) { MemoryMetricsReporter memoryReporter = new MemoryMetricsReporter(); ConsoleTotalReporter consoleReporter = new ConsoleTotalReporter(memoryReporter, provider.GetRequiredService()); - IMetricsReporter progresReporter = parseResult.GetValue(Config.ShowProgress) + IMetricsReporter progressReporter = parseResult.GetValue(Config.ShowProgress) ? new ConsoleProgressReporter() : new NullMetricsReporter(); @@ -130,9 +142,9 @@ private static IServiceProvider BuildServiceProvider(ParseResult parseResult) ? new PrometheusPushGatewayMetricsReporter(prometheusGateway, labels, prometheusGatewayUser, prometheusGatewayPassword) : new NullMetricsReporter(); - return new ComposedMetricsReporter([memoryReporter, progresReporter, consoleReporter, prometheusReporter]); + return new ComposedMetricsReporter(memoryReporter, progressReporter, consoleReporter, prometheusReporter); }); - collection.AddSingleton(provider => + collection.AddSingleton(_ => { int concurrentRequests = parseResult.GetValue(Config.ConcurrentRequests); if (concurrentRequests > 1) diff --git a/tools/Kute/Nethermind.Tools.Kute/ResponseTracer/FileResponseTracer.cs b/tools/Kute/Nethermind.Tools.Kute/ResponseTracer/FileResponseTracer.cs index 98120a5e07c6..17c16072d0c5 100644 --- a/tools/Kute/Nethermind.Tools.Kute/ResponseTracer/FileResponseTracer.cs +++ b/tools/Kute/Nethermind.Tools.Kute/ResponseTracer/FileResponseTracer.cs @@ -19,6 +19,6 @@ public async Task TraceResponse(JsonRpc.Response response, CancellationToken tok : File.CreateText(_tracesFilePath); var content = response.Json.ToString() ?? "null"; - await sw.WriteLineAsync(MemoryExtensions.AsMemory(content), token); + await sw.WriteLineAsync(content.AsMemory(), token); } } diff --git a/tools/Kute/Nethermind.Tools.Kute/ResponseTracer/NullResponseTracer.cs b/tools/Kute/Nethermind.Tools.Kute/ResponseTracer/NullResponseTracer.cs index 1838902040ed..b1dab7e84f19 100644 --- a/tools/Kute/Nethermind.Tools.Kute/ResponseTracer/NullResponseTracer.cs +++ b/tools/Kute/Nethermind.Tools.Kute/ResponseTracer/NullResponseTracer.cs @@ -5,7 +5,5 @@ namespace Nethermind.Tools.Kute.ResponseTracer; public sealed class NullResponseTracer : IResponseTracer { - public NullResponseTracer() { } - public Task TraceResponse(JsonRpc.Response response, CancellationToken token) => Task.CompletedTask; } diff --git a/tools/Kute/Nethermind.Tools.Kute/SecretProvider/FileSecretProvider.cs b/tools/Kute/Nethermind.Tools.Kute/SecretProvider/FileSecretProvider.cs index 45b50a70ab96..17fcd457055b 100644 --- a/tools/Kute/Nethermind.Tools.Kute/SecretProvider/FileSecretProvider.cs +++ b/tools/Kute/Nethermind.Tools.Kute/SecretProvider/FileSecretProvider.cs @@ -12,7 +12,7 @@ public FileSecretProvider(string filePath) _filePath = filePath; } - private bool IsHex(char c) => + private static bool IsHex(char c) => c is >= '0' and <= '9' or >= 'a' and <= 'f' or >= 'A' and <= 'F'; diff --git a/tools/Kute/README.md b/tools/Kute/README.md index 87697ed9a27a..19c97151c986 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 d86b000abd91..5ba04fe94ec7 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 f8247a54fa95..db27dc75b1a0 100644 --- a/tools/SchemaGenerator/SchemaGenerator.slnx +++ b/tools/SchemaGenerator/SchemaGenerator.slnx @@ -2,7 +2,6 @@ - diff --git a/tools/SendBlobs/AccountException.cs b/tools/SendBlobs/AccountException.cs index 00f66e3966f9..7a37127bbd5b 100644 --- a/tools/SendBlobs/AccountException.cs +++ b/tools/SendBlobs/AccountException.cs @@ -1,14 +1,8 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; - namespace SendBlobs; + internal class AccountException : Exception { public AccountException() diff --git a/tools/SendBlobs/BlobSender.cs b/tools/SendBlobs/BlobSender.cs index 76d22abd9fa4..60cf5d07de5f 100644 --- a/tools/SendBlobs/BlobSender.cs +++ b/tools/SendBlobs/BlobSender.cs @@ -18,6 +18,7 @@ using CkzgLib; namespace SendBlobs; + internal class BlobSender { private static readonly TxDecoder txDecoder = TxDecoder.Instance; @@ -37,7 +38,7 @@ public BlobSender(string rpcUrl, ILogManager logManager) _rpcClient = SetupCli.InitRpcClient(rpcUrl, _logger); _rpcUrl = rpcUrl; - KzgPolynomialCommitments.InitializeAsync().Wait(); + KzgPolynomialCommitments.InitializeAsync().GetAwaiter().GetResult(); } // send-blobs @@ -51,7 +52,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( @@ -62,43 +63,26 @@ public async Task SendRandomBlobs( ulong feeMultiplier, UInt256? maxPriorityFeeGasArgs, bool waitForInclusion, - IReleaseSpec spec) + IReleaseSpec spec, + int? seed) { List<(Signer, ulong)> signers = []; - if (waitForInclusion) - { - bool isNodeSynced = await _rpcClient.Post("eth_syncing") is bool; - if (!isNodeSynced) - { - Console.WriteLine($"Will not wait for blob inclusion since selected node at {_rpcUrl} is still syncing"); - waitForInclusion = false; - } - } + waitForInclusion = await EnsureWaitForInclusionAsync(waitForInclusion); - string? chainIdString = await _rpcClient.Post("eth_chainId") ?? "1"; - ulong chainId = HexConvert.ToUInt64(chainIdString); + ulong chainId = await _rpcClient.GetChainIdAsync(); foreach (PrivateKey privateKey in privateKeys) { - string? nonceString = await _rpcClient.Post("eth_getTransactionCount", privateKey.Address, "latest"); - if (nonceString is null) - { - _logger.Error("Unable to get nonce"); - return; - } - ulong nonce = HexConvert.ToUInt64(nonceString); + ulong nonce = await _rpcClient.GetTransactionCountAsync(privateKey.Address); signers.Add(new(new Signer(chainId, privateKey, _logManager), nonce)); } - TxDecoder txDecoder = TxDecoder.Instance; - Random random = new(); + Random random = seed is null ? new() : new(seed.Value); int signerIndex = -1; - ulong excessBlobs = (ulong)blobTxCounts.Sum(btxc => btxc.blobCount) / 2; - foreach ((int txCount, int blobCount, string @break) txs in blobTxCounts) { int txCount = txs.txCount; @@ -149,7 +133,7 @@ public async Task SendRandomBlobs( byte[][] blobHashes = proofs.ComputeHashes(blobsContainer); - BlockModel? blockResult = await _rpcClient.Post>("eth_getBlockByNumber", "latest", false); + BlockModel? blockResult = await _rpcClient.GetBlockByNumberAsync("latest", false); if (blockResult is null) { @@ -157,6 +141,7 @@ public async Task SendRandomBlobs( return; } + ulong excessBlobs = (ulong)blobCount / 2; (UInt256 maxGasPrice, UInt256 maxPriorityFeePerGas, UInt256 maxFeePerBlobGas) = await GetGasPrices(null, maxPriorityFeeGasArgs, maxFeePerBlobGasArgs, blockResult, excessBlobs, spec); maxPriorityFeePerGas *= feeMultiplier; @@ -186,8 +171,10 @@ public async Task SendRandomBlobs( if (result is not null) signers[signerIndex] = new(signer, nonce + 1); - if (waitForInclusion) + if (waitForInclusion && result is not null) + { await WaitForBlobInclusion(_rpcClient, result, blockResult.Number); + } } } } @@ -205,32 +192,24 @@ public async Task SendData( bool waitForInclusion, IReleaseSpec spec) { - int n = 0; - data = data - .Select((s, i) => (i + n) % 32 != 0 ? [s] : (s < 0x73 ? new byte[] { s } : [(byte)(32), s])) - .SelectMany(b => b).ToArray(); - - if (waitForInclusion) + 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++) { - bool isNodeSynced = await _rpcClient.Post("eth_syncing") is bool; - if (!isNodeSynced) + byte value = data[i]; + if (i % 32 == 0 && value >= 0x73) { - Console.WriteLine($"Will not wait for blob inclusion since selected node at {_rpcUrl} is still syncing"); - waitForInclusion = false; + normalized.Add(32); } + normalized.Add(value); } + data = normalized.ToArray(); - string? chainIdString = await _rpcClient.Post("eth_chainId") ?? "1"; - ulong chainId = HexConvert.ToUInt64(chainIdString); + waitForInclusion = await EnsureWaitForInclusionAsync(waitForInclusion); + ulong chainId = await _rpcClient.GetChainIdAsync(); - string? nonceString = await _rpcClient.Post("eth_getTransactionCount", privateKey.Address, "latest"); - if (nonceString is null) - { - _logger.Error("Unable to get nonce"); - return; - } - ulong nonce = HexConvert.ToUInt64(nonceString); + ulong nonce = await _rpcClient.GetTransactionCountAsync(privateKey.Address); Signer signer = new(chainId, privateKey, _logManager); @@ -252,7 +231,7 @@ public async Task SendData( byte[][] blobHashes = proofs.ComputeHashes(blobsContainer); - BlockModel? blockResult = await _rpcClient.Post>("eth_getBlockByNumber", "latest", false); + BlockModel? blockResult = await _rpcClient.GetBlockByNumberAsync("latest", false); if (blockResult is null) { @@ -268,8 +247,10 @@ public async Task SendData( Hash256? hash = await SendTransaction(chainId, nonce, maxGasPrice, maxPriorityFeePerGas, maxFeePerBlobGas, receiver, blobHashes, blobsContainer, signer); - if (waitForInclusion) + if (waitForInclusion && hash is not null) + { await WaitForBlobInclusion(_rpcClient, hash, blockResult.Number); + } } private async Task<(UInt256 maxGasPrice, UInt256 maxPriorityFeePerGas, UInt256 maxFeePerBlobGas)> GetGasPrices @@ -279,8 +260,7 @@ public async Task SendData( if (defaultMaxPriorityFeePerGas is null) { - string? maxPriorityFeePerGasRes = await _rpcClient.Post("eth_maxPriorityFeePerGas") ?? "1"; - result.maxPriorityFeePerGas = HexConvert.ToUInt256(maxPriorityFeePerGasRes); + result.maxPriorityFeePerGas = await _rpcClient.GetMaxPriorityFeePerGasAsync(); } else { @@ -340,7 +320,7 @@ public async Task SendData( string txRlp = Hex.ToHexString(txDecoder .Encode(tx, RlpBehaviors.InMempoolForm | RlpBehaviors.SkipTypedWrapping).Bytes); - string? result = await _rpcClient.Post("eth_sendRawTransaction", $"0x{txRlp}"); + string? result = await _rpcClient.SendRawTransactionAsync($"0x{txRlp}"); Console.WriteLine("Sending tx result:" + result); Console.WriteLine("Blob hashes:" + string.Join(",", tx.BlobVersionedHashes.Select(bvh => $"0x{Hex.ToHexString(bvh)}"))); @@ -348,7 +328,7 @@ public async Task SendData( return result is not null ? tx.CalculateHash() : null; } - private async static Task WaitForBlobInclusion(IJsonRpcClient rpcClient, Hash256? txHash, UInt256 lastBlockNumber) + private async static Task WaitForBlobInclusion(IJsonRpcClient rpcClient, Hash256 txHash, UInt256 lastBlockNumber) { Console.WriteLine("Waiting for blob transaction to be included in a block"); int waitInMs = 2000; @@ -357,26 +337,42 @@ private async static Task WaitForBlobInclusion(IJsonRpcClient rpcClient, Hash256 while (true) { - var blockResult = await rpcClient.Post>("eth_getBlockByNumber", lastBlockNumber.ToString() ?? "latest", false); + BlockModel? blockResult = await rpcClient.GetBlockByNumberAsync(lastBlockNumber, false); + if (blockResult is not null) { lastBlockNumber = blockResult.Number + 1; - if (txHash is not null && blockResult.Transactions.Contains(txHash)) + if (blockResult.Transactions.Contains(txHash)) { - string? receipt = await rpcClient.Post("eth_getTransactionByHash", txHash.ToString(), true); - Console.WriteLine($"Found blob transaction in block {blockResult.Number}"); return; } } - else - { - await Task.Delay(waitInMs); - } + + await Task.Delay(waitInMs); retryCount--; if (retryCount == 0) break; } } + + private async Task EnsureWaitForInclusionAsync(bool waitForInclusion) + { + if (!waitForInclusion) + { + return false; + } + + bool isNodeSynced = await _rpcClient.IsNodeSyncedAsync(); + + if (isNodeSynced) + { + return true; + } + + Console.WriteLine($"Will not wait for blob inclusion since selected node at {_rpcUrl} is still syncing"); + + return false; + } } diff --git a/tools/SendBlobs/Dockerfile b/tools/SendBlobs/Dockerfile index 9ef638c47053..ac56aa3b0784 100644 --- a/tools/SendBlobs/Dockerfile +++ b/tools/SendBlobs/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:9.0-noble AS build +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-noble AS build ARG BUILD_CONFIG=release ARG BUILD_TIMESTAMP @@ -9,17 +9,22 @@ ARG COMMIT_HASH ARG TARGETARCH ARG TARGETOS -COPY . . +COPY ./src/Nethermind ./src/Nethermind +COPY ./tools/SendBlobs ./tools/SendBlobs +COPY ./Directory.*.props . +COPY ./global.json ./global.json +COPY ./nuget.config ./nuget.config +COPY ./tools/Directory.Build.props ./tools/Directory.Build.props RUN arch=$([ "$TARGETARCH" = "amd64" ] && echo "x64" || echo "$TARGETARCH") && \ - dotnet publish tools/SendBlobs -c $BUILD_CONFIG -r $TARGETOS-$arch -o out --sc true \ - -p:BuildTimestamp=$BUILD_TIMESTAMP -p:Commit=$COMMIT_HASH + dotnet publish tools/SendBlobs/SendBlobs.csproj -c $BUILD_CONFIG -r $TARGETOS-$arch -o /out --sc true \ + -p:DebugType=None \ + -p:DebugSymbols=false -FROM --platform=$TARGETPLATFORM ubuntu +FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-noble WORKDIR /nethermind -COPY --from=build /out/SendBlobs . +COPY --from=build /out ./ -RUN apt update && apt-get install -y ca-certificates ENTRYPOINT ["./SendBlobs"] diff --git a/tools/SendBlobs/FundsDistributor.cs b/tools/SendBlobs/FundsDistributor.cs index 757d3f9665c4..79279f38b3de 100644 --- a/tools/SendBlobs/FundsDistributor.cs +++ b/tools/SendBlobs/FundsDistributor.cs @@ -12,8 +12,11 @@ using Nethermind.Serialization.Rlp; namespace SendBlobs; + internal class FundsDistributor { + private static readonly TxDecoder TxDecoderInstance = TxDecoder.Instance; + private readonly IJsonRpcClient _rpcClient; private readonly ulong _chainId; private readonly string? _keyFilePath; @@ -36,24 +39,19 @@ 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) { - string? balanceString = await _rpcClient.Post("eth_getBalance", distributeFrom.Address, "latest"); - if (balanceString is null) - throw new AccountException($"Unable to get balance for {distributeFrom.Address}"); - string? nonceString = await _rpcClient.Post("eth_getTransactionCount", distributeFrom.Address, "latest"); - if (nonceString is null) - throw new AccountException($"Unable to get nonce for {distributeFrom.Address}"); + if (keysToMake == 0) + throw new ArgumentException("keysToMake must be greater than zero.", nameof(keysToMake)); - string? gasPriceRes = await _rpcClient.Post("eth_gasPrice") ?? "1"; - UInt256 gasPrice = HexConvert.ToUInt256(gasPriceRes); + string balanceString = await _rpcClient.GetBalanceAsync(distributeFrom.Address); + ulong nonce = await _rpcClient.GetTransactionCountAsync(distributeFrom.Address); + UInt256 gasPrice = await _rpcClient.GetGasPriceAsync(); - string? maxPriorityFeePerGasRes; UInt256 maxPriorityFeePerGas = maxPriorityFee; if (maxPriorityFee == 0) { - maxPriorityFeePerGasRes = await _rpcClient.Post("eth_maxPriorityFeePerGas") ?? "1"; - maxPriorityFeePerGas = HexConvert.ToUInt256(maxPriorityFeePerGasRes); + maxPriorityFeePerGas = await _rpcClient.GetMaxPriorityFeePerGasAsync(); } UInt256 balance = new UInt256(Bytes.FromHexString(balanceString)); @@ -61,9 +59,8 @@ public async Task> DitributeFunds(Signer distributeFrom, uin if (balance == 0) throw new AccountException($"Balance on provided signer {distributeFrom.Address} is 0."); - ulong nonce = HexConvert.ToUInt64(nonceString); - - UInt256 approxGasFee = (gasPrice + maxPriorityFeePerGas) * GasCostOf.Transaction; + UInt256 maxFeePerGas = maxFee != 0 ? maxFee : gasPrice; + UInt256 approxGasFee = maxFeePerGas * GasCostOf.Transaction; //Leave 10% of the balance as buffer in case of gas spikes UInt256 balanceMinusBuffer = (balance * 900) / 1000; @@ -79,9 +76,8 @@ public async Task> DitributeFunds(Signer distributeFrom, uin using PrivateKeyGenerator generator = new(); IEnumerable privateKeys = Enumerable.Range(1, (int)keysToMake).Select(i => generator.Generate()); - List txHash = new List(); + List txHash = []; - TxDecoder txDecoder = TxDecoder.Instance; StreamWriter? keyWriter = null; if (!string.IsNullOrWhiteSpace(_keyFilePath)) @@ -97,26 +93,18 @@ public async Task> DitributeFunds(Signer distributeFrom, uin { if (maxFee == 0) { - gasPriceRes = await _rpcClient.Post("eth_gasPrice") ?? "1"; - gasPrice = HexConvert.ToUInt256(gasPriceRes); - - maxPriorityFeePerGasRes = await _rpcClient.Post("eth_maxPriorityFeePerGas") ?? "1"; - maxPriorityFeePerGas = HexConvert.ToUInt256(maxPriorityFeePerGasRes); + gasPrice = await _rpcClient.GetGasPriceAsync(); + maxPriorityFeePerGas = await _rpcClient.GetMaxPriorityFeePerGasAsync(); } Transaction tx = CreateTx(_chainId, key.Address, - maxFee != 0 ? maxFee : gasPrice + maxPriorityFeePerGas, + maxFee != 0 ? maxFee : gasPrice, nonce, maxPriorityFeePerGas, perKeyToSend); - await distributeFrom.Sign(tx); - - string txRlp = Convert.ToHexStringLower(txDecoder - .Encode(tx, RlpBehaviors.SkipTypedWrapping | RlpBehaviors.InMempoolForm).Bytes); - - string? result = await _rpcClient.Post("eth_sendRawTransaction", $"0x{txRlp}"); + string? result = await SignAndSendAsync(distributeFrom, tx); if (result is not null) txHash.Add(result); @@ -144,33 +132,26 @@ public async Task> ReclaimFunds(Address beneficiary, UInt256 : File.ReadAllLines(_keyFilePath).Select(k => new Signer(_chainId, new PrivateKey(k), _logManager)); ILogger log = _logManager.GetClassLogger(); - List txHashes = new List(); - TxDecoder txDecoder = TxDecoder.Instance; - + List txHashes = []; foreach (var signer in privateSigners) { - string? balanceString = await _rpcClient.Post("eth_getBalance", signer.Address, "latest"); - if (balanceString is null) - continue; - string? nonceString = await _rpcClient.Post("eth_getTransactionCount", signer.Address, "latest"); - if (nonceString is null) - continue; + string balanceString = await _rpcClient.GetBalanceAsync(signer.Address); + ulong nonce = await _rpcClient.GetTransactionCountAsync(signer.Address); UInt256 balance = new UInt256(Bytes.FromHexString(balanceString)); - ulong nonce = HexConvert.ToUInt64(nonceString); + ulong nonceValue = nonce; - string? gasPriceRes = await _rpcClient.Post("eth_gasPrice") ?? "1"; - UInt256 gasPrice = HexConvert.ToUInt256(gasPriceRes); + UInt256 gasPrice = await _rpcClient.GetGasPriceAsync(); UInt256 maxPriorityFeePerGas = maxPriorityFee; if (maxPriorityFee == 0) { - string? maxPriorityFeePerGasRes = await _rpcClient.Post("eth_maxPriorityFeePerGas") ?? "1"; - maxPriorityFeePerGas = HexConvert.ToUInt256(maxPriorityFeePerGasRes); + maxPriorityFeePerGas = await _rpcClient.GetMaxPriorityFeePerGasAsync(); } - UInt256 approxGasFee = (gasPrice + maxPriorityFeePerGas) * GasCostOf.Transaction; + UInt256 maxFeePerGas = maxFee != 0 ? maxFee : gasPrice; + UInt256 approxGasFee = maxFeePerGas * GasCostOf.Transaction; if (balance < approxGasFee) { @@ -182,34 +163,39 @@ public async Task> ReclaimFunds(Address beneficiary, UInt256 Transaction tx = CreateTx(_chainId, beneficiary, - maxFee != 0 ? maxFee : gasPrice + maxPriorityFeePerGas, - nonce, + maxFee != 0 ? maxFee : gasPrice, + nonceValue, maxPriorityFeePerGas, toSend); - await signer.Sign(tx); + string? result = await SignAndSendAsync(signer, tx); - string txRlp = Convert.ToHexStringLower(txDecoder - .Encode(tx, RlpBehaviors.SkipTypedWrapping | RlpBehaviors.InMempoolForm).Bytes); - - string? result = await _rpcClient.Post("eth_sendRawTransaction", $"0x{txRlp}"); if (result is not null) + { txHashes.Add(result); + } } return txHashes; } - private static Transaction CreateTx(ulong chainId, Address beneficiary, UInt256 maxFee, ulong nonce, UInt256 maxPriorityFeePerGas, UInt256 toSend) + private async Task SignAndSendAsync(Signer signer, Transaction tx) { - return new() - { - Type = TxType.EIP1559, - ChainId = chainId, - Nonce = nonce, - GasLimit = GasCostOf.Transaction, - GasPrice = maxPriorityFeePerGas, - DecodedMaxFeePerGas = maxFee, - Value = toSend, - To = beneficiary, - }; + await signer.Sign(tx); + + string txRlp = Convert.ToHexStringLower(TxDecoderInstance + .Encode(tx, RlpBehaviors.SkipTypedWrapping | RlpBehaviors.InMempoolForm).Bytes); + + return await _rpcClient.SendRawTransactionAsync($"0x{txRlp}"); } + + private static Transaction CreateTx(ulong chainId, Address beneficiary, UInt256 maxFee, ulong nonce, UInt256 maxPriorityFeePerGas, UInt256 toSend) => new() + { + Type = TxType.EIP1559, + ChainId = chainId, + Nonce = nonce, + GasLimit = GasCostOf.Transaction, + GasPrice = maxPriorityFeePerGas, + DecodedMaxFeePerGas = maxFee, + Value = toSend, + To = beneficiary, + }; } diff --git a/tools/SendBlobs/HexConvert.cs b/tools/SendBlobs/HexConvert.cs deleted file mode 100644 index d68014b229c4..000000000000 --- a/tools/SendBlobs/HexConvert.cs +++ /dev/null @@ -1,18 +0,0 @@ -// 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/Properties/launchSettings.json b/tools/SendBlobs/Properties/launchSettings.json index fbf1ea049d7b..864a8cc9d3f1 100644 --- a/tools/SendBlobs/Properties/launchSettings.json +++ b/tools/SendBlobs/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "SendBlobs": { "commandName": "Project", - "commandLineArgs": "--rpcurl http://172.105.149.206:8545 --bloboptions 1 --privatekey 0x4bc391a63f2827ed88291061c3428887c83cab6c090328e68ae1738fbbedf1d8 --receiveraddress 0x3Af4A1fe34392AACbf4cDA98518860b9Ea3c8943 --maxpriorityfee 200000000000 --feemultiplier 20" + "commandLineArgs": "--rpcurl http://127.0.0.1:8545 --bloboptions 1 --privatekey 0x4bc391a63f2827ed88291061c3428887c83cab6c090328e68ae1738fbbedf1d8 --receiveraddress 0x3Af4A1fe34392AACbf4cDA98518860b9Ea3c8943 --maxpriorityfee 200000000000 --feemultiplier 20 --seed 42" } } -} +} \ No newline at end of file diff --git a/tools/SendBlobs/README.md b/tools/SendBlobs/README.md index ebd6d41899fd..52da8adcdcc6 100644 --- a/tools/SendBlobs/README.md +++ b/tools/SendBlobs/README.md @@ -2,58 +2,43 @@ ## Run using docker -``` +```sh docker run nethermindeth/send-blobs:latest --rpcurl http://localhost:8545 --bloboptions 5 --privatekey 0x0000000000000000000000000000000000000000000000000000000000000000 --receiveraddress 0x000000000000000000000000000000000000f1c1 docker run nethermindeth/send-blobs:latest --rpcurl http://localhost:8545 --bloboptions 5x6 --privatekey 0x0000000000000000000000000000000000000000000000000000000000000000 --receiveraddress 0x000000000000000000000000000000000000f1c1 --maxfeeperblobgas 10000 --feemultiplier 4 ``` ## Usage +The tool can help with: + +- blob spamming with random data, from multiple accounts +- sending files as blobs, +- batch funds distribution + +Use "SendBlobs [command] --help" for more information about supported commands. + +The default fork for now is Osaka, which means blobs will be sent with V1 proofs. Use `--fork Prague` option to change it to V0. The default behavior may change post Osaka. + +## Build + +```sh +apt install libsnappy-dev dotnet-sdk-10.0 -y +cd ./nethermind/tools/SendBlobs +dotnet publish --sc -o . +./SendBlobs ``` -Usage: SendBlobs [options] [command] - -Options: - --help Show help information - --rpcurl Url of the Json RPC. - --bloboptions Options in format '10x1-2', '2x5-5' etc. for the blobs. - --privatekey The key to use for sending blobs. - --keyfile File containing private keys that each blob tx will be send from. - --receiveraddress Receiver address of the blobs. - --maxfeeperblobgas (Optional) Set the maximum fee per blob data. - --feemultiplier (Optional) A multiplier to use for gas fees. - --maxpriorityfee (Optional) The maximum priority fee for each transaction. - --fork (Optional) Fork rules: Cancun/Prague/Osaka - -Commands: - distribute Distribute funds from an address to a number of new addresses. - reclaim Reclaim funds distributed from the 'distribute' command. - - -Use "SendBlobs [command] --help" for more information about a command. - -Usage: SendBlobs __distribute__ [options] - -Options: - --help Show help information - --rpcurl Url of the Json RPC. - --privatekey The private key to distribute funds from. - --number The number of new addresses/keys to make. - --keyfile File where the newly generated keys are written. - --maxpriorityfee (Optional) The maximum priority fee for each transaction. - --maxfee (Optional) The maxFeePerGas fee paid for each transaction. - - -Usage: SendBlobs __reclaim__ [options] - -Options: - --help Show help information - --rpcurl Url of the Json RPC. - --receiveraddress The address to send the funds to. - --keyfile File of the private keys to reclaim from. - --maxpriorityfee (Optional) The maximum priority fee for each transaction. - --maxfee (Optional) The maxFeePerGas paid for each transaction. - -sh + +or via docker + +```sh +cd ./nethermind/ # repository root +docker build . -f ./tools/SendBlobs/Dockerfile -t send-blobs +docker run send-blobs +``` + +### Examples + +```sh ./SendBlobs --rpcurl http://localhost:8545 # url-that-does-not-require-auth-in-header --bloboptions 1000,5x6,100x2 # transaction count: just a number or a list of tx-count x blob-count --privatekey 0x0000..0000 # secret-key @@ -62,9 +47,12 @@ sh --feemultiplier 4 # fee multiplier to compete with other txs in the pool, 4 by default # send 5 transactions, 1 blob each -./SendBlobs --rpcurl http://localhost:8545 --bloboptions 5 \ - 0x0000000000000000000000000000000000000000000000000000000000000000 \ - 0x000000000000000000000000000000000000f1c1 10000 4 +./SendBlobs --rpcurl http://localhost:8545 \ + --bloboptions 5 \ + --privatekey 0x0000000000000000000000000000000000000000000000000000000000000000 \ + --receiveraddress 0x000000000000000000000000000000000000f1c1 \ + --maxfeeperblobgas 10000 \ + --feemultiplier 4 # send several transactions with 1 blob, with 6 blobs and than with 2 blobs ./SendBlobs --rpcurl http://localhost:8545 @@ -74,19 +62,21 @@ sh --maxfeeperblobgas 10000 \ --feemultiplier 4 -#send a couple of transactions +# send a couple of broken transactions ./SendBlobs --rpcurl http://localhost:8545 \ - --bloboptions 2x4-1 \ + --bloboptions 2x4-2 \ --privatekey 0x0000000000000000000000000000000000000000000000000000000000000000 \ --receiveraddress 0x0000000000000000000000000000000000000001 \ --maxfeeperblobgas 10000 \ --feemultiplier 4 ``` + ## Blob options -``` -< GetChainIdAsync() + { + string chainIdString = await PostWithRetry(rpcClient, nameof(IEthRpcModule.eth_chainId)); + + return Bytes.FromHexString(chainIdString).AsSpan().ReadEthUInt64(); + } + + public async Task GetTransactionCountAsync(Address address) + { + string nonceString = await PostWithRetry(rpcClient, nameof(IEthRpcModule.eth_getTransactionCount), address, Latest); + + return Bytes.FromHexString(nonceString).AsSpan().ReadEthUInt64(); + } + + public async Task GetGasPriceAsync() + { + string gasPriceRes = await PostWithRetry(rpcClient, nameof(IEthRpcModule.eth_gasPrice)); + + return UInt256.Parse(gasPriceRes); + } + + public async Task GetMaxPriorityFeePerGasAsync() + { + string maxPriorityFeeRes = await PostWithRetry(rpcClient, nameof(IEthRpcModule.eth_maxPriorityFeePerGas)); + + return UInt256.Parse(maxPriorityFeeRes); + } + + public async Task IsNodeSyncedAsync() + { + object syncingResult = await PostWithRetry(rpcClient, nameof(IEthRpcModule.eth_syncing)); + + return syncingResult is bool isSyncing && isSyncing is false; + } + + public async Task GetBalanceAsync(Address address, string blockTag = Latest) => await PostWithRetry(rpcClient, nameof(IEthRpcModule.eth_getBalance), address, blockTag); + + public async Task SendRawTransactionAsync(string txRlpHex) => await PostWithRetry(rpcClient, nameof(IEthRpcModule.eth_sendRawTransaction), txRlpHex); + + public async Task?> GetBlockByNumberAsync(object blockNumberOrTag, bool fullTxObjects) => await PostWithRetryNullable>(rpcClient, nameof(IEthRpcModule.eth_getBlockByNumber), blockNumberOrTag, fullTxObjects); + + private async Task PostWithRetry(string method, params object?[]? parameters) => + (await PostWithRetryNullable(rpcClient, method, parameters: parameters)) ?? throw new RpcException($"RPC call '{method}' returned null or empty response."); + + private async Task PostWithRetryNullable(string method, params object?[]? parameters) + { + Exception? lastException = null; + + for (int attempt = 1; attempt <= MaxRetryCount; attempt++) + { + try + { + object?[] safeParameters = parameters ?? []; + return await rpcClient.Post(method, safeParameters); + } + catch (Exception ex) + { + lastException = ex; + + if (attempt >= MaxRetryCount) + { + break; + } + + int delayMs = 300 * attempt; + await Task.Delay(delayMs); + } + } + + string baseMessage = $"RPC call '{method}' failed after {MaxRetryCount} attempts."; + + if (lastException is null) + { + throw new RpcException(baseMessage); + } + + throw new RpcException($"{baseMessage} {lastException.Message}", lastException); + } + } +} + +public class RpcException(string message, Exception? innerException = null) : Exception(message, innerException); diff --git a/tools/SendBlobs/SendBlobs.slnx b/tools/SendBlobs/SendBlobs.slnx index f0491fc80123..f6e2b1fbbd9f 100644 --- a/tools/SendBlobs/SendBlobs.slnx +++ b/tools/SendBlobs/SendBlobs.slnx @@ -1,47 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tools/SendBlobs/SetupCli.cs b/tools/SendBlobs/SetupCli.cs index d24f04ef84e3..9d04e06ad060 100644 --- a/tools/SendBlobs/SetupCli.cs +++ b/tools/SendBlobs/SetupCli.cs @@ -1,9 +1,8 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Consensus; using Nethermind.Crypto; -using Nethermind.Int256; using Nethermind.Logging; using System.CommandLine; using Nethermind.Core.Specs; @@ -11,8 +10,10 @@ using Nethermind.JsonRpc.Client; using Nethermind.Serialization.Json; using Nethermind.Specs; +using Nethermind.Int256; namespace SendBlobs; + internal static class SetupCli { public static void SetupExecute(RootCommand command) @@ -60,13 +61,19 @@ public static void SetupExecute(RootCommand command) Description = "A multiplier to use for gas fees", HelpName = "value" }; - Option maxPriorityFeeGasOption = new("--maxpriorityfee") + Option maxPriorityFeeGasOption = new("--maxpriorityfee") { Description = "The maximum priority fee for each transaction", HelpName = "fee" }; Option waitOption = new("--wait") { Description = "Wait for tx inclusion" }; - Option forkOption = new("--fork") { Description = "Specify fork for MaxBlobCount, TargetBlobCount" }; + Option forkOption = new("--fork") { Description = "Specify fork for MaxBlobCount, TargetBlobCount, Osaka by default" }; + Option seedOption = new("--seed") + { + Description = "Seed for randomness used to generate blob content", + HelpName = "seed", + DefaultValueFactory = r => null + }; command.Add(rpcUrlOption); command.Add(blobTxOption); @@ -79,36 +86,60 @@ public static void SetupExecute(RootCommand command) command.Add(maxPriorityFeeGasOption); command.Add(waitOption); command.Add(forkOption); + command.Add(seedOption); command.SetAction((parseResult, cancellationToken) => { - PrivateKey[] privateKeys; + return ExecuteWithRpcHandling(() => + { + PrivateKey[] privateKeys; - string? privateKeyFileValue = parseResult.GetValue(privateKeyFileOption); - string? privateKeyValue = parseResult.GetValue(privateKeyOption); + string? privateKeyFileValue = parseResult.GetValue(privateKeyFileOption); + string? privateKeyValue = parseResult.GetValue(privateKeyOption); - if (privateKeyFileValue is not null) - privateKeys = File.ReadAllLines(privateKeyFileValue).Select(k => new PrivateKey(k)).ToArray(); - else if (privateKeyValue is not null) - privateKeys = [new PrivateKey(privateKeyValue)]; - else - { - Console.WriteLine("Missing private key argument."); - return Task.CompletedTask; - } + if (privateKeyFileValue is not null) + { + if (!File.Exists(privateKeyFileValue)) + { + Console.WriteLine($"Private key file not found: {privateKeyFileValue}"); + return Task.CompletedTask; + } + + privateKeys = File.ReadAllLines(privateKeyFileValue).Select(k => new PrivateKey(k)).ToArray(); + } + else if (privateKeyValue is not null) + privateKeys = [new PrivateKey(privateKeyValue)]; + else + { + Console.WriteLine("Missing private key argument."); + return Task.CompletedTask; + } - string? fork = parseResult.GetValue(forkOption); - IReleaseSpec spec = fork is null ? Prague.Instance : SpecNameParser.Parse(fork); - - BlobSender sender = new(parseResult.GetValue(rpcUrlOption)!, SimpleConsoleLogManager.Instance); - return sender.SendRandomBlobs( - ParseTxOptions(parseResult.GetValue(blobTxOption)), - privateKeys, - parseResult.GetValue(receiverOption)!, - parseResult.GetValue(maxFeePerBlobGasOption) ?? parseResult.GetValue(maxFeePerDataGasOptionObsolete), - parseResult.GetValue(feeMultiplierOption), - parseResult.GetValue(maxPriorityFeeGasOption), - parseResult.GetValue(waitOption), - spec); + string? fork = parseResult.GetValue(forkOption); + IReleaseSpec spec = fork is null ? Osaka.Instance : SpecNameParser.Parse(fork); + + BlobSender sender = new(parseResult.GetRequiredValue(rpcUrlOption), SimpleConsoleLogManager.Instance); + ulong? maxFeePerBlobGas = parseResult.GetValue(maxFeePerBlobGasOption) ?? + parseResult.GetValue(maxFeePerDataGasOptionObsolete); + ulong? maxPriorityFee = parseResult.GetValue(maxPriorityFeeGasOption); + + (int count, int blobCount, string @break)[] txOptions = ParseTxOptions(parseResult.GetValue(blobTxOption)); + if (txOptions.Length == 0) + { + Console.WriteLine("No --bloboptions provided. Nothing to send."); + return Task.CompletedTask; + } + + return sender.SendRandomBlobs( + txOptions, + privateKeys, + parseResult.GetRequiredValue(receiverOption), + maxFeePerBlobGas.HasValue ? (UInt256?)maxFeePerBlobGas.Value : null, + parseResult.GetValue(feeMultiplierOption), + maxPriorityFee.HasValue ? (UInt256?)maxPriorityFee.Value : null, + parseResult.GetValue(waitOption), + spec, + parseResult.GetValue(seedOption)); + }); }); } @@ -124,16 +155,27 @@ private static (int count, int blobCount, string @break)[] ParseTxOptions(string int offSet = 0; while (true) { - nextComma = SplitToNext(chars[offSet..], ','); + ReadOnlySpan segment = SplitToNext(chars[offSet..], ','); + nextComma = segment.Trim(); + if (nextComma.IsEmpty) + { + offSet += segment.Length + 1; + if (offSet > chars.Length) + { + break; + } + continue; + } - ReadOnlySpan @break = SplitToNext(nextComma, '-', true); - ReadOnlySpan rest = nextComma[..(nextComma.Length - (@break.Length == 0 ? 0 : @break.Length + 1))]; - ReadOnlySpan count = SplitToNext(rest, 'x'); - ReadOnlySpan txCount = SplitToNext(rest, 'x', true); + ReadOnlySpan breakSegment = SplitToNext(nextComma, '-', true); + ReadOnlySpan @break = breakSegment.Trim(); + ReadOnlySpan rest = nextComma[..(nextComma.Length - (breakSegment.Length == 0 ? 0 : breakSegment.Length + 1))]; + ReadOnlySpan count = SplitToNext(rest, 'x').Trim(); + ReadOnlySpan txCount = SplitToNext(rest, 'x', true).Trim(); result.Add(new(int.Parse(count), txCount.Length == 0 ? 1 : int.Parse(txCount), new string(@break))); - offSet += nextComma.Length + 1; + offSet += segment.Length + 1; if (offSet > chars.Length) { break; @@ -171,19 +213,20 @@ public static void SetupDistributeCommand(Command root) Option keyNumberOption = new("--number") { Description = "The number of new addresses/keys to make", - HelpName = "value" + HelpName = "value", + Required = true }; Option keyFileOption = new("--keyfile") { Description = "File where the newly generated keys are written", HelpName = "path" }; - Option maxPriorityFeeGasOption = new("--maxpriorityfee") + Option maxPriorityFeeGasOption = new("--maxpriorityfee") { Description = "The maximum priority fee for each transaction", HelpName = "fee" }; - Option maxFeeOption = new("--maxfee") + Option maxFeeOption = new("--maxfee") { Description = "The maxFeePerGas fee paid for each transaction", HelpName = "fee" @@ -195,25 +238,27 @@ public static void SetupDistributeCommand(Command root) command.Add(keyFileOption); command.Add(maxPriorityFeeGasOption); command.Add(maxFeeOption); - command.SetAction(async (parseResult, cancellationToken) => + command.SetAction((parseResult, cancellationToken) => { - IJsonRpcClient rpcClient = InitRpcClient( - parseResult.GetValue(rpcUrlOption)!, - SimpleConsoleLogManager.Instance.GetClassLogger()); + return ExecuteWithRpcHandling(async () => + { + IJsonRpcClient rpcClient = InitRpcClient( + parseResult.GetRequiredValue(rpcUrlOption), + SimpleConsoleLogManager.Instance.GetClassLogger()); - string? chainIdString = await rpcClient.Post("eth_chainId") ?? "1"; - ulong chainId = HexConvert.ToUInt64(chainIdString); + ulong chainId = await rpcClient.GetChainIdAsync(); - Signer signer = new(chainId, new PrivateKey(parseResult.GetValue(privateKeyOption)!), - SimpleConsoleLogManager.Instance); + Signer signer = new(chainId, new PrivateKey(parseResult.GetRequiredValue(privateKeyOption)), + SimpleConsoleLogManager.Instance); - FundsDistributor distributor = new( - rpcClient, chainId, parseResult.GetValue(keyFileOption), SimpleConsoleLogManager.Instance); - IEnumerable hashes = await distributor.DitributeFunds( - signer, - parseResult.GetValue(keyNumberOption), - parseResult.GetValue(maxFeeOption), - parseResult.GetValue(maxPriorityFeeGasOption)); + FundsDistributor distributor = new( + rpcClient, chainId, parseResult.GetValue(keyFileOption), SimpleConsoleLogManager.Instance); + await distributor.DistributeFunds( + signer, + parseResult.GetValue(keyNumberOption), + parseResult.GetValue(maxFeeOption), + parseResult.GetValue(maxPriorityFeeGasOption)); + }); }); root.Add(command); @@ -240,41 +285,65 @@ public static void SetupReclaimCommand(Command root) Option keyFileOption = new("--keyfile") { Description = "File of the private keys to reclaim from", - HelpName = "path" + HelpName = "path", + Required = true }; - Option maxPriorityFeeGasOption = new("--maxpriorityfee") + Option maxPriorityFeeGasOption = new("--maxpriorityfee") { Description = "The maximum priority fee for each transaction", HelpName = "fee" }; - Option maxFeeOption = new("--maxfee") + Option maxFeeOption = new("--maxfee") { Description = "The maxFeePerGas fee paid for each transaction", HelpName = "fee" }; command.Add(rpcUrlOption); + command.Add(receiverOption); command.Add(keyFileOption); command.Add(maxPriorityFeeGasOption); command.Add(maxFeeOption); - command.SetAction(async (parseResult, cancellationToken) => + command.SetAction((parseResult, cancellationToken) => { - IJsonRpcClient rpcClient = InitRpcClient( - parseResult.GetValue(rpcUrlOption)!, - SimpleConsoleLogManager.Instance.GetClassLogger()); + return ExecuteWithRpcHandling(async () => + { + string keyFilePath = parseResult.GetRequiredValue(keyFileOption); + if (!File.Exists(keyFilePath)) + { + Console.WriteLine($"Key file not found: {keyFilePath}"); + return; + } + + IJsonRpcClient rpcClient = InitRpcClient( + parseResult.GetRequiredValue(rpcUrlOption), + SimpleConsoleLogManager.Instance.GetClassLogger()); - string? chainIdString = await rpcClient.Post("eth_chainId") ?? "1"; - ulong chainId = HexConvert.ToUInt64(chainIdString); + ulong chainId = await rpcClient.GetChainIdAsync(); - FundsDistributor distributor = new(rpcClient, chainId, parseResult.GetValue(keyFileOption), SimpleConsoleLogManager.Instance); - IEnumerable hashes = await distributor.ReclaimFunds( - new(parseResult.GetValue(receiverOption)!), - parseResult.GetValue(maxFeeOption), - parseResult.GetValue(maxPriorityFeeGasOption)); + FundsDistributor distributor = new(rpcClient, chainId, parseResult.GetValue(keyFileOption), SimpleConsoleLogManager.Instance); + await distributor.ReclaimFunds( + new(parseResult.GetRequiredValue(receiverOption)), + parseResult.GetValue(maxFeeOption), + parseResult.GetValue(maxPriorityFeeGasOption)); + }); }); root.Add(command); } + private static async Task ExecuteWithRpcHandling(Func action) + { + try + { + await action(); + } + catch (RpcException ex) + { + Console.Error.WriteLine(ex.Message); + Environment.Exit(-1); + } + } + public static IJsonRpcClient InitRpcClient(string rpcUrl, ILogger logger) => new BasicJsonRpcClient( new Uri(rpcUrl), @@ -312,7 +381,7 @@ public static void SetupSendFileCommand(Command root) HelpName = "address", Required = true, }; - Option maxFeePerBlobGasOption = new("--maxfeeperblobgas") + Option maxFeePerBlobGasOption = new("--maxfeeperblobgas") { DefaultValueFactory = r => 1000, Description = "Set the maximum fee per blob data", @@ -324,13 +393,13 @@ public static void SetupSendFileCommand(Command root) Description = "A multiplier to use for gas fees", HelpName = "value" }; - Option maxPriorityFeeGasOption = new("--maxpriorityfee") + Option maxPriorityFeeGasOption = new("--maxpriorityfee") { Description = "The maximum priority fee for each transaction", HelpName = "fee" }; Option waitOption = new("--wait") { Description = "Wait for tx inclusion" }; - Option forkOption = new("--fork") { Description = "Specify fork for max blob count, target blob count, proof type" }; + Option forkOption = new("--fork") { Description = "Specify fork for max blob count, target blob count, proof type. Osaka by default" }; command.Add(fileOption); command.Add(rpcUrlOption); @@ -343,21 +412,31 @@ public static void SetupSendFileCommand(Command root) command.Add(forkOption); command.SetAction((parseResult, cancellationToken) => { - PrivateKey privateKey = new(parseResult.GetValue(privateKeyOption)!); - byte[] data = File.ReadAllBytes(parseResult.GetValue(fileOption)!); - BlobSender sender = new(parseResult.GetValue(rpcUrlOption)!, SimpleConsoleLogManager.Instance); - - string? fork = parseResult.GetValue(forkOption); - IReleaseSpec spec = fork is null ? Prague.Instance : SpecNameParser.Parse(fork); - - return sender.SendData( - data, - privateKey, - parseResult.GetValue(receiverOption)!, - parseResult.GetValue(maxFeePerBlobGasOption), - parseResult.GetValue(feeMultiplierOption), - parseResult.GetValue(maxPriorityFeeGasOption), - parseResult.GetValue(waitOption), spec); + return ExecuteWithRpcHandling(() => + { + PrivateKey privateKey = new(parseResult.GetRequiredValue(privateKeyOption)); + string filePath = parseResult.GetRequiredValue(fileOption); + if (!File.Exists(filePath)) + { + Console.WriteLine($"File not found: {filePath}"); + return Task.CompletedTask; + } + + byte[] data = File.ReadAllBytes(filePath); + BlobSender sender = new(parseResult.GetRequiredValue(rpcUrlOption), SimpleConsoleLogManager.Instance); + + string? fork = parseResult.GetValue(forkOption); + IReleaseSpec spec = fork is null ? Osaka.Instance : SpecNameParser.Parse(fork); + + return sender.SendData( + data, + privateKey, + parseResult.GetRequiredValue(receiverOption), + (UInt256)parseResult.GetValue(maxFeePerBlobGasOption), + parseResult.GetValue(feeMultiplierOption), + (UInt256?)parseResult.GetValue(maxPriorityFeeGasOption), + parseResult.GetValue(waitOption), spec); + }); }); root.Add(command); diff --git a/tools/StatelessExecution/Program.cs b/tools/StatelessExecution/Program.cs index 471da9380d25..eae8b165d37b 100644 --- a/tools/StatelessExecution/Program.cs +++ b/tools/StatelessExecution/Program.cs @@ -1,5 +1,5 @@ -// See https://aka.ms/new-console-template for more information - +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only using StatelessExecution; using System.CommandLine; @@ -7,7 +7,5 @@ RootCommand rootCommand = []; SetupCli.SetupExecute(rootCommand); -CommandLineConfiguration cli = new(rootCommand); - -return await cli.InvokeAsync(args); +return await rootCommand.Parse(args).InvokeAsync(); diff --git a/tools/StatelessExecution/SetupCli.cs b/tools/StatelessExecution/SetupCli.cs index 631fd0f893fc..cd651f30a86e 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, }; @@ -127,7 +123,7 @@ private static (Witness witness, Block block, BlockHeader parent) ReadData(strin for (int j = 0; j < transactions.Length; j++) { var tx = (JsonElement)suggestedBlockForRpc.Transactions[j]; - transactions[j] = serializer.Deserialize(tx.GetRawText()).ToTransaction(); + transactions[j] = (Transaction)serializer.Deserialize(tx.GetRawText()).ToTransaction(); } Block suggestedBlock = new Block(suggestedBlockHeader, transactions, [], suggestedBlockForRpc.Withdrawals); diff --git a/tools/StatelessExecution/StatelessExecution.csproj b/tools/StatelessExecution/StatelessExecution.csproj index 58ff2bc0aaba..8131de9abf90 100644 --- a/tools/StatelessExecution/StatelessExecution.csproj +++ b/tools/StatelessExecution/StatelessExecution.csproj @@ -2,9 +2,6 @@ Exe - net9.0 - enable - enable